记录一下那天晚上的排查过程。服务用 httpx 连接池上限 100,上游抖动的十分钟里大量请求进入退避等待,连接一直被占着不放,正常请求报 PoolTimeout,健康检查也开始超时。

刚开始盯着「池上限按平时 QPS 配,没有给重试留余量」,折腾了半个多小时,排掉了。

原因不止一个,得分开看。等待重试期间连接没有归还连接池。还有一种是重试任务和正常请求共用同一个池。

还有一种情况,退避时间叠加在持有连接的时间上,越等越久。

动手时先看池占用与等待队列长度

python -c "import json;d=json.load(open('pool.json'));print(d)"

随后把重试等待移到归还连接之后

python -c "print('release_before_backoff=True')"

给重试单独一个小池并限制并发

python -c "print({'retry_pool':20,'normal_pool':80})"

压测确认 PoolTimeout 归零

python loadtest.py --target chat --qps 120 --duration 90

改完先别撤,确认这几项。上游抖动期间 PoolTimeout 从每小时 900 次降到 0,退避中的请求不再占用连接,正常请求 P99 回到 900 毫秒。

这里还有个坑。重试的等待时间不该算在连接持有时间里,池子是共享资源,重试流量必须单独隔离,否则一次抖动会把整个池拖死。报错文案容易误导,真正的原因往往在相邻那一层的日志里。

作者 9823189