bigywater.tech
微服务 · 12

自适应限流:根据延迟调整并发上限(AIMD、gradient)

并发上限设多少?静态值太小吃不满,太大排队;AIMD 和 gradient 这样的自适应算法能不能自己找到?容量突然变化时呢?这篇用本机的小服务和手写的两种自适应限流器量出来。

2/s 对 142/s
静态上限 256 与自适应上限(AIMD / gradient)下,300 个/秒打容量约 142/s 的服务时成功的速率
823 ms 对 94 ms
静态上限 64 与 AIMD(最近 2 秒基准)放行请求的 p99 延迟
10/s 对 71/s
容量减半后,基准 rtt 取最近 200 个样本与取最近 2 秒的 AIMD 的成功速率

静态上限 vs 自适应

实测 · 本机执行输出
  上限                        | 第 2 到 10 秒到达的请求 | 被客户端拒绝 | 成功   | 成功的速率 | 放行请求延迟中位数 | p99     | 平均上限
  --------------------------+-----------------+--------+------+-------+-----------+---------+-----
  静态 4                      | 2400            | 1863   | 537  | 67/s  | 57 ms     | 62 ms   | 4   
  静态 8                      | 2400            | 1339   | 1061 | 133/s | 57 ms     | 62 ms   | 8   
  静态 16                     | 2400            | 1264   | 1136 | 142/s | 109 ms    | 137 ms  | 16  
  静态 64                     | 2400            | 1264   | 1122 | 140/s | 446 ms    | 823 ms  | 64  
  静态 256                    | 2400            | 1267   | 19   | 2/s   | 1804 ms   | 3532 ms | 256 
  AIMD(参照最近 200 个样本的最小 rtt) | 2400            | 1266   | 1134 | 142/s | 72 ms     | 96 ms   | 10  
  AIMD(参照最近 2 秒的最小 rtt)     | 2400            | 1262   | 1138 | 142/s | 74 ms     | 94 ms   | 10  
  gradient                  | 2400            | 1263   | 1137 | 142/s | 132 ms    | 201 ms  | 19  
  (连接类错误 0 次;不是超时,应为 0)

上限太小吃不满容量,太大请求在服务端排队、延迟爆炸;自适应不用事先知道容量。

容量突然变化

实测 · 本机执行输出
  上限               | 变化之前:成功速率 · p99    | 平均上限 | 变化之后:成功速率 · p99   | 平均上限
  -----------------+--------------------+------+-------------------+-----
  静态 8             | 132/s · p99 62 ms  | 8    | 73/s · p99 113 ms | 8   
  静态 64            | 124/s · p99 811 ms | 64   | 1/s · p99 1046 ms | 64  
  AIMD(最近 200 个样本) | 141/s · p99 94 ms  | 10   | 10/s · p99 113 ms | 1   
  AIMD(最近 2 秒)     | 140/s · p99 101 ms | 10   | 71/s · p99 182 ms | 8   
  gradient         | 142/s · p99 216 ms | 19   | 75/s · p99 204 ms | 12  
  (连接类错误 0 次;不是超时,应为 0)

静态上限为变化前调的,变化后服务端排队;自适应能跟着降,但基准 rtt 的取法会让 AIMD 卡死在 1。

分步演示

自适应限流
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动。服务端是 Python http.server,用信号量限制同时处理 8 个请求,每个请求 sleep 50 ms(之后变成 100 ms),多的在服务端排队;容量理论值 160/s,实测上限约 142/s(Python 线程调度的开销);两个自适应限流器(AIMD、gradient)是我写的简化版,参数(0.9、1.5 倍、窗口 200 ms、平滑 0.2、队列项 4)是我选的,不是 Netflix concurrency-limits 的实现;实验里连接类错误 0 次
    • 没有验证:Netflix concurrency-limits 的 Vegas / Gradient2 的参数与实现;服务端同时有多种请求耗时不同的情形;多个客户端各自自适应时互相的影响;基准 rtt 的更多取法(例如周期性探测);每种配置只跑了一轮,小于 5% 的差别不能当结论
    京ICP备2026067502号-1京公网安备11010502063487号