★
推荐阅读人群 :Sentinel 只连过控制台、规则重启就丢、Nacos 只拿来存配置文件的后端
一句话导读 :这篇不重复"限流熔断降级是什么",讲它在 Sentinel + Nacos 里怎么落地:一条请求穿过哪几个 Slot、滑动窗口怎么记账、规则从哪来、集群流控的 token 谁发。配置和代码都是能直接跑的。
01 · 三个默认值,同时失效
大促 0 点,订单详情接口 QPS 从 2000 冲到 7600。压测上限是 5000。
雪崩链很标准:连接池 200 个打满 → Tomcat 800 线程占死 → 网关超时 → 前端重试再推一轮 → 依赖详情页的页面全挂。
Sentinel 我们两年前就接了,控制台里流控、熔断都配着。事故后复盘,三个数字全部站不住:
- 流控阈值 10000 —— 压测上限 5000,限在 10000。这规则当年在控制台点的,没进 Nacos,后来换集群重建过一次,就再没人碰过
- 熔断只配了异常比例 —— 那次故障一个异常都没抛,RT 从 80ms 拖到 4s。异常比例 0%,熔断器全程认为下游健康
- 热点参数没限 —— 一个爆款 SKU 吃掉 70% 的详情流量,接口级限流把正常请求一起拒了
Sentinel 默认把规则存在客户端内存里,重启即忘;控制台改的规则,只对当时在线的那批实例生效。
02 · 一条请求穿过什么
Sentinel 的核心不是注解,是责任链。SphU.entry() 之后,请求依次穿过这些 Slot:
SphU.entry("orderDetail")
-> NodeSelectorSlot builds DefaultNode
-> ClusterBuilderSlot merges ClusterNode
-> StatisticSlot counts in LeapArray
-> AuthoritySlot block/allow list
-> SystemSlot load / RT / threads
-> FlowSlot flow rules
-> DegradeSlot circuit breaker
从左到右:建节点、汇全局、记账、验名单、看系统水位、判流控、判熔断。每个 Slot 只查自己那本规则,通过就下一个,不通过抛 BlockException 直接短路。@SentinelResource 干的事,就是给这次 entry 包一层 AOP。
三个概念对应三个 Slot:
- 限流 —— FlowSlot,在入口,依据自己的容量
- 熔断 —— DegradeSlot,在调下游处,依据对方的表现
- 降级 —— 不在 Slot 里,是
blockHandler/fallback接住异常后的返回
判断依据不同,参数来源就完全不同:限流看压测报告,熔断看监控曲线,降级看业务方点头。三个数据源凑不齐,别谈"高可用方案"。
03 · 原理:滑动窗口怎么记账
StatisticSlot 往 ClusterNode 里记 QPS、RT、异常数,底层是 LeapArray:把 1 秒切成 N 个桶,滚着用。
intervalInMs = 1000
sampleCount = 2 -> one bucket = 500ms
+--------+--------+
| slot 0 | slot 1 |
+--------+--------+
timeId = now / 500 % 2
- 写入 —— 用
timeId定位桶,桶的windowStart对不上就整格重置 - 读取 —— 遍历所有桶求和,跨过 1 秒的旧桶自然作废
- 好处 —— 任意时刻往前看都是完整 1 秒,不会出现固定窗口那种 0:59 和 1:00 各算满 1000
这也解释了 count 的单位。QPS 模式是每秒放行数,THREAD 模式是并发执行数——后者对慢调用更敏感,下一节用得上。
04 · 限流:QPS 还是线程数
规则怎么写成代码
FlowRule r = new FlowRule("orderDetail");
r.setGrade(RuleConstant.FLOW_GRADE_QPS);
r.setCount(560);
// 0 直接拒 1 预热 2 排队
r.setControlBehavior(
RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
FlowRuleManager.loadRules(
Collections.singletonList(r));
三种 controlBehavior 的差别值得单独记:
- DEFAULT —— 超了直接拒,最快,绝大多数接口用它
- WARM_UP —— 冷启动,阈值从
count/3爬坡到count,默认 3 秒。刚发布、缓存还没热的实例必须用它,否则一上线就被打死 - RATE_LIMITER —— 匀速排队,每
1000/count毫秒放一个。注意它会让请求线程等着,别在网关用
QPS 还是 THREAD
- QPS 模式 —— 只数请求,不管每个请求多久。下游变慢时 QPS 没超,线程已经占满了
- THREAD 模式 —— 数的是"正在执行的并发数",超了直接拒,等于一个免费的信号量隔离
r.setGrade(RuleConstant.FLOW_GRADE_THREAD);
r.setCount(200); // 并发线程数,不是 QPS
结论:入口用 QPS 挡流量,出口用 THREAD 保线程。
阈值怎么算
- 压测,找 RT 突然翻倍的那个点——不是 QPS 上不去的点
- 单机压出 800 QPS,乘 0.7 得 560,余量留给 GC、慢 SQL、邻居抢 CPU
- 再乘实例数,才是集群总量
集群流控的 token 谁发
10 台各限 500,热点实例先撞墙开始拒,旁边 9 台还闲着。要精确就得集中判定,Sentinel 的做法是插一个 Token Server:
FlowRule r = new FlowRule("orderDetail");
r.setCount(5000);
r.setClusterMode(true);
ClusterFlowConfig c = new ClusterFlowConfig();
c.setFlowId(1001L);
c.setThresholdType(
ClusterFlowConfig.THRESHOLD_TYPE_GLOBAL);
r.setClusterConfig(c);
原理很直白:Client 每次请求向 Token Server 要配额,Server 用 ClusterNode 做全局统计并返回剩余量。THRESHOLD_TYPE_GLOBAL 表示 5000 是集群总量,不是单机量。
部署两种形态:
- embedded —— 一台业务实例兼做 Server,省机器,但 Server 挂了集群流控退化成单机
- 独立部署 —— 单独跑
sentinel-cluster-server,稳定,多一组需要运维的进程
多实例抢 Server 身份需要选主,一般交给 Nacos 注册中心 + 指定实例,别让它们自己抢。
05 · 熔断:慢调用才是主要死法
三种策略
- 慢调用比例
DEGRADE_GRADE_RT—— RT 超过count毫秒算慢,慢调用占比超过slowRatioThreshold就断 - 异常比例
DEGRADE_GRADE_EXCEPTION_RATIO—— 异常占比超过count(0 到 1 之间) - 异常数
DEGRADE_GRADE_EXCEPTION_COUNT—— 统计窗口内异常数超过count
DegradeRule r = new DegradeRule("orderDetail");
r.setGrade(RuleConstant.DEGRADE_GRADE_RT);
r.setCount(800); // 慢调用阈值 ms
r.setSlowRatioThreshold(0.6);
r.setTimeWindow(10); // 熔断 10 秒
r.setMinRequestAmount(20);
DegradeRuleManager.loadRules(
Collections.singletonList(r));
四个参数怎么定
count(RT 模式) —— 取 P99 的 2 到 3 倍,或者取"用户开始重复点击"的时长。800ms 不是标准值,是业务能忍的上限minRequestAmount—— 默认 5,太小。低频接口 QPS 2,不到 3 秒就凑够 5 个样本,随便抖一下就误断。按QPS × 10抬到 20 以上timeWindow—— 从进 OPEN 起算,到点转 HALF_OPEN 放一个请求探路,成功回 CLOSED,失败回 OPENstatIntervalMs—— 默认 1000。低流量接口拉到 10000,用时间换样本数
只配异常比例,等于没装熔断
线上最常见的故障形态是"慢而不死":下游没挂,只是每个请求从 80ms 变成 4s。异常数 0,异常比例 0,熔断器全程 CLOSED,线程一个个被占死,直到 Tomcat 打满。
RT 模式就是为这个场景设计的。只配异常比例的熔断,只防住了下游崩溃,没防住下游变慢——而变慢才是常态。
06 · Nacos:规则从哪来
这是整套体系里最容易漏的一环。Sentinel 的规则默认只活在客户端内存里:
- 应用重启,规则清空,裸奔到下次有人手动点控制台
- 控制台改规则,只推给当时连着的实例,新扩容的机器拿不到
- 多实例规则可能不一致,A 机限 560,B 机还是 10000
数据源模式
客户端主动去 Nacos 订阅,规则变了实时推下来:
spring:
cloud:
sentinel:
transport:
dashboard: 10.0.0.8:8858
port: 8719
eager: true
datasource:
flow:
nacos:
server-addr: 10.0.0.6:8848
namespace: prod
group-id: SENTINEL_GROUP
data-id: order-svc-flow.json
data-type: json
rule-type: flow
一个 dataId 只放一类规则。rule-type 支持 flow、degrade、system、authority、param-flow、gw-flow、gw-api-group 七种,熔断就再加一段 degrade 的数据源,结构照抄。
原理:四步
- 启动时
NacosDataSource拉一次配置,反序列化成List<FlowRule> - 包成
SentinelProperty,注册给FlowRuleManager - Nacos 侧挂监听器,配置变更回调 →
property.updateValue(list) FlowRuleManager里的AtomicReference换掉旧值,FlowSlot下次 entry 直接读新的
没有推送失败重试的坑,因为客户端是订阅方,不是被动接收方。这也是数据源模式比控制台推送可靠的根本原因。
Nacos 里存什么
[{
"resource": "GET:/order/{id}",
"grade": 1,
"count": 560,
"controlBehavior": 0,
"clusterMode": false
}]
命名建议三段定位:namespace 分环境,group 固定 SENTINEL_GROUP,dataId 用 应用名-规则类型.json。别全塞 public 和 DEFAULT_GROUP,出事那天你会找不到规则在哪。
控制台只当看板用
要,但只读。控制台改规则走的是"推给客户端内存",不写 Nacos,重启就丢。改规则走 Nacos 发布,顺便留下变更记录和审批。
版本坑
spring-cloud-starter-alibaba-sentinel 和 sentinel-core 版本必须对齐。Spring Cloud Alibaba 2022.x 对应 Sentinel 1.8.6,2023.x 对应 1.8.7 以上。错位的典型现象是 rule-type: gw-flow 静默不生效,或者启动报 ClassNotFoundException,而日志里只字不提版本。
07 · 兜底、串联与四个打架点
blockHandler 和 fallback 不是一回事
被限流、熔断拒绝时抛的是 BlockException,只进 blockHandler;业务代码里的异常才进 fallback。只写一个,等于漏掉一半场景。
@SentinelResource(
value = "orderDetail",
blockHandler = "blocked",
fallback = "businessFallback")
public OrderVO detail(Long id) {
return orderClient.get(id);
}
// 被限流、熔断拒绝
private OrderVO blocked(Long id,
BlockException e) {
return OrderVO.empty();
}
// 业务异常兜底,只读本地
private OrderVO businessFallback(Long id,
Throwable t) {
return OrderVO.empty();
}
兜底方法必须同签名、同返回值,只在末尾多一个 BlockException 或 Throwable 参数。参数对不上不会启动报错,运行时才抛 blockHandler not found——这种错往往上线后才发现。
兜底三铁律
兜底方法里不能有远程调用、不能查库、不能抛异常。它必须是最纯粹的本地返回。
反例:fallback 里调 remoteCache.get()。下游挂的时候兜底跟着挂,用户还是看到 500,这个 fallback 白写了。
四个打架点
- 阈值大于容量 —— Nacos 里躺着数字,没人验证过它等于压测上限
- 熔断没配降级 —— 熔断打开后请求被拒,用户看到错误页。熔断必须和 fallback 成对出现
- 重试喂饱熔断 —— Feign 默认重试,一次失败变两次,下游 90% 负载瞬间翻到 180%。先关重试,再开熔断
- 多层限流叠加 —— 网关 5000、服务 3000、接口 1000,实际生效的是 1000,另两层是摆设。要么统一到网关,要么分层定职责:网关防刷,服务保命
配齐了还崩,先查重试,再查阈值。
08 · 三句话总结
- Sentinel 的核心是 Slot 责任链,不是注解。
@SentinelResource只是给SphU.entry()包了层 AOP,判断全在 FlowSlot 和 DegradeSlot 里,LeapArray负责记账。 - RT 模式是主力策略。线上故障多数是慢而不死,只配异常比例等于把熔断器关掉;
minRequestAmount默认 5 太小,按QPS × 10抬。 - 规则必须离开内存。数据源模式接 Nacos,一个 dataId 管一类规则,控制台只当只读看板——不然重启一次,三道闸全回出厂设置。
写在最后
觉得有用的话,拜托两件事:
- 点「赞」+「推荐」(在看)
- 关注「斯维赤」 —— Java 实战、开发工具、踩坑复盘,持续更新
下期写「超时到底该设多少」——它比熔断基础,也比熔断更容易被随手写死。
我们下期再见