六
熔断降级:Sentinel
Flow Control & Circuit Breaking with Sentinel
第四个救命问题:一个服务挂了,不能拖死全家。下单服务要调用户服务查信息,结果用户服务因为数据库挂了卡住不返回,下单服务的线程全堵在那等,最后下单服务也跟着崩。这叫"雪崩"。Sentinel 就是刹车——访问量太大就限流,下游挂了就熔断,不让故障扩散。
论熔断状态机:关闭 → 打开 → 半开
关闭(Closed):正常状态,请求正常通过。
打开(Open):错误率/慢调用比例超阈值,熔断器打开。之后的请求不再真的去调下游,直接走降级逻辑(返回默认值)。
半开(Half-Open):过了一段时间(比如 5 秒),放一个请求过去试探。成功了就关闭熔断器恢复正常;失败了继续打开。
大白话:就像家里保险丝——电流太大自动跳闸,过一会儿手动推上去试一下,还过载就继续跳。别硬顶着烧电线。
接入 Sentinel:控制台 + 依赖
pom.xml 加 Sentinel 依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- Sentinel 与 OpenFeign 集成,让 Feign 调用也走熔断 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
application.yml 告诉业务服务"Sentinel 控制台在哪"
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 # Sentinel 控制台地址
port: 8719 # 业务服务跟控制台通信的端口
eager: true # 启动就连控制台,别等第一次调用
# 让 Feign 集成 Sentinel
feign:
sentinel:
enabled: true
@SentinelResource 定义资源与降级方法
在要保护的方法上加 @SentinelResource,指定一个 fallback 方法——熔断或限流时就调这个兜底方法,返回默认值。
订单服务里保护"查用户信息"这个方法
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.stereotype.Component;
@Component
public class OrderService {
// value = 资源名,Sentinel 控制台里就叫这个名字
// fallback = 降级方法名,熔断/限流时调它
@SentinelResource(value = "queryUser", fallback = "queryUserFallback")
public UserDTO queryUser(Long userId) {
// 正常逻辑:Feign 远程调用户服务
return userClient.getUserById(userId);
}
// 降级方法:参数和返回值必须跟原方法一致,方法名跟上面 fallback 对应
public UserDTO queryUserFallback(Long userId) {
UserDTO dto = new UserDTO();
dto.setId(userId);
dto.setName("用户信息暂不可用(降级返回)");
return dto;
}
}
| 规则类型 | 大白话 |
| 流控规则(QPS) | 每秒请求数超过阈值就限流,超出的请求直接拒绝。 |
| 降级规则(RT) | 响应时间超过阈值、或异常比例太高,就熔断一段时间。 |
| 热点参数限流 | 针对某个参数值单独限流,比如用户 ID=1234 访问太频繁就限制他。 |
| 系统保护 | 整台机器 CPU、负载太高时,自动整体限流,保命用。 |
热点参数限流:只治那个闹事的人
全站 QPS 没超,但某个 orderId=999(黄牛、爬虫)一秒刷几百次。普通流控是"整个接口一刀切",热点参数限流能针对某一个参数值单独设阈值,只治他,不影响其他正常用户。
// ① 方法上标 @SentinelResource,第二个参数 orderId 就是热点参数
@SentinelResource(value = "getOrder", blockHandler = "block")
public OrderDTO getOrder(Long userId, Long orderId) { ... }
// ② 控制台配:簇点链路 → getOrder → 热点参数 → 参数索引选 1(orderId)
// - 单机阈值:普通参数 QPS=10
// - 例外项:orderId=999 单独设 QPS=2,这个黄牛被单独按住
论参数索引怎么数
方法参数从 0 开始数。getOrder(Long userId, Long orderId) 里 userId 是索引 0、orderId 是索引 1。想对哪个参数做热点限流,控制台就选那个索引。热点限流只对参数类型支持的方法生效,配合 @SentinelResource 用。
系统规则:整机快扛不住时的最后一道闸
前面的流控、熔断都是"按接口"治。系统保护(系统规则)是"按整台机器"治:CPU 使用率、系统 Load、入口 QPS 超过阈值,Sentinel 直接拒绝新请求,保住机器不被打死。控制台"系统规则"里配。
| 维度 | 人话 |
| CPU 使用率 | 机器 CPU 超过阈值(如 80%),开始限流。 |
| 系统 Load | 系统负载过高时限流(适合不关注 CPU 细节的场景)。 |
| 入口 QPS / 平均 RT | 单机入口总 QPS 或平均响应时间超阈值,整体拒绝。 |
完整案例:@SentinelResource + blockHandler + fallback
Sentinel 里两个兜底方法容易混:blockHandler 管"被限流/熔断挡住"的情况,fallback 管"业务方法抛异常"的情况。两个一起用,谁触发谁兜底。
一个方法同时配 blockHandler 和 fallback
@SentinelResource(
value = "getOrder",
blockHandler = "getOrderBlock", // 被流控/熔断时调它
fallback = "getOrderFallback") // 抛异常时调它
public OrderDTO getOrder(Long orderId) {
return orderDao.selectById(orderId); // 查库,可能抛异常
}
// blockHandler 必须额外加一个 BlockException 参数,签名要对得上
public OrderDTO getOrderBlock(Long orderId, BlockException ex) {
OrderDTO dto = new OrderDTO();
dto.setNote("手速太快,被限流了,喝口水再来");
return dto;
}
public OrderDTO getOrderFallback(Long orderId, Throwable t) {
OrderDTO dto = new OrderDTO();
dto.setNote("订单系统开小差,稍后再试");
return dto;
}
控制台里怎么配规则(操作路径)
# 启动 Sentinel 控制台(默认 8080)
# 浏览器开 http://localhost:8080 ,看到 order-service 已连上来
# 1. 流控规则:
# 簇点链路 → getOrder → 流控 → 单机阈值 QPS = 5 → 确定
# 意思:一秒超过 5 次访问 getOrder,多的走 blockHandler
# 2. 降级规则:
# 降级 → 新增 → 异常比例 0.5(错误率超 50%)→ 熔断 10 秒
# 3. 热点参数:
# 热点参数 → 给 orderId 设规则,比如 id=12345 的用户单独限流
验证效果
# 正常:
GET /order/getOrder?orderId=1 → 正常订单数据
# 用压测工具一秒打 10 次,超过 QPS=5 的部分:
GET /order/getOrder?orderId=1 → {"note":"手速太快,被限流了,喝口水再来"}
# 故意把数据库停了让方法抛异常:
GET /order/getOrder?orderId=1 → {"note":"订单系统开小差,稍后再试"}
Sentinel 规则默认不持久化
你在 Sentinel 控制台配的流控规则,默认只存在内存里,服务重启就没了。生产环境必须把规则持久化到 Nacos——控制台改规则时同步写 Nacos,业务服务启动时从 Nacos 拉。网上有现成的"规则持久化到 Nacos"的配置写法,搜 Sentinel Nacos rule persistent 即可,以官方 wiki 最新说明为准。
本章面试题
面试快答 · 熔断降级与限流
Q1. 限流的常见算法有哪些?
参考答案
① 计数器(固定窗口,临界突发问题);② 滑动窗口;③ 漏桶(匀速消费,平滑但不允许突发);④ 令牌桶(匀速补令牌,允许一定突发,最常用)。Gateway 的 RequestRateLimiter 和 Sentinel 的排队等待都基于令牌桶思想。
Q2. 熔断和限流有什么区别?
参考答案
限流是"防自己被打死"——请求太多,多的直接拒绝,保护自己;熔断是"防被下游拖死"——下游错误率太高,干脆不调它、走降级,保护下游也保护自己线程池。一个是入口节流,一个是出口断连。
Q3. blockHandler 和 fallback 到底谁管谁?
参考答案
blockHandler 只在被 Sentinel 限流/熔断(BlockException)时触发;fallback 在方法本身抛业务异常时触发。签名上 blockHandler 方法要额外加一个 BlockException 参数。
Q4. 为什么会雪崩?怎么从架构上防?
参考答案
同步调用 + 下游变慢 → 调用方线程全阻塞在等下游 → 线程池耗尽 → 自己也崩。防线:超时(别无限等)、熔断(下游挂了别再打)、限流(别让自己过载)、降级(返回兜底)、异步(MQ 解耦)。