记录一下那天晚上的排查过程。服务用 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 毫秒。
这里还有个坑。重试的等待时间不该算在连接持有时间里,池子是共享资源,重试流量必须单独隔离,否则一次抖动会把整个池拖死。报错文案容易误导,真正的原因往往在相邻那一层的日志里。
