ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

限流阈值改一半就上线:Sentinel 滑动窗口与规则热更新的两个坑

限流阈值改一半就上线:Sentinel 滑动窗口与规则热更新的两个坑 title: 限流阈值改一半就上线Sentinel 滑动窗口与规则热更新的两个坑date: 2026-08-31tags: [Sentinel, 熔断降级, 限流, 微服务, 高可用]一次「规则不一致」引发的雪崩我们网关对下单接口设了 Sentinel QPS 流控规则存在 Nacos 里通过 Dashboard 改了就自动推送到所有节点。某天下午运营说要把下单接口从 1000 QPS 临时降到 200 应对一场小活动。我在 Dashboard 上把值从 1000 改成 200点了保存看监控里几个节点的 QPS 线掉下去了就以为生效了。半小时后下游订单库的连接池告警HikariPool-1 - Connection is not available, request timed out after 30000ms。排查发现集群 8 个网关节点里有 3 个节点的 QPS 线根本没掉还是按 1000 在放流量。这三台承接的瞬时流量远超 200直接把订单服务的 MySQL 连接打满连锁拖垮了整个下单链路。根因是 Nacos 的配置推送是「尽力而为」的Dashboard 改完配置写入 NacosNacos 通过长连接 notify 各节点但当时一台网关节点的 Nacos client 因为一次 Full GC 丢了连接没收到这次推送一直用着旧的 1000 阈值。换句话说Sentinel 的限流能力再强规则没下发到位就是零。滑动窗口到底在数什么很多人以为 Sentinel 的「QPS 流控」就是数一秒内的请求数其实它用的是滑动窗口LeapArray把一秒切成sampleCount个小窗口每个窗口独立计数请求落在哪个窗口就累加哪个。// Sentinel 1.8.6 中流控统计的核心结构 public class LeapArrayT { private final int sampleCount; // 一秒内被切成的窗口数默认 2 private final int intervalInMs; // 统计窗口总长度默认 1000ms protected final AtomicReferenceArrayWindowWrapT array; public WindowWrapT currentWindow(long timeMillis) { long timeId timeMillis / windowLengthInMs(); // 当前时间属于第几个窗口 int idx (int) (timeId % array.length()); WindowWrapT old array.get(idx); if (old null || isWindowDeprecated(timeMillis, old)) { // 窗口过期或被复用重置计数 WindowWrapT w new WindowWrap(windowLengthInMs(), timeId, newEmptyBucket()); array.compareAndSet(idx, old, w); return w; } return old; } }逐行解释- 第 3-4 行sampleCount是窗口切片数默认 2intervalInMs是统计总时长默认 1000ms两者相除得到单窗口长度 500ms。- 第 9 行timeId计算当前时间戳落在全局时间轴上的第几个窗口第 10 行idx通过取模把无限的时间轴映射到固定长度的环形数组这是典型的「时间轮」思路。- 第 11-15 行如果目标窗口不存在或已过期isWindowDeprecated就用 CAS 重置一个新窗口否则复用旧窗口累加。关键点统计值不是「精确的一秒」而是「最近 N 个窗口的滑动和」。如果sampleCount只有 2意味着窗口粒度是 500ms瞬时突刺很容易在 500ms 内把额度用光造成「误杀」——这也是我们后来把热点接口的sampleCount调到 10 的原因窗口越细抖动越小但 CPU 统计开销略增。规则热更新的第二个坑限流没生效的 30 秒除了推送丢失还有个更隐蔽的坑应用启动时 Sentinel 规则还没加载前 30 秒等于裸奔。PostConstruct public void initRules() { ListFlowRule rules new ArrayList(); FlowRule rule new FlowRule(); rule.setResource(createOrder); // 资源名必须和 SentinelResource 一致 rule.setCount(200); // QPS 阈值 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setLimitApp(default); // 对所有来源生效不区分调用方 rules.add(rule); FlowRuleManager.loadRules(rules); // 阻塞加载但依赖 Nacos 就绪 }逐行解释- 第 4 行setResource的资源名必须和代码里SentinelResource(createOrder)完全一致大小写错一个字母规则就套不上我们踩过这种低级坑。- 第 6 行FLOW_GRADE_QPS表示按 QPS 限流另一种FLOW_GRADE_THREAD是按并发线程数。- 第 7 行setLimitApp(default)是对所有来源统一限流如果写成某个具体的appName只有来自那个微服务的调用才受限其他来源直接放行——曾经有人误设成自己服务名结果外部流量全没限。- 第 9 行loadRules如果在PostConstruct里同步读 Nacos而此时 Nacos 还没连上规则就是空的接口直接无限流。我们现在的做法是不依赖启动时加载规则来源统一走 Nacos 的DataSource监听new NacosDataSource(...)并加了启动探针规则为空时让接口直接返回 503 而不是放行。熔断降级比限流更难用对限流是「防别人打挂我」熔断是「别人挂了别拖死我」。Sentinel 的熔断基于慢调用比例或异常比例窗口期后进入半开状态试探。DegradeRule rule new DegradeRule(callInventory) .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType()) // 慢调用比例 .setCount(0.5) // 慢调用超过 50% 触发 .setTimeWindow(10) // 熔断 10 秒 .setMinRequestAmount(20) // 至少 20 个请求才统计避免冷启动误判 .setStatIntervalMs(10000) // 统计窗口 10 秒 .setSlowRatioThreshold(0.5); DegradeRuleManager.loadRules(List.of(rule));逐行解释- 第 2 行SLOW_REQUEST_RATIO按慢调用比例熔断另一选项是ERROR_RATIO异常比例和ERROR_COUNT异常数。- 第 4 行setMinRequestAmount(20)是我强烈建议保留的——统计窗口内的请求数少于 20 时不熔断否则冷启动前几次慢请求就能把接口熔断我们吃过这个亏。- 第 6 行setStatIntervalMs(10000)统计窗口要和setTimeWindow区分开前者是「判断依据的时间跨度」后者是「熔断后多久恢复」。限流 vs 熔断怎么配合维度流控FlowRule熔断DegradeRule保护目标保护自身不被打垮防止被下游拖垮触发依据QPS / 线程数慢调用比例 / 异常比例典型场景大促峰值削峰下游依赖超时/报错恢复方式实时滑动统计熔断窗口后半开试探复盘那次事故的数字3 台节点规则未生效持续约 22 分钟订单库连接池被打满 3 次下单失败率峰值 11%影响约 2400 笔订单SRE 事后复盘说「限流配置」比「限流代码」更值得盯。我的取舍我不太建议在 Dashboard 上手动改阈值就直接上线尤其是生产环境。更稳的做法是规则全部走 Nacos 配置中心 灰度发布先推 1 台验证再全量并在发布单里加一条「确认所有节点规则已生效」的检查项。另外一个反直觉的点sampleCount不要舍不得调大。默认 2 个窗口在 QPS 上千时抖动明显调到 10 之后限流曲线平滑很多CPU 开销那点增长可以忽略。限流不是为了「卡掉刚好第 1001 个请求」的精确感而是为了「保护系统不雪崩」的可靠性。流控效果快速失败、预热、排队别只懂第一种FlowRule还有一个常被忽略的字段controlBehavior流控效果它决定了「超出阈值时怎么处理」不同效果适合不同场景Sentinel 1.8.6rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP); // 预热冷启动 rule.setWarmUpPeriodSec(10); // 10 秒内阈值从 1/3 爬到设定值 // CONTROL_BEHAVIOR_RATE_LIMITER 则是匀速排队对应漏桶思路逐行解释- 第 1 行CONTROL_BEHAVIOR_WARM_UP适合「系统刚启动、缓存还没热」的场景阈值不是瞬间拉满而是在WarmUpPeriodSec内从约 1/3 缓慢升到设定值避免冷启动瞬间被打挂。- 第 3 行注释的CONTROL_BEHAVIOR_RATE_LIMITER是匀速排队超过阈值的请求不是直接拒绝而是按固定速率排队通过适合「突发流量但要平滑」的出口调用。我们下单接口用的是默认CONTROL_BEHAVIOR_DEFAULT快速失败超了直接抛FlowException返回 429。但有个内部批量导单的任务用的是排队模式——它不在乎晚几百毫秒但不能被直接拒。同一套 Sentinel两种效果按接口区分这是很多人没用起来的能力。思考题你的限流规则是代码里写死、还是配置中心动态推如果配置中心推送失败你的服务是会「规则失效裸奔」还是「拿不到规则就拒绝服务」这两种失败模式你选哪个本文为 Round 5 重写稿与 R3 可观测性/Sentinel 相关旧文使用不同事故场景未复用旧文。
返回列表