线上某个列表接口偶尔超时,监控上看到 P99 突然从 80ms 涨到 3s,但数据库慢查询日志里什么都没有。这类问题通常不在 SQL 本身,而在请求排队。记录一下当时的排查路径。
一、先分清时间花在哪
给一次请求打上分段耗时:
网关接入 → 应用排队 → 业务执行 → 外部调用 → 响应序列化
排查时发现 业务执行 只用了 20ms,绝大部分时间落在 应用排队。这说明请求已经进到服务,但迟迟没拿到工作线程。
二、线程池被打满的常见原因
Web 容器(Tomcat / php-fpm / uvicorn worker)的并发数是固定的。当大量请求卡在慢的外部调用上,工作线程就会被占住,后面的请求只能排队。
当时的问题是一个下游 HTTP 调用没设读超时,对方抖动时请求挂在连接读取上,默认超时长达数分钟。于是几十个线程被占满,整个服务的健康接口都开始超时。
修复要点:
connect_timeout = 1s # 建连
read_timeout = 2s # 等响应
retry = 1 次,且只对幂等请求
任何外部调用都必须显式设置超时,不要依赖库默认值。超时时间要小于上游给你的超时预算,留出重试和降级的余量。
三、连接池复用别忽略
另一个坑是每次请求新建连接。短连接在高并发下会带来三次握手开销,还可能把本地端口耗尽,表现为偶发 Cannot assign requested address。
用连接池时三个参数要配套:
maxTotal:上限,按峰值 QPS × 平均耗时估,留一点余量maxIdle:空闲保留数,太大浪费、太小会频繁建连maxWait:拿不到连接时最多等多久,超时要快速失败而不是无限阻塞
四、加上隔离和兜底
把不同下游的调用拆到独立线程池或独立信号量,避免一个慢接口拖垮全站。同时对非核心数据做降级:推荐列表取不到就返回空数组,不要阻塞主流程返回。
小结
超时类问题先看排队耗时再优化 SQL,往往能少走弯路。改完之后这个接口的 P99 回到 90ms 左右,而且下游再抖动也不会扩散到其它接口。
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:https://mi-blog.cn/archives/707
共有 0 条评论