bigywater.tech
微服务 · 13

过载保护:队列满时丢哪一部分请求,以及怎么让上游慢下来

服务过载时请求队列该怎么处理?队满丢哪一个?背压怎么把下游的慢传回上游?这篇用本进程里的服务模型和流水线,把吞吐曲线、几种丢弃策略和背压的效果量出来。

0/s 对 155/s
到达 400/s 时,无保护(无界 FIFO)与有保护(队列 50 + 检查截止时间)500 ms 内成功的速率
100% 对 24%
按优先级丢时,高优先级与低优先级请求的成功比例(到达 400/s,其余做法两类都在 36% 到 41%)
5662 ms 对 739 ms
上游 500/s、下游约 160/s 时,无界队列与有界队列(100)里任务最长的排队时间

吞吐曲线

实测 · 本机执行输出
  到达速率  | 无保护:成功速率 | p99    | 白干(处理完时已超时) | 有保护:成功速率 | p99    | 丢掉的(拒绝 + 过期)
  ------+----------+--------+-------------+----------+--------+-------------
  100/s | 100/s    | 30 ms  | 0           | 100/s    | 31 ms  | 0           
  200/s | 20/s     | 505 ms | 685         | 152/s    | 368 ms | 238         
  300/s | 0/s      | -      | 836         | 153/s    | 373 ms | 832         
  400/s | 0/s      | -      | 880         | 155/s    | 372 ms | 1421        
  600/s | 0/s      | -      | 907         | 155/s    | 366 ms | 2617        
  800/s | 0/s      | -      | 916         | 157/s    | 371 ms | 3805        

无保护的队列越积越长,排到的请求都已过截止时间,成功速率塌到 0;有保护稳定在容量上。

队满丢哪个

实测 · 本机执行输出
  做法          | 成功速率  | 成功请求延迟中位数 | p99    | 高优先级成功比例 | 低优先级成功比例 | 白干(处理完时已超时)
  ------------+-------+-----------+--------+----------+----------+------------
  无界 FIFO     | 0/s   | -         | -      | 0%       | 0%       | 875        
  tail-drop   | 154/s | 350 ms    | 368 ms | 36%      | 39%      | 0          
  head-drop   | 160/s | 149 ms    | 315 ms | 41%      | 40%      | 0          
  LIFO + 丢最老的 | 164/s | 28 ms     | 415 ms | 41%      | 41%      | 3          
  随机丢         | 159/s | 218 ms    | 332 ms | 38%      | 40%      | 0          
  按优先级丢       | 156/s | 270 ms    | 333 ms | 100%     | 24%      | 0          

总成功速率差不多,区别在延迟分布和谁被牺牲;LIFO 让多数请求很快,按优先级丢保住高优先级。

背压

实测 · 本机执行输出
  队列             | 上游 6 秒生产了 | 上游实际速率 | 每秒的队列长度                         | 最后 300 个任务排队时间的中位数 | 最长排队时间  | 上游停止时队里还剩
  ---------------+-----------+--------+---------------------------------+--------------------+---------+----------
  无界             | 3001      | 500/s  | [0, 353, 706, 1060, 1415, 1773] | 4910 ms            | 5662 ms | 1834     
  有界(100)+阻塞 put | 967       | 161/s  | [0, 100, 100, 100, 100, 100]    | 699 ms             | 739 ms  | 0        
  有界队列把下游的慢直接传导给上游:上游被 put 阻塞,速率降到下游的处理能力;无界队列把这个差额存起来,变成越来越长的排队时间(和内存)

有界队列加阻塞把下游的慢传给上游;无界队列把差额存起来变成排队时间和内存。

分步演示

过载保护
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动。服务模型是我写的(4 个工作线程 sleep 20 ms,理论容量 200/s,实测约 160/s),请求是进程内的 Python 对象,没有经过网络,client 是开环线程;第三节是两个线程之间的 queue.Queue,下游 sleep 5 ms;各丢弃策略的队列容量都是 50;每种配置只跑一轮,数字每次运行会有小幅抖动
    • 没有验证:真实网络与 HTTP 连接的开销(队列在 accept 队列和内核缓冲里);客户端重试对过载的放大(见第 2、4 篇);服务端按请求代价(而不是个数)限制队列;TCP 层的背压(接收窗口)与应用层的背压的交互;gRPC 的流控窗口(见 gRPC 系列)
    京ICP备2026067502号-1京公网安备11010502063487号