Sentinel 限流规则在网关层生效,但压测发现误杀 5% 正常流量,如何调优

解读

  1. 场景定位:网关层(Spring Cloud Gateway / Zuul / 自研)统一接入,Sentinel 以 Filter 或插件形式做全局限流。
  2. 现象:压测并发线程或 QPS 达到阈值后,5% 的请求被 Sentinel 直接拒绝(返回 BlockException),但业务日志显示这些请求并非“恶意”,属于正常用户。
  3. 目标:把误杀率降到 0.5% 以内,同时保持限流效果,且不能显著抬高网关本身 RT 与 CPU。
  4. 约束:
    • 生产已上线,规则变更必须灰度、可回滚;
    • 网关集群 8C16G×20 实例,峰值入口 60k QPS;
    • 后端服务 SLA 要求 P99<500ms,限流不能成为新瓶颈。

知识点

  1. Sentinel 核心统计模型

    • 滑动窗口:1s 拆成 2 个 500ms 桶,桶内计数原子累加;
    • 样本窗口:对并发线程数使用 LeapArray,对 QPS 使用 MetricBucket;
    • 阈值类型:QPS、Thread、Bw(带宽)、Hot Param;
    • 流控效果:快速失败、匀速排队、预热、冷启动。
  2. 网关层特殊点

    • 单实例多路由:一个网关进程同时代理 50+ 微服务,不同路由 RT 差异大(10ms~800ms);
    • 连接池复用:Netty worker 线程数固定,Sentinel 插桩位置若在 IO 线程,会放大竞争;
    • 多协议:HTTP、WebSocket、HTTP/2 共用端口,统计维度需区分。
  3. 误杀根因分类
    A. 统计精度:窗口划分太粗,突发流量被“一刀切”;
    B. 阈值设定:按峰值 QPS 直接给单实例均分,未考虑网关本地热点;
    C. 参数热点:同一接口不同 UID_0 占比 30%,默认热点参数限流只支持 5 个热点值,其余全部限流;
    D. 路由混用:慢接口占满线程池,导致快接口拿不到线程,Sentinel 以线程数限流,误杀快接口;
    E. 网络重传:客户端超时重发,Sentinel 把重试流量当新请求,瞬间 QPS 翻倍。

  4. 调优手段

    • 窗口细化:修改 csp.sentinel.statistic.max.rt=5000sample.count=600,把 1s 切成 20 个 50ms 桶,降低毛刺;
    • 参数级联:网关统一路由前缀 /serviceId/**,在 Sentinel 控制台拆分为 N 个 API Group,按服务+接口+请求方式三维限流,避免“大锅烩”;
    • 预热阈值:使用 RuleConstant.CONTROL_BEHAVIOR_WARM_UP,冷启动因子 3,把突发 60k QPS 平滑到 30s 内爬升;
    • 匀速排队:对写操作接口开启 maxQueueingTimeMs=20,把瞬间 2000 QPS 削成 1000 QPS,队列超时直接拒绝,降低毛刺误判;
    • 热点参数扩容:调整 paramIdx=0, topK=50, durationInSec=1,将 UID 维度从 5 扩到 50,并开启 clusterMode=false,避免跨节点 RPC 损耗;
    • 线程池隔离:网关层新增 sentinel-block-thread-pool,拒绝策略直接返回 429,不占用 Netty worker;
    • 双层校验:网关限流后,在下游服务再布一层 Sentinel,阈值按网关侧 80% 设定,形成“漏斗”,防止网关误杀后无兜底;
    • 压测模型修正:JMeter 关闭“Follow Redirects”与“Retry”,固定连接池复用,避免本地端口耗尽导致 SYN 重传;
    • 灰度规则:利用 Sentinel 1.8 的“动态规则 DB+Apollo”方案,先对 5% 实例下调阈值 10%,对比误杀率与 RT,无异常再全量。
  5. 观测指标

    • 误杀率 = BlockException 数 / 总请求数;
    • 限流 RT 增长 = 限流开启后网关 P99 – 基线 P99,目标 <20ms;
    • CPU 增长,目标 <5%;
    • 成功率恢复时间:从压测峰值回落到正常后,误杀率 1min 内回到 0.2%。

答案

  1. 复现与定位
    a) 在压测平台固定 60k QPS、50 并发线程,持续 10min,收集 sentinel-block.logaccess.log,发现 5% 返回 429,且 event=BlockException, rule=QPS, limitApp=default
    b) 把 spring.cloud.sentinel.log.switch=true,打开 sentinel-record.log,确认被限请求大部分 URI=/order/create,且参数 UID 分布 Top5 之外;
    c) 用 jstat -gc 观察网关 GC,无频繁 FullGC,排除内存抖动导致统计延迟;
    d) 结论:热点参数默认只保留 5 个,Top5 之外 UID 全部限流,造成误杀。

  2. 参数热点规则调优
    在 Sentinel 控制台新增热点规则:资源名=orderCreate,参数索引=0(UID),阈值=100,burst=50,duration=1s,topK=50,开启缓存统计。发布后误杀率从 5% 降到 1.2%。

  3. 滑动窗口细化
    网关启动参数追加 -Dcsp.sentinel.statistic.sample.count=600 -Dcsp.sentinel.metric.file.single.size=104857600,把 1s 切成 20 桶,桶内计数误差从 ±6% 降到 ±1%,误杀率再降 0.6%。

  4. 预热 + 匀速排队
    对 orderCreate 资源改流控效果为 WarmUp+Rate Limiter,warmUpPeriodSec=30,stableInterval=1ms,maxQueueingTimeMs=20。压测 60k QPS 时,网关 P99 仅增加 15ms,误杀率最终 0.3%,满足 <0.5% 目标。

  5. 灰度与回滚
    通过 Apollo 灰度 10% 实例,对比误杀率、CPU、RT 1h 无异常后全量推送;保留旧规则快照,10min 内可一键回滚。

拓展思考

  1. 集群流控 vs 单节点
    如果网关横向 20 实例,单节点限流阈值 = 总阈值 / 20,一旦流量倾斜,单机热点仍可能误杀。可引入 Sentinel Token Server 做集群流控,但带来 2~3ms 额外 RT,需评估网关是否接受。

  2. 业务层面令牌桶
    对订单创建这类写接口,可在业务服务内用 Redis+Lua 实现分布式令牌桶,网关层只做初步防护,把精度压力下沉到 Redis,网关只负责兜底。

  3. 自适应限流
    结合系统负载(CPU、Load1、Avg RT)做自适应规则,Sentinel 1.8 提供 SystemRule,当系统 CPU>80% 且 RT>3 倍基线时再触发限流,减少“误伤”正常流量。

  4. 压测流量标记
    在压测平台统一加 Header X-Pressure-Test=1,网关层通过 GatewayFlowRule.setLimitApp("pressure") 把压测流量路由到单独阈值,避免与真实流量混用同一套规则,彻底解决“压测误杀”问题。