记录一下那天晚上的排查过程。uvicorn 0.30 配了 limit_concurrency=100,压测到 120 并发时多出来的请求直接返回 503,响应体是空的,没有 Retry-After,客户端 SDK 把 503 当可重试,重试把流量又放大了一轮。
刚开始盯着「worker 数只有 1,并发上限按进程算」,折腾了半个多小时,排掉了。
排查方向其实有限,先把可能性排一排。limit_concurrency 超限时直接 503 且不带 Retry-After。还有一种是前置网关把 503 当可重试,放大了流量。
还有一种情况,请求在队列里等待的时间没计入超时。
复现并看响应头
curl -s -o /dev/null -D - -w '%{http_code}\n' http://localhost:8000/health
然后用并发工具压到上限以上
python -c "import concurrent.futures,urllib.request;f=lambda _:urllib.request.urlopen('http://localhost:8000/health').status;print(set(concurrent.futures.ThreadPoolExecutor(120).map(f,range(120))))"
再往下在网关层对 503 加 Retry-After
python -c "print({'status':503,'headers':{'Retry-After':'2'}})"
调大 worker 数与 limit 后复测
uvicorn app:app --workers 4 --limit-concurrency 400
改完先别撤,确认这几项。超过上限的请求收到带 Retry-After 的 503,客户端按 2 秒退避,重试总量可控,正常请求的 P99 不再被拖高。
limit_concurrency 是保护进程的硬闸,返回体很简陋;要在网关补 Retry-After 并让客户端区分可重试与不可重试,否则退避会变成放大器。改动前把原配置备份一份,出问题能马上回滚,这比任何技巧都实用。
