黑马商城学习项目07-微服务保护
微服务核心问题解决方案:服务保护与分布式事务
在微服务远程调用的世界里,就像一条串联着多个站点的列车线路 —— 只要其中一个站点出问题,整条线路都可能瘫痪。比如你在购物 APP 里查购物车,背后其实是购物车服务在 “拜托” 商品服务拿最新商品信息。可要是商品服务突然 “罢工”,原本只是 “查商品” 的小问题,很可能让 “查购物车” 也跟着报错;更糟的是,要是商品服务因为人太多 “堵死了”,还会像多米诺骨牌一样,把购物车服务、甚至更多关联服务都拖垮。除此之外,下单时 “扣库存、存订单、清购物车” 这一系列操作,怎么保证要么全成功、要么全失败,也是个让人头疼的难题。不过别担心,今天我们就带着这些问题,一步步找到解决办法!
1. 微服务保护
要让微服务像 “打不死的小强” 一样稳健,避免级联失败引发的 “雪崩灾难”,这就是微服务保护的核心目标。接下来我们会拆解常见的保护方案,再用实际工具落地这些方案。
1.1 服务保护方案
微服务保护的思路,其实和我们应对生活中的 “风险” 很像 —— 比如节假日景区限流、家里装防火门、电路装保险丝,都是通过 “牺牲一点便利,换整体安全”。这些方案本质上都属于 “服务降级”,虽然可能让部分功能体验打折扣,但能保住整个服务不崩溃。
1.1.1 请求限流
你有没有过这种经历:热门奶茶店突然排长队,店员会说 “今天限购 100 杯”?这就是现实中的 “限流”。微服务里的请求限流也是一个道理 —— 服务故障的头号元凶,往往是 “突发的高并发”,比如某商品突然爆火,一瞬间几万用户查购物车,直接把服务器 “冲垮”。
请求限流就像给服务装了一个 “智能闸门”:不管上游冲过来多少请求,闸门只会按照我们设定的 “速度” 放行。比如设定每秒最多处理 6 个查购物车请求,哪怕来了 10 个,也只会让 6 个通过,剩下的 4 个先 “排队” 或 “礼貌拒绝”。这就像水电站的大坝,既能拦住突发的洪水,又能让下游水流始终平稳,避免下游被冲毁。
1.1.2 线程隔离
假设你家有两个卧室,一个卧室漏水了,如果没有墙壁隔开,水会流到另一个卧室;但有了墙壁,漏水只会局限在一个房间里。线程隔离的思路,就来自轮船的 “舱壁模式”—— 轮船的船舱被隔板分成一个个独立空间,就算某部分进水,也不会让整艘船沉没。
在微服务里,这个 “隔板” 就是 “线程资源限制”。比如查购物车时需要调用商品服务,我们给 “查商品” 这个操作单独分配 20 个线程 —— 就算商品服务卡得要死,最多也只会占用这 20 个线程,不会把购物车服务的所有线程都耗光。这样一来,你用 “加购物车”“改商品数量” 这些功能时,依然能快速响应,不会被 “查购物车” 的故障拖累。
1.1.3 服务熔断
线程隔离能阻止故障扩散,但如果商品服务一直 “慢吞吞” 或 “报错”,每次调用它还是会浪费时间、消耗资源 —— 就像你明知某条路堵车,还非要硬闯,结果只是白白浪费时间。这时候就需要 “服务熔断”,像电路里的保险丝一样:一旦电流过大,立刻断开,避免电器烧毁。
服务熔断要做两件关键的事:
- 准备 “备用方案”(降级逻辑):比如查商品失败时,不直接报错,而是返回 “商品信息暂时无法获取”,或者展示购物车里的历史商品信息 —— 至少让用户能看到购物车,而不是一个空白页面。
- 智能 “断连” 与 “恢复”:统计商品服务的故障情况(比如慢请求太多、报错太多),一旦超过阈值,就暂时 “断开” 对它的调用,所有请求直接走备用方案;等过一会儿,再试探性地发一个请求,如果成功了,就恢复正常调用;如果还是失败,就继续 “断连”。
1.2 Sentinel
讲完了方案,我们来认识一个 “实战工具”——Sentinel。它就像微服务的 “保安队长”,既能帮我们实现限流、隔离、熔断,还能实时监控服务状态。Sentinel 分为两部分:
- 核心库(Jar 包:埋在我们的微服务项目里,负责执行限流、熔断逻辑;
- 控制台(Dashboard):一个可视化界面,能看到服务的实时状态,还能手动配置保护规则。
1.2.1 介绍和安装
下载 “保安队长的控制台”
去 Sentinel 的 GitHub Releases 页面(https://github.com/alibaba/Sentinel/releases)下载最新的 Jar 包,或者用课前提供的版本,把它重命名为sentinel-dashboard.jar(方便记忆),放在一个没有中文、没有特殊符号的文件夹里。启动控制台
打开命令行,进入 Jar 包所在的文件夹,输入下面的命令:1
java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
这条命令的意思是:让控制台跑在 8090 端口,并且把自己也纳入监控范围,名字叫 “sentinel-dashboard”。
登录控制台
打开浏览器,访问http://localhost:8090,输入默认账号和密码(都是sentinel),就能看到控制台界面了 —— 默认会显示控制台自己的监控数据,就像保安队长先给自己做了个体检。
1.2.2 微服务整合
接下来,我们让购物车服务(cart-service)“认” 这个保安队长,步骤很简单:
引入 Sentinel 依赖
在 cart-service 的pom.xml里加一段依赖,相当于给购物车服务装了 “保安队员”:1
2
3
4
5<!--sentinel 核心库(保安队员)-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>告诉 “队员” 控制台在哪
修改 cart-service 的application.yaml,添加控制台地址:1
2
3
4
5spring:
cloud:
sentinel:
transport:
dashboard: localhost:8090 # 保安队长的地址触发监控
重启 cart-service,然后随便访问一个购物车接口(比如查购物车)—— 这时候 “队员” 会主动把购物车服务的信息上报给控制台。刷新控制台的 “簇点链路” 页面,就能看到购物车服务的接口列表了。
这里有个小细节:我们的接口是 Restful 风格,比如 /carts 既能查购物车(GET),又能删购物车(DELETE)。默认情况下,Sentinel 会把 “/carts” 当成一个资源,没法区分请求方式。解决办法很简单,在 application.yaml 里加一行配置,让 Sentinel 把 “请求方式 + 路径” 当成资源名(比如 GET:/carts、DELETE:/carts):
1 | spring: |
重启后再访问接口,控制台的 “簇点链路” 就会显示不同请求方式的接口了,再也不会 “张冠李戴”。
1.3 请求限流
现在我们给 “查购物车”(GET:/carts)接口装个 “限流闸门”,比如每秒最多允许 6 个请求通过。
- 配置限流规则
在控制台的 “簇点链路” 页面,找到GET:/carts,点击后面的 “流控” 按钮,弹出配置框:- 阈值类型选 “QPS”(每秒请求数);
- 阈值填 “6”;
其他默认,点击 “确定”。
- 测试限流效果
用 Jemeter 模拟 “每秒 10 个请求” 的场景(就像 10 个人同时查购物车)。跑一会儿后看控制台监控:- 通过的请求每秒稳定在 6 个左右;
- 被拒绝的请求每秒大概 4 个;
完全符合我们设定的规则,就像闸门精准地控制着水流速度,不会让服务器 “超负荷工作”。
1.4 线程隔离
限流能防 “突发流量”,但如果商品服务本身出了问题(比如响应很慢),查购物车时调用商品服务,还是会占用购物车服务的线程。这时候就需要线程隔离,给 “调用商品服务” 这个操作划一个 “专属线程池”。
1.4.1 OpenFeign 整合 Sentinel
我们调用商品服务用的是 Feign(就像购物车服务给商品服务 “打电话”),所以要先让 Feign 支持 Sentinel,步骤如下:
开启 Feign 的 Sentinel 支持
修改 cart-service 的application.yaml,加一行配置:1
2
3feign:
sentinel:
enabled: true # 让Feign调用支持Sentinel保护调整 Tomcat 线程数(方便测试)
默认情况下,Tomcat 的最大线程数是 200,单机测试很难 “打满” 线程。我们把它调小一点,让隔离效果更明显:1
2
3
4
5
6
7server:
port: 8082
tomcat:
threads:
max: 50 # 最多50个工作线程
accept-count: 50 # 最多50个排队请求
max-connections: 100 # 最多100个连接查看 Feign 资源
重启 cart-service,访问一次查购物车接口(触发 Feign 调用商品服务)。再看控制台的 “簇点链路”,会发现多了一个 Feign 相关的资源(比如GET:http://item-service/items)—— 这就是调用商品服务的接口,我们要给它做线程隔离。
1.4.2 配置线程隔离
- 设置隔离规则
在 “簇点链路” 里找到 Feign 资源(比如GET:http://item-service/items),点击 “流控” 按钮:- 阈值类型选 “并发线程数”;
- 阈值填 “5”(最多用 5 个线程调用商品服务);
点击 “确定”。
- 测试隔离效果
用 Jemeter 模拟 “每秒 100 个查购物车请求”—— 这时候:- 调用商品服务的请求,每秒最多通过 10 个左右(因为 5 个线程,每个线程每秒处理 2 个请求);
- 但购物车服务的其他接口(比如
POST:/carts加购物车),响应依然很快,完全不受影响。
这就像给 “查商品” 这个操作装了一道 “防火墙”,就算它出问题,也不会占用其他操作的资源。
1.5 服务熔断
线程隔离解决了 “故障扩散”,但如果商品服务一直 “慢吞吞”(比如每次响应要 500 毫秒),就算只占 5 个线程,也会让查购物车的响应时间变长,用户体验还是不好。这时候就需要 “熔断”—— 既然这条路走不通,不如暂时不走,直接用备用方案。
1.5.1 编写降级逻辑
降级逻辑就是 “调用失败时的备用方案”。比如查商品失败时,不报错,而是返回空列表(让购物车只显示商品 ID,至少用户能看到自己加了什么);而扣库存失败时,要报错(因为下单必须扣库存成功)。
给 Feign 写降级逻辑有两种方式,我们选更灵活的 “FallbackFactory”(能处理异常信息):
定义降级处理类
在 hm-api 模块里,新建ItemClientFallback类,实现FallbackFactory<ItemClient>:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33package com.hmall.api.client.fallback;
import com.hmall.api.client.ItemClient;
import com.hmall.api.dto.ItemDTO;
import com.hmall.api.dto.OrderDetailDTO;
import com.hmall.common.exception.BizIllegalException;
import com.hmall.common.utils.CollUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import java.util.Collection;
import java.util.List;
public class ItemClientFallback implements FallbackFactory<ItemClient> {
public ItemClient create(Throwable cause) {
return new ItemClient() {
public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
log.error("远程调用ItemClient#queryItemByIds方法出现异常,参数:{}", ids, cause);
// 查购物车允许失败,返回空列表(用户至少能看到购物车结构)
return CollUtils.emptyList();
}
public void deductStock(List<OrderDetailDTO> items) {
// 扣库存失败必须报错,触发事务回滚
throw new BizIllegalException(cause);
}
};
}
}注册降级 Bean
在 hm-api 的DefaultFeignConfig类里,把ItemClientFallback注册成 Spring Bean:1
2
3
4
public ItemClientFallback itemClientFallback() {
return new ItemClientFallback();
}绑定 Feign 接口
在ItemClient接口上,用@FeignClient的fallbackFactory属性绑定降级类:1
2
3
4
public interface ItemClient {
// 接口方法...
}测试降级效果
重启服务后,用 Jemeter 压测查购物车接口 —— 被限流的请求不会报错,而是返回空的商品列表,购物车依然能正常显示,用户体验好了很多。
1.5.2 服务熔断
现在我们配置 “慢调用熔断”:如果调用商品服务的请求,超过 200 毫秒还没返回,就算 “慢调用”;最近 1 秒内至少有 5 次请求,且慢调用比例超过 50%,就触发熔断,20 秒内不再调用商品服务,直接走降级逻辑。
- 配置熔断规则
在控制台 “簇点链路” 里,找到 Feign 资源(GET:http://item-service/items),点击 “熔断” 按钮:- 熔断策略选 “慢调用比例”;
- 最大 RT(慢调用阈值)填 “200”(超过 200 毫秒算慢);
- 比例阈值填 “0.5”(50% 以上慢调用);
- 最小请求数填 “5”(至少 5 次请求才统计);
- 熔断时长填 “20”(熔断后 20 秒不调用);
点击 “确定”。
- 测试熔断效果
用 Jemeter 压测查购物车接口:- 刚开始,请求会正常调用商品服务,但因为商品服务慢,很快就达到 “50% 慢调用比例”;
- 触发熔断后,调用商品服务的请求直接被拦截,通过 QPS 降到 0,所有请求走降级逻辑;
- 此时查购物车的平均响应时间很短(不用等慢调用了),购物车服务整体依然稳健。
等 20 秒后,熔断进入 “半开状态”,会放行一个请求试探:如果商品服务恢复了,就回到 “关闭状态”,正常调用;如果还是慢,就继续 “打开状态”,避免浪费资源。
-
感谢你赐予我前进的力量