bigywater.tech
微服务 · 6

熔断的统计窗口与阈值:计数窗口与时间窗口,失败率与慢调用率怎么选

熔断器用计数窗口还是时间窗口?失败率阈值和慢调用率阈值怎么选?这篇用虚拟时钟模拟和本机的 HTTP 服务,把触发时间、误触发和慢调用的盲区量出来。

0.02 s 对 5.00 s
500 次/秒时发现下游全部失败所需时间:计数窗口(20 次)对 时间窗口(10 s)
328 次
6 小时里计数 20 次、失败率 ≥ 20% 的配置的误触发次数(时间窗口三种配置都是 0)
241 s 对 52 s
服务只慢不错时,变慢之后发出的调用占用线程的总时间:只看失败率 对 加慢调用率

计数窗口与时间窗口

实测 · 本机执行输出
  请求速率    | 计数窗口:发现延迟 | 已经打到坏下游的请求 | 时间窗口:发现延迟         | 已经打到坏下游的请求
  --------+-----------+------------+-------------------+-----------
  0.5 次/秒 | 18.00 s   | 10         | 不触发(窗口里不到 10 次调用) | 1500      
  1 次/秒   | 9.00 s    | 10         | 4.00 s            | 5         
  2 次/秒   | 4.50 s    | 10         | 4.50 s            | 10        
  5 次/秒   | 2.00 s    | 10         | 5.20 s            | 26        
  20 次/秒  | 0.45 s    | 10         | 4.95 s            | 100       
  100 次/秒 | 0.09 s    | 10         | 4.99 s            | 500       
  500 次/秒 | 0.02 s    | 10         | 5.00 s            | 2501      
  计数窗口的发现时间 ≈ 10 次 ÷ 速率,速率越高越快,速率低时要等很久;时间窗口只要窗口里调用数够就是看比例,发现时间在 5 s 上下(窗口一半的时间),与速率无关,但速率低到窗口里不够最少调用数时永远不会触发

计数窗口按“多少次失败”触发,速率越高越快;时间窗口按比例触发,约窗口一半的时间,低速率时凑不够最少调用数就永不触发。

阈值与误触发

实测 · 本机执行输出
  配置                            | 只有 2% 基线失败时的误触发次数 | 加上 5 秒小故障时的误触发次数 | 真故障(100% 失败)的发现延迟
  ------------------------------+-------------------+------------------+------------------
  计数 20 次 / 最少 20 / 失败率 ≥ 50%   | 0                 | 43               | 0.18 s           
  计数 20 次 / 最少 20 / 失败率 ≥ 20%   | 104               | 328              | 0.06 s           
  计数 100 次 / 最少 100 / 失败率 ≥ 20% | 0                 | 50               | 0.38 s           
  时间 10 s / 最少 20 / 失败率 ≥ 50%   | 0                 | 0                | 4.98 s           
  时间 10 s / 最少 20 / 失败率 ≥ 20%   | 0                 | 0                | 1.98 s           
  时间 60 s / 最少 20 / 失败率 ≥ 20%   | 0                 | 0                | 4.98 s           
  6 小时里一共有 18 次 5 秒的小故障;小窗口里随机失败的个数波动很大(20 次调用、失败率 30% 时,一个窗口里失败 ≥ 10 次的概率约 4.8%,一个 5 秒小故障有 250 个这样的窗口),所以计数窗口小的配置会被短暂的小故障误触发;时间窗口按比例看,6 小时里没有误触发

小窗口里失败个数波动大,短暂小故障就能触发;时间窗口按比例看更稳,代价是发现真故障慢几秒。

慢调用率

实测 · 本机执行输出
  熔断器          | 打开的时刻  | 服务变慢之后(1 s 起)发出的调用 | 这些调用占用线程的总时间
  -------------+--------+--------------------+-------------
  A 只看失败率      | 没有触发   | 120                | 241 s       
  B 失败率 + 慢调用率 | 3.01 s | 26                 | 52 s        
  (连接类错误 0 次;不是超时,应为 0)
  慢调用要等调用返回才能被记成“慢”,所以打开的时刻不会早于第一批慢调用完成(服务变慢在 1 s,每个要 2 s,最早约 3 s);A 永远看不见这种“慢但不错”的下游,20 个线程一直被拖在里面

只看失败率看不见只慢不错的下游;加上慢调用率可以打开,但要等慢调用返回之后。

分步演示

熔断的统计窗口与阈值
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Go / nginx / Envoy / etcd / Consul / Nacos / OpenTelemetry / Prometheus(按各篇注明用到哪些及版本);所有服务都是本机自己起的进程,端口 9150 以上,只监听 127.0.0.1,不连任何外部服务,不碰本机已有的实例;故障用 kill -9 进程、让连接断开或在代码里注入延迟与错误来制造;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇前两节是虚拟时钟下的模拟(同一个 WB 类,不真的 sleep):第一节请求以固定速率到达,第二节 6 小时、50 次/秒的随机失败(带种子),每分钟 5% 的概率出现 5 秒的 30% 失败小故障,触发后窗口清空;第三节用真实的本机 HTTP 服务和 20 个线程
    • 没有验证:Resilience4j、Hystrix 等库各自的窗口与最少调用数的具体语义(这里是我自己的实现);窗口分桶(滚动统计)的实现细节;服务端排队时慢调用率与超时的叠加;计数窗口 N、最少调用数的更多组合
    京ICP备2026067502号-1京公网安备11010502063487号