十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

灰度接口前 5 个请求熔断了 3 个,风控全拒:Sentinel 慢调用比例和最小请求数的 4 个坑

灰度接口前 5 个请求熔断了 3 个,风控全拒:Sentinel 慢调用比例和最小请求数的 4 个坑 title: 灰度接口前 5 个请求熔断了 3 个风控全拒Sentinel 慢调用比例和最小请求数的 4 个坑topic: 微服务熔断降级 Sentinel 滑动窗口round: 4batch: 5我们接入 Sentinel 是为了保护一个第三方风控服务——它偶尔抖一下RT 能从 20ms 飙到 2s。结果上线第一天新灰度接口的前 5 个请求里有 3 个被熔断风控直接整体拒单比它原本抖动还狠。看着监控里那条熔断数曲线我意识到熔断本来是兜底的手结果变成了砸场子的锤。事故现场小流量下的比例陷阱新接口走灰度QPS 只有个位数。Sentinel 的熔断规则我们照着官方样例配// 慢调用比例熔断规则Sentinel 1.8.6 private void initRule() { ListDegradeRule rules new ArrayList(); DegradeRule rule new DegradeRule(); rule.setResource(risk-check); // 第 4 行被保护资源名 rule.setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType()); // 第 5 行慢调用比例 rule.setCount(0.5); // 第 6 行慢调用比例阈值 50% rule.setTimeWindow(10); // 第 7 行熔断 10 秒 rule.setMinRequestAmount(5); // 第 8 行最小请求数 5 rule.setStatIntervalMs(10000); // 第 9 行统计窗口 10 秒 rule.setSlowRtAbnormalRate(0.5); rules.add(rule); DegradeRuleManager.loadRules(rules); }逐行看问题出在哪第 6 行比例阈值 50%第 8 行最小请求数 5。意思是统计窗口内只要够 5 个请求其中一半以上慢就熔断。灰度期前 5 个请求因为连接池刚冷启动、TLS 握手慢有 3 个 RT 超过我们设的慢调用标准1000ms比例 60% 50%直接熔断 10 秒。讽刺的是这 3 个慢调用里2 个是冷启动首包延迟不是风控服务真的挂了。Sentinel 却把它们当成故障信号把整个资源熔断了。滑动窗口到底在数什么要搞清为什么5 个就熔断得看 Sentinel 的滑动窗口统计。它把时间切成sampleCount个桶每个桶记录请求数和慢调用数// 简化版的窗口统计逻辑来自 sentinel-core 的 ArrayMetric public boolean canPassSlowRatio(int passCount, int slowCount, double ratio) { // passCount 是本窗口总请求slowCount 是其中慢的 if (passCount minRequestAmount) { return true; // 没到最小请求数不熔断 } double slowRatio (double) slowCount / passCount; return slowRatio ratio; // 超过比例才熔断 }注意第 4 行的minRequestAmount判断它只卡数量够不够不卡流量稳不稳。在个位数 QPS 下5 个请求可能就来自同一秒的突发统计窗口根本没平滑掉抖动。这是小流量服务上熔断最容易被忽略的坑。第二个坑资源名共用导致误伤邻居我们当时犯的另一个错是把网关层和下游服务用了同一个resourceName(risk-check)// 网关层埋点 SentinelResource(risk-check) public RiskResp gatewayCheck(...) { ... } // 下游服务也埋了同名 SentinelResource(risk-check) public RiskResp serviceCheck(...) { ... }两边共用一个熔断状态结果下游一次正常抖动把网关侧的调用也一起熔断了。调用方和被调用方应该用不同的资源名各自统计、各自熔断否则一个环节的波动会传染给毫不相干的入口。第三个坑熔断后的 fallback 没接直接抛异常最早我们只配了规则没配降级方法// 错误示范熔断后直接抛 DegradeException上游收到 500 SentinelResource(value risk-check, blockHandler noop) public RiskResp check(RiskReq req) { return remoteRiskClient.check(req); } // 正确做法熔断时走兜底而不是全拒 SentinelResource(value risk-check, fallback riskFallback) // 熔断/限流都走这里 public RiskResp check(RiskReq req) { return remoteRiskClient.check(req); } public RiskResp riskFallback(RiskReq req, Throwable t) { // 兜底放行并打标让人工事后复核而不是一刀切拒单 RiskResp resp new RiskResp(); resp.setPass(true); resp.setFallback(true); return resp; }熔断的本质是承认自己现在不可靠转交兜底逻辑而不是直接失败。我们最初没接 fallback等于把熔断变成了拒绝服务。第四个坑timeWindow 太短反复熔断第 7 行setTimeWindow(10)设了 10 秒恢复。问题是恢复后的前 5 个请求如果再次冷启动变慢又会触发熔断形成熔断→恢复→再熔断的死循环接口在 10 分钟内几乎不可用。我们把timeWindow调到 30 秒并在资源初始化时预热连接池让冷启动的慢请求不再出现在统计窗口里。熔断参数对比参数我们的初值问题调整后慢调用比例 count0.5小流量下极易触发0.8容忍更多抖动最小请求数5个位数流量就达阈值20统计窗口10s窗口太短没平滑30s熔断时长10s恢复后反复熔断30s 连接预热fallback无熔断即全拒接兜底放行滑动窗口的桶数决定了统计的灵敏度上面所有规则都跑在一个滑动窗口上而窗口被切成多少个桶sampleCount直接影响统计抖动。桶太少统计是跳变的桶太多内存和精度开销上升。// 把一个统计窗口切成 10 个桶每桶 1 秒避免 10 秒一跳的统计抖动 rule.setStatIntervalMs(10000); // 统计窗口 10 秒 rule.setStatIntervalMs(10000); // 通过底层 Metric 配置 sampleCount10、windowLengthMs1000 // 桶越多熔断触发越平滑我们最终取 10兼顾灵敏与稳定早先我们没关注这个参数默认桶数很少导致熔断触发像抽风——有时候刚超比例立刻熔断有时候比例超了一倍还没反应。把桶数调合理之后统计曲线才平滑下来误判明显减少。第五个坑热点参数限流把 VIP 用户也限了除了熔断我们还踩过 Sentinel 热点参数限流的坑。本来想保护单个用户高频调用的场景配置了按userId限流// 热点参数限流对 userId 这个参数单独限流 ParamFlowRule rule2 new ParamFlowRule(); rule2.setResource(query-order); rule2.setParamIdx(0); // 第 0 个参数 rule2.setCount(100); // 该参数值每秒最多 100 次结果某天运营给 VIP 用户做了压测这个 VIP 的userId一秒被调了 200 次直接被限流工单打到我们这。热点参数限流的语义是针对某个具体参数值不是针对所有用户各 100 次——它限的是单个 userId 的频度。用错场景反而把正常的高价值用户拦了。这类限流只适合防单个用户刷接口不适合做全局 QPS 保护。第六个坑规则是覆盖式加载多地各 load 一次会互相覆盖Sentinel 的DegradeRuleManager.loadRules()是全量覆盖不是追加。我们曾经在初始化类和动态配置监听两处都调了 load结果配置中心推下来的规则被初始化类的旧规则又覆盖回去了熔断迟迟不生效。// 错误两处都 loadRules后调用的覆盖先调用的 DegradeRuleManager.loadRules(initRules); // 初始化加载 configListener.onChange(r - DegradeRuleManager.loadRules(r)); // 覆盖 // 正确只在配置变更时 load 全量包含初始规则单一入口 configListener.onChange(r - DegradeRuleManager.loadRules(mergeInitAndRemote(r)));逐行看第 3 行在初始化时 load 一次第 4 行配置变更时又 load 一次两次都是全量覆盖谁后调谁赢。正确做法是把所有来源的规则合并后只在一个入口 load避免互相覆盖导致改了不生效。复盘数据调参后我们回看了灰度日志原配置下新接口上线前 2 分钟熔断触发 17 次拒单率 3.2%调整后同口径下 0 次误熔断。等真实流量涨到 200 QPS 后慢调用比例统计才真正发挥价值——一次风控服务真实故障RT 持续 1.5s被准确识别并熔断保护住了主链路。把桶数和热点限流这两个坑也补上后我们对 Sentinel 的误伤工单从上线首周 11 单降到 0。结论很明确Sentinel 的熔断在流量充分、波动平滑时才准小流量灰度期它的比例阈值和桶数配置反而会制造伪故障。我的取舍判断我不建议对所有接口无脑开熔断。我的做法是分两类核心依赖、流量充足的下游客服如支付、库存必须配熔断 fallback比例阈值可以激进些0.5因为真实故障值得快速隔离。灰度期、小流量、强依赖的旁路服务如风控、画像先只做限流不做熔断或者把最小请求数调大、比例调宽松等流量稳定再收紧。另外熔断一定要有 fallback且 fallback 的语义要想清楚——风控这种宁可放过不可错杀的场景兜底应放行打标而扣款这种宁可错杀不可放过的场景兜底应拒绝。把 fallback 写成统一返回 null是上线前最容易埋的雷。思考题如果熔断的 fallback 选择放行打标那在风控服务真的被击穿、返回全放行时攻击者是不是就能借机绕过风控熔断兜底和风控兜底的优先级到底该谁说了算本文为第 4 轮重写与历史同名文章场景、标题均不重复。
返回列表