Sentinel 系统规则实战:让 JVM 的 CPU 使用率直接参与限流决策
先扯个实际场景。你有没有遇到过这种诡异情况:线上服务 QPS 明明还在可接受范围,接口 RT 却开始直线飙高,紧接着整个应用像被什么东西掐住脖子一样,卡顿、超时、批量报错,最后连着数据库一起拖垮。我早期做高并发服务时就栽过一回,流量洪峰到来前业务团队粗估了一个 QPS 阈值,辛辛苦苦配好了单机限流,结果流量真的冲上来时,单机 QPS 还没到预设值,机器 CPU 先被打满了,限流规则根本没机会触发,服务照样雪崩。
这事逼着我重新想一个问题:限流到底应该限什么?是数字上的 QPS,还是背后真正决定系统吞吐能力的资源?后来我把目光放到了阿里开源的 Sentinel 上。Sentinel 的系统规则不按调用次数卡你,而是直接盯 JVM 所在节点的系统负载、CPU 使用率这类硬指标,把限流决策和机器真实状态绑在一起。今天这篇就围绕“Sentinel 系统规则 + JVM 指标联动,靠 CPU 使用率触发限流”展开,把原理、配置、实战参数、踩坑记录全盘托出。适合正在用 Sentinel 做服务保护、但对系统规则模块还不太熟的读者,也适合那些和我一样被“误伤型限流”坑过的同学。
1. 系统规则的核心思路:为什么不用固定 QPS,反而盯 CPU
1.1 单机 QPS 限流的局限在哪里
先拆一下固定 QPS 限流的逻辑。它的核心假设是:单机能扛 2000 QPS,超过就拦。这个假设有两个脆弱点。
第一,QPS 阈值是拍脑袋定的。上线前压测得出 2000 QPS,上线后业务代码加了一段耗时逻辑,或者 JVM 堆内存调小了,实际扛 1500 QPS 就喘不过气。你原来的 2000 阈值不但没保护服务,反而成了帮凶,因为请求放进来之后,线程在疯狂堆积,RT 指数级恶化,这就是经典的请求堆积 → 线程饥饿 → 服务假死链路。
第二,固定 QPS 限流只能看到调用量,看不到系统真实健康度。举个极端例子:一个接口 CPU 密集计算 50ms,另一个接口只查了一次缓存耗时 2ms。两个接口配同样的 QPS 阈值显然不合理,但如果分别配,又很难预先算准每种业务的资源开销。
所以固定 QPS 本质上是“以预测代替感知”,而分布式系统的突发负载、Full GC、宿主机抢占,都不是你提前能预测准的。这也是我后来坚定转向系统规则的原因——它不做预测,只做响应。
1.2 系统规则的运行逻辑:自适应保护
Sentinel 的系统规则,官方叫 SystemRule,核心思想是让入口流量自适应系统承载能力。它的保护对象是整台 JVM 所在的机器节点,不是某个接口。
具体实现上,Sentinel 会周期性采样当前系统的几个核心指标:
| 指标 | 说明 | 触发保护的动作 |
|---|---|---|
| 系统负载(Load) | 基于操作系统 1 分钟负载平均值,做 BBR 风格换算 | 根据系统负载和当前 RT 计算最大允许 QPS,超出即拦截 |
| CPU 使用率 | 本机所有核心的平均使用率(不是单个进程的) | 超过阈值后,拒绝所有新请求进入 |
| RT | 所有入口流量的平均响应时间 | 超过阈值后,拒绝新请求 |
| 线程数 | 入口流量的并发线程数 | 超过阈值后,拒绝新请求 |
| 入口 QPS | 所有入口流量的聚合 QPS | 一般配合负载策略使用 |
注意上表里两个容易混淆的点。一是 CPU 使用率指标和系统负载指标是两套独立逻辑:CPU 使用率一旦超阈值,是直接把后续请求全部拒绝,根本没有协商余地;系统负载则是动态调整入口 QPS,负载越高,允许进来的请求越少,控制更柔和。二是 CPU 使用率采样的是整个操作系统层面,不是 JVM 进程本身。这点后面还会展开讲,它直接影响你的规则能不能触发。
1.3 系统规则的代码结构
看一眼核心类,你会更清楚它的运作方式。Sentinel 里系统规则入口是SystemRuleManager,它内部有个定时任务,默认每秒执行一次采样。
// Sentinel 核心:系统状态采样与规则检查入口 public class SystemRuleManager { // 负载阈值,-1 表示不限制 private static double highestSystemLoad = Double.MAX_VALUE; // CPU 使用率阈值,-1 表示不限制 private static double highestCpuUsage = Double.MAX_VALUE; // 定时采样任务,线程池固定频率执行 static { ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); scheduler.scheduleAtFixedRate(() -> { try { currentSystemLoad = SystemStatusListener.getSystemLoad(); currentCpuUsage = SystemStatusListener.getCpuUsage(); // 计算当前允许的最大入口 QPS calculateAllowedQps(); } catch (Throwable e) { RecordLog.warn("[SystemRuleManager] Error when computing system metrics", e); } }, 1, 1, TimeUnit.SECONDS); } }这个定时任务会刷新维护一个SystemMetric对象,里面保存着当前负载、CPU、RT、线程数。每个请求进入 Sentinel 的 slot 链路时,SystemSlot会读取这些指标,和规则阈值比较,决定放行还是拒绝。整个链路是典型的“异步采样 + 同步判断”,实时性依赖采样频率,这也是后面排查问题时一个关键切入点。
2. 核心实操:CPU 阈值与负载参数的配置全流程
2.1 系统规则有哪些参数,该怎么定初始值
我直接给你一套我在生产环境验证过的基础配置思路。
| 参数 | 配置入口 | 我的生产建议 | 备注 |
|---|---|---|---|
| CPU 使用率阈值 | SystemRule的highestCpuUsage | 80%~85% | 超过后直接拒绝所有新请求 |
| 系统负载阈值 | SystemRule的highestSystemLoad | 等于 CPU 核心数(如 4 核设 4.0) | 动态调整入口 QPS |
| 平均 RT 阈值 | SystemRule的avgRt | 根据业务压测结果,比如 300ms | 超过后拒绝新请求 |
| 线程数阈值 | SystemRule的maxThread | 核心线程数 * 2 附近 | 防线程堆积 |
为什么 CPU 使用率阈值选 80%~85%?因为 CPU 超过这个值后,系统可能还有一点缓冲余量,但 RT 已经开始出现明显毛刺。如果设到 95%,设备长时间满负荷运行,JVM 的 GC 线程和业务线程互相抢 CPU,服务已经处在崩溃边缘,保护动作来得太晚。如果设到 50%,机器经常因为一次不算严重的毛刺就整体拒绝流量,可用性受损。
系统负载阈值我建议直接等于 CPU 核心数。这里需要解释一下“系统负载”和 CPU 使用率的区别。系统负载是“当前正在运行 + 等待 CPU 的线程平均数量”,4 核机器负载 4.0,意味着每个核正好有一个任务在跑,CPU 接近饱和但还没过载。负载超过核心数,说明有线程在排队,这时候再继续放流量进来,排队只会越来越严重。
2.2 Sentinel 的负载阈值是怎么换算成 QPS 的
这里值得多说一句,因为很多人配置完负载阈值后,发现实际生效的 QPS 数字和预期完全对不上。Sentinel 没有拿负载阈值直接限流,它参考了 TCP BBR 的带宽延迟积思想,做了一个换算公式。核心逻辑是:当前系统能承受的最大 QPS,约等于“并发线程数当前值 / 平均 RT”。
我拆解一下这个过程。Sentinel 在DefaultNode里维护了入口流量的统计,包含当前并发线程数curThreadNum和平均 RT。系统负载超过阈值后,它按 BBR 风格带宽评估公式计算出一个新的最大 QPS:
private static double calculateAllowedQps() { double avgRt = SystemStatusListener.getCurrentMetric().avgRt(); double maxQps = maxThread / avgRt; // 再根据当前负载和临界负载的比例做平滑调整 return maxQps * (highestSystemLoad / currentSystemLoad); }简单理解就是:系统负载越高,可放行的 QPS 越低,但不是直接跌到 0,而是按比例平滑下降。这个设计比一刀切拒绝要合理得多,能消化掉一些突发毛刺,又不至于把系统压垮。
所以你在配置负载规则时,心里要有个预期:规则控制的不是某个固定 QPS 数值,而是一条动态曲线,它会根据 RT 和线程数实时变化。如果接口 RT 涨了一倍,系统允许进入的 QPS 自动减半,这是自适应保护最明显的价值。
2.3 实操:三种方式配置系统规则
方式一:硬编码,适合测试环境和快速验证。
// 加载系统规则,以下参数按实际机器配置调整 private void initSystemRule() { SystemRule rule = new SystemRule(); rule.setHighestCpuUsage(0.85); // CPU 使用率阈值 85% rule.setHighestSystemLoad(4.0); // 4 核机器,负载阈值 4.0 rule.setAvgRt(300); // 平均 RT 阈值 300ms rule.setMaxThread(16); // 最大并发线程数 16 SystemRuleManager.loadRules(Collections.singletonList(rule)); }方式二:通过控制台动态推送。在 Sentinel 控制台的“系统规则”菜单里新增,填好阈值后直接发布。这种方式适合生产环境动态调整,但要注意控制台版本和服务端版本要匹配,否则推送不生效。
方式三:通过FlowRule的grade参数指定为RuleConstant.SYSTEM_LOAD或RuleConstant.SYSTEM_CPU,在流控规则里单独配置 CPU 维度。这种方式相对少见,但从命名上能看出 Sentinel 把系统保护规则也纳入到了统一规则体系里。
我实践下来最推荐的方式是:代码初始化兜底 + 控制台动态调参。代码里写一套保守阈值做启动保护,控制台再根据压测数据调优到合理值。不然线上机器配置变更或者业务扩容后,硬编码的阈值就成了定时炸弹。
2.4 如何验证规则真的生效了
配置完了不代表它就会按下你想象的方式工作。我见过不少人配完规则,压测半天没触发,怀疑规则没用。其实很大概率是压测姿势不对。
验证要分三步走。第一步,确认机器的 CPU 使用率确实能通过 Sentinel 采集到。打开日志看sentinel-record.log,里面会周期性打印系统指标采样值,确认不是 0,也不是空。
第二步,制造压力。用压测工具(我常用 wrk 或 JMeter)直接打入口服务,观察控制台或日志里有没有出现一个关键日志:"[Sentinel] Blocked by system protection: SystemRule"或类似字样。只要出现这条日志,说明系统规则真正参与拦截了。
第三步,验证恢复。停掉压测流量,观察系统指标回落后,请求是否自动恢复放行。有时候规则触发后一直不恢复,多半是采样线程被 GC 或线程池饥饿影响了,这个后面排查章节会细说。
3. JVM 指标联动的进阶玩法:不只盯 CPU 使用率,还要看 GC 和内存
标题里专门提到“JVM 指标联动”,很多人会理解为直接读取 JVM 的 CPU 使用率。但实际做生产监控时,我会把联动拆成两个层面:一是 CPU 使用率触发的刚性拦截,一是 JVM 内部指标(Full GC 频次、堆内存占用、线程数)对阈值调整的柔性修正。这两个结合起来,才是完整的“系统规则 + JVM 指标联动”。
3.1 为什么 CPU 使用率高时,先要看是不是 Full GC 在捣鬼
有一次线上服务 CPU 使用率飙到 90% 以上,系统规则触发后大量请求被拦截,但业务团队说没看到明显的流量增长。最后查了半天,是堆内存里有一个本地缓存实现有误,数据无限增长导致 Full GC 频繁触发。Full GC 是多线程并行回收阶段,会吃满所有 CPU 核心,表现为“系统负载高、CPU 使用率高”,而业务线程其实没有干多少活。
这个案例说明,CPU 使用率高只是一个结果,不一定是业务流量导致的。如果直接把 CPU 阈值当成唯一限流依据,那么一次频繁的 GC 就会引发全站“无差别限流”,甚至把正常流量也挡在门外。
所以我在实践里会做一层过滤:当 CPU 使用率触发系统规则后,先通过 JMX 拉取 JVM 的 GC 频次数据,判断 CPU 是被业务线程消耗还是被 GC 线程消耗。
// 通过 JMX 获取 Full GC 次数,辅助判断 CPU 飙高的原因 public long getFullGcCount() { List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans(); long fullGcCount = 0L; for (GarbageCollectorMXBean bean : gcBeans) { // 老年代回收器名称通常包含 "Old" 或 "MarkSweep" if (bean.getName().contains("Old") || bean.getName().contains("MarkSweep")) { fullGcCount += bean.getCollectionCount(); } } return fullGcCount; }完整的决策逻辑是这样的:CPU 使用率触发限流 → 检查 Full GC 频次是否在短时间内大幅上升 → 如果是,说明系统进入“GC 抖动状态”,此时不应该简单拒绝所有请求,因为流量本身没有超载,真正的问题是内存或 GC 参数不合理。这时候最优动作是保底拦截一部分请求,给 GC 喘息时间,同时触发内存 dump 或扩容,而不是让正常用户在无感知情况下全部被拦截。
3.2 联动方案一:根据 JVM 指标动态调整系统规则阈值
系统规则的阈值不应该是静态写死的。我基于 Spring Boot 的@Scheduled任务做过一个动态修正器,每 10 秒采集一次 JVM GC 和内存指标,动态调整 Sentinel 的 CPU 阈值。核心思路是:正常情况下 CPU 阈值保持 85%,如果监测到 Full GC 频繁,自动把 CPU 阈值下调到 70%,让系统更早进入保护状态,减少因 GC 造成的请求堆积。
@Component public class SentinelDynamicRuleAdjuster { private static final double DEFAULT_CPU_THRESHOLD = 0.85; private static final double GC_PRESSURE_CPU_THRESHOLD = 0.70; @Scheduled(fixedDelay = 10000) public void adjustSystemRule() { long currentFullGcCount = getFullGcCount(); long delta = currentFullGcCount - lastFullGcCount; // 10 秒内超过 2 次 Full GC,认为 GC 压力大 if (delta > 2) { updateCpuThreshold(GC_PRESSURE_CPU_THRESHOLD); } else { updateCpuThreshold(DEFAULT_CPU_THRESHOLD); } lastFullGcCount = currentFullGcCount; } }这么做的好处是把“限流保护”和“系统自治”绑在了一起。流量高峰时 CPU 超过 85%,限流先兜住;JVM 内存异常时 GC 频繁,阈值自动前移到 70%,兜得更早。两条防线互相配合,而不是只靠一个静态阈值硬扛。
3.3 联动方案二:把 JVM 线程数指标纳入限流判断
很多人只盯着 CPU 和负载,忽略了线程数。Sentinel 的线程数阈值其实是针对“入口并发线程数”的,而不是 JVM 全部线程。但生产环境里,一个比入口线程数更危险的信号是 JVM 整体的活线程数逼近线程池上限。
当 Tomcat 或业务线程池的线程全部处于RUNNABLE状态,并且大量阻塞在远程调用或锁等待上时,JVM 线程数这个指标比 CPU 使用率更早反映问题。我在压测中遇到过:CPU 使用率才 40%,但线程池已经被占满,RT 狂飙,此时系统规则根本没触发,因为 CPU 还没到阈值。
对这种场景,我把 JVM 线程数也纳入联动判断:定期检查ThreadMXBean.getThreadCount(),如果超过预设水位(比如核心线程数 150,水位设在 120),就自动加载一条临时系统规则,把入口 QPS 压低,或者直接把线程数阈值调低。
long threadCount = ManagementFactory.getThreadMXBean().getThreadCount(); if (threadCount > threadPoolHighWatermark) { SystemRule systemRule = new SystemRule(); systemRule.setMaxThread(currentAllowedThreadCount); systemRule.setHighestCpuUsage(0.8); // 让规则在 30 秒内自动过期,避免长时间误伤 SystemRuleManager.loadRules(Collections.singletonList(systemRule)); }3.4 JVM 参数调优对系统规则效果的影响
聊到这里必须提一嘴 JVM 参数,因为很多人发现系统规则“该触发时没触发”,问题不在 Sentinel,而是 JVM 本身的机制掩盖了真实压力。
以 Tomcat 启动设置 JVM 参数为例,如果你的堆内存和 GC 策略不合理,那么系统在 CPU 高负载之前,很早就会进入频繁 GC 的状态,CPU 时间大半被 GC 吃掉,但 JVM 对外表现的 RT 可能还没有明显恶化。这时候系统规则以为系统“还行”,等 RT 真正恶化时,其实已经晚了。
我常用的生产 JVM 参数组合是这样:
-Xms4g -Xmx4g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs/jvm -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/data/logs/gc.log核心原则是:堆内存初始值和最大值保持一致,避免运行时动态扩容造成 CPU 尖峰;用 G1 回收器并设置 GC 停顿目标,让 GC 造成的 CPU 消耗可控。这几点直接影响系统规则采样的“体感”:GC 平稳了,CPU 使用率才有资格作为限流信号。否则你限的是“GC 引发的 CPU 毛刺”,而不是真正的业务负载,整个联动逻辑就失真了。
4. 常见问题排查与避坑实录
这部分全是真金白银的踩坑记录。我按出现频率排个序,把典型问题和排查思路整理成一张速查表,再展开讲几个最坑的。
| 问题现象 | 可能原因 | 排查步骤 |
|---|---|---|
| 系统规则配置了但不生效 | 控制台版本不匹配;未引入sentinel-parameter-flow-control或系统规则模块 | 检查依赖;查看控制台是否展示规则;用代码加载规则做对照 |
| 一台机器限流另一台不限流 | 单机采样差异;宿主机 CPU 争抢;负载不均衡 | 对比各节点日志中的系统指标;检查是否在同一宿主机 |
| CPU 阈值很明显超了但还是放行 | 采样频率是 1s;秒级数据存在窗口;判断用的是上一秒指标 | 降低触发窗口;连续多秒超标才拦截;增强采样日志 |
| 触发后不恢复 | 采样线程卡死;GC 导致采样线程调度延迟 | 检查 GC 日志;优化 JVM 参数;确认采样线程优先级 |
| 控制台推送规则成功但服务端不生效 | 服务端未接入数据库/配置中心;推送给应用但应用未注册监听 | 检查应用侧日志;确认是否包含SentinelAutoConfiguration |
| 负载阈值换算出来的 QPS 不直观 | 对 BBR 换算公式不理解 | 看calculateAllowedQps逻辑;在日志看动态 QPS 值 |
4.1 系统规则“不生效”的经典坑:版本与控制台不匹配
这个问题排查成本最高。有些团队的 Sentinel 控制台是早期版本,应用内嵌的sentinel-core却升級到了较新版本,两个版本之间规则推送协议不一致,控制台上配置了系统规则,应用端完全没有感知。
我的排查习惯是三步走。第一,直接跑一个本地 Java 进程,用官方控制台同版本对接,把同样的规则推一遍,看本地是否触发。第二,检查服务的sentinel-record.log,里面有没有Receive new config from command center之类的记录。第三,用最原始的办法验证,代码里手动写死一条 CPU 阈值规则来测试,排除一切外部依赖,先确认基础链路通不通。
4.2 CPU 使用率采集偏差:别忽略宿主机的影响
Sentinel 读取 CPU 使用率是通过OperatingSystemMXBean.getSystemLoadAverage()和自定义的 CPU 采样器来实现的。注意,getSystemLoadAverage()返回的是系统 1 分钟平均负载,而 CPU 使用率是通过com.sun.management.OperatingSystemMXBean.getSystemCpuLoad()获取。
在容器化部署(比如 Docker 或 K8s)场景下,这个采集有一个天坑:容器默认读取的是宿主机整体 CPU 状态。假如宿主机 32 核,你的 Pod 被限制只用 2 核,Pod 计算密集型业务能把 2 核打满,但getSystemCpuLoad()看到的是 32 核里只有 2 核忙,整体算下来 CPU 使用率可能只有 6%,系统规则永远触发不了。
这是我在容器化落地中遇到的最头疼的问题。目前可用的解决方案是引入容器 CPU 感知的采集器,或者干脆绕过系统整体 CPU,直接用 cgroup 文件计算容器 CPU 使用率。虽然 Sentinel 自身没有提供完美的容器适配,但你可以通过定时任务读取/sys/fs/cgroup/cpu/cpuacct.usage或cpu.stat,计算容器级别的 CPU 使用率,再手动调用SystemRuleManager的规则触发逻辑。
// 通过 cgroup 计算容器 CPU 使用率,替代系统级 CPU 使用率 private double getContainerCpuUsage() throws IOException { Path cpuStatPath = Paths.get("/sys/fs/cgroup/cpu/cpu.stat"); if (!Files.exists(cpuStatPath)) { return -1; } // nr_periods 为周期数,nr_throttled 为被限制周期数 List<String> lines = Files.readAllLines(cpuStatPath); long nrPeriods = 0, nrThrottled = 0; for (String line : lines) { if (line.startsWith("nr_periods")) { nrPeriods = Long.parseLong(line.split("\\s+")[1]); } else if (line.startsWith("nr_throttled")) { nrThrottled = Long.parseLong(line.split("\\s+")[1]); } } if (nrPeriods == 0) { return -1; } return (double) nrThrottled / nrPeriods; }这段代码的思路是:容器被 CPU 限流的时间占比,本质上就是容器能够使用的 CPU 配额被打满的占比。如果这个值超过 50%,说明容器长期处于 CPU 饥饿状态,比宿主机级别指标准确得多。
4.3 阈值触发后一直不恢复
这个坑也很有代表性。系统规则在 CPU 使用率下降后,理论上应该自动恢复放行。但实际中,如果触发限流时有很多请求在被拒绝前已经进入了业务线程池,它们会继续在后台处理一段时间,导致系统负载和 CPU 在限流生效后依然高居不下。然后规则就不停触发,形成死锁感。
解决方案是给系统规则加一个“冷却期”概念,也就是连续 N 次采样都低于阈值才恢复放行,而不是一次采样低于阈值就立刻放行。这个逻辑 Sentinel 原生没有,需要自己包一层。
private int consecutiveHealthyCount = 0; private static final int HEALTHY_THRESHOLD = 5; public boolean shouldBlock(boolean isCpuOverThreshold) { if (isCpuOverThreshold) { consecutiveHealthyCount = 0; return true; } // 连续 5 次采样健康才恢复 if (consecutiveHealthyCount >= HEALTHY_THRESHOLD) { return false; } consecutiveHealthyCount++; return true; }从效果看,这个冷却期避免了规则在临界阈值附近快速抖动,让系统有足够时间消化存量请求,恢复过程更平滑。代价是误伤时间变长,但比起在临界点反复抖动造成的不稳定体验,这点代价完全值得。
4.4 阈值设多少才合理:给一套可复用的“试探法”
我推荐用灰度试探法。具体操作:先配一个高阈值,比如 CPU 使用率 95%,系统负载 2 倍核数,同时观察线上 RT 和错误率。每隔 15 分钟下调一次阈值,每次下调 5% 或 0.5 倍核数,直到你发现 RT 毛刺明显减少、而业务 QPS 没有大量被拒绝,那个点就是当前版本的“甜点区间”。
这套方法的原理是:在阈值高位时,系统规则的介入少,能观察到底层数据;逐步下调,找到“拦截最陡曲线”的拐点。这比直接拍脑袋设 80% 要科学,因为不同服务对 CPU 的敏感度完全不同——纯 IO 型服务 CPU 到 90% 可能都没影响,计算密集的服务 70% 就得限制。
5. 生产落地的扩展:动态数据源与多节点一致性
5.1 把规则迁移到配置中心,别让控制台背锅
使用控制台推送规则有个隐患:控制台本身是独立进程,一旦它挂了或者规则推送通道断了,服务端的规则还停留在最后一次推送的状态。生产环境最好把规则持久化到配置中心,比如 Nacos 或 Apollo,Sentinel 通过DataSource接口监听配置变化。
// 使用 Nacos 作为 Sentinel 规则数据源的伪代码 ReadableDataSource<String, List<SystemRule>> systemRuleDataSource = new NacosDataSource<>(nacosAddr, groupId, dataId, source -> JSON.parseObject(source, new TypeReference<List<SystemRule>>() {})); SystemRuleManager.register2Property(systemRuleDataSource.getProperty());这样做还有一个额外的好处:多实例的规则版本一致。控制台手动推规则是推给单个 IP,配置中心推给的是所有订阅节点,不会出现一台机器限流一台不限流的“半抬起半落下”状态。热词里有人搜spring cloud sentinel datasource redis集群,其实也是这个思路——规则源的存储无所谓,Redis、数据库还是 Nacos,只要能保证一致性和实时性就行。
5.2 多节点限流的公平性问题
集群部署下还有一个注意点:每台机器独立运行系统规则,而系统负载是单机指标。如果负载均衡算法不完美,可能出现一台机器 CPU 爆掉触发限流,另一台还很空闲。这不算 Sentinel 的 bug,而是“自适应保护”天然没有全局视角的体现。
如果业务对全局一致性要求高,可以把系统规则和集群流控配合使用。集群流控负责全局流量分配,系统规则负责单机兜底。一个简单的组合:集群流控把总流量控制在整个集群容量的 70%,剩下 30% 留给单机系统规则动态消化。这样 CPU 使用率监控在单机层面继续生效,但全局流量不会因为一台机器抖动就大面积受限。
最后分享一个我坚持在生产环境使用的验证脚本
每次调完系统规则参数,我会用一段脚本做烟雾测试,确认限流不是“纸面配置”。
# 用 wrk 制造持续 30 秒的并发压力,观察系统规则触发后 QPS 是否被压低 wrk -t8 -c200 -d30s http://your-service/api/test # 同时从 sentinel-record.log 里过滤系统保护记录 grep "System protection" /data/logs/sentinel-record.log | tail -20确认输出里有连续的“Blocked”记录,同时服务依然能返回部分成功请求,而不是完全 502,说明规则有效且系统仍在兜底运行。这套“边压边验”的流程我坚持了很久,每次调参心里都有底。
系统规则 + JVM 指标联动这件事,核心不在于 Sentinel 配置本身多复杂,而是想明白“到底拿什么信号来代表系统过载”。CPU 使用率是一个好信号,但它必须结合 JVM 的 GC、线程、内存一起看,才能区分“流量过载”和“系统自身抖动”。希望这篇实战记录能帮你把限流从“拍数字”进化到“看体征”。