在微服务架构里,服务间调用关系错综复杂,一个服务出问题很可能引发连锁反应,也就是 “雪崩”。今天就带大家从零开始学习 Sentinel,这款阿里开源的微服务保护工具,帮你解决雪崩难题,做好流量控制、隔离降级。
一、初识 Sentinel:先搞懂雪崩和解决方案
1.1 雪崩问题到底是啥?
微服务中,一个服务往往依赖多个其他服务。比如服务 A 依赖服务 B,服务 B 又依赖服务 C。要是服务 C 故障了,服务 B 调用 C 时会阻塞,服务 A 调用 B 也会跟着阻塞。
服务器的线程和并发数是有限的,一旦大量请求阻塞,服务器资源很快就会耗尽,进而导致所有服务不可用。接着,依赖这些服务的其他服务也会陆续 “挂掉”,形成级联失败 —— 这就是雪崩。
1.2 解决雪崩的 4 种核心方案
既然雪崩这么可怕,那怎么防?主要有 4 种思路:
- 超时处理:给请求设个超时时间,超过时间没响应就返回错误,别一直等。比如调用一个服务,超过 200ms 就直接提示 “请求超时”,避免线程一直占用。
- 仓壁模式(线程隔离):像船舱的防水隔板一样,给每个业务分配独立的线程池。比如订单业务用 10 个线程,商品业务用 10 个线程,就算订单业务线程耗尽,也不影响商品业务。
- 断路器:统计服务调用的异常比例,要是超过阈值(比如异常率 50%),就 “熔断”—— 暂时拦截所有访问该服务的请求,等服务恢复了再放行。
- 限流:限制服务的 QPS(每秒请求数),比如每秒最多处理 100 个请求,超过的直接拒绝,避免突发流量把服务冲垮。
简单总结下:限流是 “预防”,提前挡住过多流量;超时、仓壁、断路器是 “补救”,故障发生时控制影响范围。
1.3 服务保护工具对比:为啥选 Sentinel?
Spring Cloud 里有 Hystrix、Resilience4J、Sentinel 等工具,国内用得最多的是 Sentinel,对比 Hystrix 优势很明显:
对比项 | Sentinel | Hystrix |
---|---|---|
隔离策略 | 信号量隔离 | 线程池 / 信号量隔离 |
熔断策略 | 慢调用 / 异常比例 | 失败比率 |
流量整形 | 支持预热、排队等待 | 不支持 |
控制台 | 开箱即用,秒级监控 | 不完善 |
生态适配 | Spring Cloud、Dubbo 等 | 主要适配 Spring Cloud Netflix |
Sentinel 不仅功能全,还能承接阿里双十一大促的流量,稳定性有保障,这也是咱们选它的核心原因。
1.4 Sentinel 安装:3 步搞定
Sentinel 有个可视化控制台,能配置规则、看监控,安装特别简单:
- 下载 jar 包:去 GitHub 搜 “Sentinel”,下载最新的 dashboard jar 包(比如 sentinel-dashboard-1.8.1.jar)。
- 运行 jar 包:把 jar 包放到非中文目录,打开命令行执行:
# 默认端口8080,账号密码都是sentinel java -jar sentinel-dashboard-1.8.1.jar # 想改端口的话,比如改到8090 java -Dserver.port=8090 -jar sentinel-dashboard-1.8.1.jar
- 访问控制台:打开浏览器输
http://localhost:8080
,输入账号密码(都是 sentinel)就能进。刚进去是空白的,因为还没整合微服务。
1.5 微服务整合 Sentinel:以 order-service 为例
以订单服务(shop-order)为例,整合 Sentinel 就 3 步:
- 加依赖:在 pom.xml 里加 Sentinel 依赖:
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
- 配控制台地址:在 application.yaml 里加配置,告诉服务控制台在哪:
spring:cloud:sentinel:transport:dashboard: localhost:8080 # 控制台地址
- 触发监控:启动服务后,访问一个接口(比如
http://localhost:8091/order/prod/19
),再刷新控制台,就能看到服务的监控数据了。
二、流量控制:提前挡住突发流量
限流是预防雪崩的关键,Sentinel 的流量控制功能很强大,咱们从基础到进阶慢慢讲。
2.1 先认识 “簇点链路”
请求进微服务时,会走DispatcherServlet → Controller → Service → Mapper
这样的调用链,这就是 “簇点链路”。链路上的每个接口(比如 Controller 的方法)都是一个 “资源”,Sentinel 默认会监控 SpringMVC 的所有接口。
在控制台的 “簇点链路” 菜单里,能看到所有资源,每个资源后面都有 “流控”“降级” 等按钮,点进去就能配置规则。
2.2 流控快速入门:给接口设 QPS 阈值
比如给/order/prod/{pid}
这个接口设限流规则:QPS 不超过 5(每秒最多 5 个请求)。
- 加规则:在簇点链路里,找到
/order/prod/{pid}
,点后面的 “流控”,配置:- 阈值类型:QPS
- 单机阈值:5
- 其他默认,点 “新增”。
- 用 Jmeter 测试:建个线程组,20 个用户、2 秒内发完(QPS=10,超过 5)。运行后看结果,每秒只会成功 5 个请求,超过的会被拒绝。
2.3 3 种流控模式:应对不同场景
流控模式是指 “什么时候触发限流”,Sentinel 支持 3 种,咱们结合场景讲:
2.3.1 直接模式:默认模式,对当前资源限流
刚才的快速入门就是直接模式 —— 只要当前资源的 QPS 超过阈值,就直接限流。适合简单的接口保护,比如普通查询接口。
2.3.2 关联模式:高优先级资源优先,限流低优先级
场景:比如订单的 “更新”(/order/update)和 “查询”(/order/query)都操作数据库,更新要优先保证,要是更新请求太多,就限流查询。
- 加接口:在 OrderController 里加两个接口:
// 查询订单 @GetMapping("/order/query") public String queryOrder() {return "查询订单成功"; } // 更新订单 @GetMapping("/order/update") public String updateOrder() {return "更新订单成功"; }
- 配规则:给
/order/query
加关联流控规则:- 流控模式:关联
- 关联资源:
/order/update
(当更新接口 QPS 超 5 时,限流查询) - 阈值:5
- 测试:用 Jmeter 压
/order/update
(QPS=10),再用浏览器访问/order/query
,会提示 “Blocked by Sentinel (flow limiting)”—— 查询被限流了。
2.3.3 链路模式:只限流指定来源的请求
场景:查询订单(/order/query)和创建订单(/order/save)都要调用 “查询商品”(queryGoods)方法,只想限流从 “查询订单” 进来的请求。
- 加方法和接口:
- OrderService 加 queryGoods 方法,用
@SentinelResource("goods")
标记(让 Sentinel 监控):@SentinelResource("goods") public void queryGoods() {System.err.println("查询商品"); }
- OrderController 加
/order/save
,并让/order/query
和/order/save
都调用 queryGoods:// 查询订单调用商品查询 @GetMapping("/order/query") public String queryOrder() {orderService.queryGoods();return "查询订单成功"; } // 新增订单调用商品查询 @GetMapping("/order/save") public String saveOrder() {orderService.queryGoods();return "新增订单成功"; }
- OrderService 加 queryGoods 方法,用
- 关资源聚合:Sentinel 默认会把 SpringMVC 请求聚合到一个 root 资源,链路模式会失效,所以要在 application.yaml 里关了:
yaml
spring:cloud:sentinel:web-context-unify: false # 关闭context整合
- 配规则:给 “goods” 资源加链路流控规则:
- 流控模式:链路
- 入口资源:
/order/query
(只统计从查询订单进来的请求) - 阈值:2
- 测试:用 Jmeter 压
/order/save
(QPS=4),访问/order/save
正常;压/order/query
(QPS=4),超过 2 的请求会被限流。
2.4 3 种流控效果:超过阈值后怎么处理?
流控效果是指 “QPS 超阈值后,怎么处理新请求”,Sentinel 支持 3 种:
2.4.1 warm up(预热模式):应对服务冷启动
服务刚启动时,资源没初始化(比如连接池没建立),要是直接扛最大 QPS,可能会宕机。预热模式就是让阈值从一个小值慢慢涨到最大值,给服务 “预热” 时间。
比如给/order/prod/{pid}
设规则:
- 阈值类型:QPS
- 单机阈值:10(最大 QPS)
- 流控效果:warm up
- 预热时长:5 秒
Sentinel 默认冷启动因子是 3,所以初始阈值是 10/3≈3。刚启动时,每秒只能过 3 个请求,5 秒后慢慢涨到 10 个。用 Jmeter 测试,刚开始失败率高,后来会越来越低。
2.4.2 排队等待:让请求平滑执行
快速失败和 warm up 都是直接拒绝超阈值请求,排队等待则是让请求排队,按固定间隔执行,避免流量波动。
比如给/order/prod/{pid}
设规则:
- 阈值类型:QPS
- 单机阈值:10(每秒 10 个,即每 200ms 一个)
- 流控效果:排队等待
- 超时时间:5000ms(请求等待超过 5 秒就拒绝)
用 Jmeter 压 QPS=15(超阈值),结果所有请求都会通过,但响应时间会变长 —— 因为都在排队。控制台看 QPS 曲线会很平滑,一直保持在 10 左右,对服务器很友好。
2.4.3 快速失败:默认效果,直接拒绝
超过阈值后,新请求直接被拒绝,抛出FlowException
,适合对响应时间敏感的场景(比如支付接口,不能让用户等)。
2.5 热点参数限流:对不同参数区别对待
之前的限流是统计整个资源的 QPS,热点参数限流则是按 “参数值” 统计。比如/order/prod/{pid}
接口,pid=1 是秒杀商品(热点),pid=2 是普通商品,想给它们设不同的 QPS 阈值。
2.5.1 实战案例:给 pid 设不同阈值
需求:/order/prod/{pid}
默认 QPS=2,pid=1 设 QPS=4,pid=19 设 QPS=10。
- 标记资源:给接口加
@SentinelResource("hot")
(热点限流要这个注解):@SentinelResource("hot") @GetMapping("/order/prod/{pid}") public String getOrderByPid(@PathVariable Integer pid) {// 业务逻辑 }
- 加热点规则:控制台进 “热点规则”,点 “新增”:
- 资源名:hot
- 限流模式:QPS 模式
- 参数索引:0(第一个参数,即 pid)
- 单机阈值:2(默认阈值)
- 高级选项→参数例外项:
- 加 pid=1(long 类型),阈值 4
- 加 pid=19(long 类型),阈值 10
- 测试:用 Jmeter 分别压 pid=1(QPS=5)、pid=19(QPS=12)、普通 pid(QPS=3):
- pid=1:每秒成功 4 个
- pid=19:每秒成功 10 个
- 普通 pid:每秒成功 2 个
三、隔离和降级:故障发生时控制影响范围
限流是预防,隔离和降级是故障发生后的 “止损” 手段。Sentinel 主要通过线程隔离(仓壁模式)和熔断降级来实现。
3.1 Feign 整合 Sentinel:远程调用要保护
微服务远程调用大多用 Feign,所以要先整合 Feign 和 Sentinel,才能在远程调用时做隔离和降级。
3.1.1 开启 Feign 的 Sentinel 支持
在 order-service 的 application.yaml 里加配置:
feign:sentinel:enabled: true # 开启Feign对Sentinel的支持
3.1.2 写失败降级逻辑
远程调用失败时(比如服务宕机),不能直接报错,要返回友好提示。有两种方式:
方式 1:FallbackClass(不支持捕获异常)
- 写降级类,实现 Feign 接口:
@Component public class ProductServiceFallback implements IProductService {@Overridepublic Product findByPid(Integer pid) {// 降级逻辑:返回默认值Product product = new Product();product.setPid(-1);product.setPname("暂无商品");return product;} }
- Feign 接口指定降级类:
@FeignClient(value = "service-product", fallback = ProductServiceFallback.class) public interface IProductService {@GetMapping("/product/{pid}")Product findByPid(@PathVariable Integer pid); }
方式 2:FallbackFactory(支持捕获异常)
要是想知道调用失败的原因,就用 FallbackFactory:
- 写降级工厂类:
@Component public class ProductServiceFallbackFactory implements FallbackFactory<IProductService> {@Overridepublic IProductService create(Throwable throwable) {// 打印异常信息System.out.println("调用失败:" + throwable.getMessage());// 返回降级逻辑return new IProductService() {@Overridepublic Product findByPid(Integer pid) {Product product = new Product();product.setPid(-1);product.setPname("暂无商品");return product;}};} }
- Feign 接口指定降级工厂:
@FeignClient(value = "service-product", fallbackFactory = ProductServiceFallbackFactory.class) public interface IProductService {@GetMapping("/product/{pid}")Product findByPid(@PathVariable Integer pid); }
3.2 线程隔离(仓壁模式):避免资源耗尽
线程隔离是给每个远程调用分配独立的线程池(或信号量),就算某个调用故障,也只消耗该线程池的资源,不影响其他调用。
3.2.1 两种隔离方式对比
隔离方式 | 原理 | 优点 | 缺点 |
---|---|---|---|
信号量隔离 | 计数器模式,统计线程数 | 简单,开销小 | 不能隔离慢调用 |
线程池隔离 | 给每个调用分配独立线程池 | 隔离彻底,支持慢调用 | 线程切换开销大 |
Sentinel 默认用信号量隔离,要是需要隔离慢调用(比如调用一个慢服务),就用线程池隔离。
3.2.2 实战:给 Feign 接口设线程数阈值
需求:order-service 调用 product-service 的接口,线程数不超过 2(即同时最多 2 个线程调用)。
- 加规则:在簇点链路里找到 Feign 接口(比如
/product/{pid}
),点 “流控”:- 阈值类型:线程数
- 单机阈值:2
- 其他默认
- 测试:用 Jmeter 发 10 个并发请求(瞬间发完),会发现部分请求返回降级逻辑(“暂无商品”)—— 因为线程数超 2 了,超出的请求被限流。
3.3 熔断降级:故障服务暂时 “隔离”
要是一个服务调用失败率很高(比如 80% 的请求都失败),继续调用只会浪费资源,这时候就该 “熔断”—— 暂时拦截所有调用,等服务恢复了再放行。
Sentinel 的熔断策略有两种:
- 慢调用比例:请求响应时间超过 “慢调用阈值” 的比例,超过阈值就熔断。
- 异常比例:调用失败(抛异常)的比例超过阈值,就熔断。
配置方式和流控类似,在簇点链路里点 “降级”,选对应的策略和阈值即可。比如设 “异常比例”:异常率超过 50%,熔断时长 5 秒 ——5 秒内所有调用都会被拦截,5 秒后会放少量请求试探,要是恢复了就取消熔断。
总结
Sentinel 的核心功能就是 “流量控制” 和 “隔离降级”:
- 流量控制:通过直接 / 关联 / 链路模式,结合快速失败 /warm up / 排队效果,提前挡住突发流量,预防雪崩。
- 隔离降级:通过线程隔离(仓壁模式)和熔断降级,在服务故障时控制影响范围,避免级联失败。
实际项目中,要根据业务场景选择合适的规则 —— 比如秒杀接口用 warm up + 热点参数限流,慢服务调用用线程池隔离,核心接口用熔断降级。多测试、多调优,才能让微服务更稳定~