
周三下午大促预热刚启动监控告警就炸了。杨工所有接口都在超时 小刘冲进工位但诡异的是——CPU 才 15%内存也正常服务器明明很闲请求就是进不来我扫了一眼监控大屏/order/create、/user/info、/product/list……所有接口 P99 延迟全部飙到 30 秒QPS 却从平时的 500 掉到了 50。别慌。 我拉过一把椅子CPU 很闲但请求进不来——这是典型的线程池/连接池耗尽。线程都被下游的慢调用占死了新请求在门口排队服务器当然闲。第一幕现象与机理——为什么 CPU 低还会卡死小刘CPU 才 15%怎么会卡死不应该 CPU 飙高才对吗我这是最常见的误区。线程卡在 IO 等待上时状态是WAITING或BLOCKED不消耗 CPU。就像餐厅里服务员都在等后厨出菜人很闲但客人进不来。我给他画了一张图请求 → accept 队列最多 100 个→ Tomcat 工作线程最多 200 个→ 下游调用DB/RPC/HTTPTomcat 默认maxThreads200、acceptCount100。当 200 个工作线程全被下游慢调用占住时新请求只能在 accept 队列里排队。队列满了就直接拒绝。所以表现就是接口集体超时但 CPU 很闲。小刘那怎么确认是这个问题第二幕定位——jstack 抓堵在哪我抓线程栈看工作线程都在干嘛。jstack 12345 thread_dump.txt我让他 grep 一下grep -c WAITING\|BLOCKED thread_dump.txt # 输出198小刘198 个线程都在 WAITING/BLOCKED我再看它们卡在什么方法上。grep -A 5 WAITING\|BLOCKED thread_dump.txt | head -60输出里大量线程停在java.net.SocketInputStream.socketRead0(Native Method) java.net.SocketOutputStream.socketWrite0(Native Method)我看到了吗线程全卡在socketRead0/socketWrite0上——说明全被下游的慢调用数据库、RPC、HTTP占住了。不是线程数不够是下游太慢把线程借走了不还。小刘那线程池和连接池的监控呢我配合监控埋点一起看线程池active长期顶满maxqueue持续上涨连接池Druid 的getWaitThreadCount() 0有线程在等连接getActiveCount()活跃连接顶满。两个信号同时出现基本就能确认是下游慢调用把线程和连接都占死了。第三幕关键认知——调线程数只是治标小刘那我把maxThreads从 200 调到 800 不就行了我治标不治本。 我拿起笔在纸上画请求要过三道关accept 队列 → 工作线程 → 连接池。你把线程数从 200 调到 800只是把排队的位置往后挪了——线程还是会卡在下游调用上只不过从 200 个变成 800 个一起卡。小刘那治本的方法是什么我四条给所有下游调用加超时——这是最基本的。JDBC 的socketTimeout、连接池的maxWait、Feign/HttpClient 的connectTimeout/readTimeout、Redis 的timeout一个都不能少。没有超时的调用就是定时炸弹。加熔断快速失败——用 Sentinel 或 Hystrix下游错误率超过阈值就自动断开不让故障扩散。与其让 200 个线程一起等死不如快速失败释放资源。耗时操作异步化——发邮件、发短信、写日志这些非核心链路扔到线程池或 MQ 里异步处理尽快归还工作线程。顺手检查线程总数——ls /proc/pid/task | wc -l如果超过一千查哪里在无界创建线程。有些框架或代码会偷偷起线程不控制的话会把系统拖垮。第四幕高频追问——线程数到底怎么设小刘杨工那线程数到底设多少合适我经典公式背下来CPU 密集型N 1N 是 CPU 核数。多出来的 1 个是为了防止页缺失等偶发停顿。IO 密集型2N或更精确的N × (1 等待时间 / 计算时间)。但公式只是起点最终要以压测为准。不同业务的 IO 等待时间差异很大压测出来的值才是最靠谱的。小刘还有一个问题——CPU 不高为什么还会卡死我因为线程在等锁、等 IO、等下游都是WAITING/BLOCKED状态不占 CPU。记住一句话CPU 高是计算瓶颈CPU 低但卡死是资源等待瓶颈。尾声排查下来根因是下游一个第三方物流接口响应从 200ms 涨到了 15 秒且没有设置超时。200 个 Tomcat 线程全卡在socketRead0上新请求全部被堵在 accept 队列里。修复方案给 Feign 客户端加上connectTimeout3s、readTimeout5s同时接入 Sentinel 做熔断降级。上线后接口延迟恢复正常即使下游偶尔抖动也能在 5 秒内快速失败不再拖垮整个服务。小刘杨工今天学到的东西够我吹一年面试了。我记住排查这类问题就像疏通下水道先看现象CPU 低 请求堵→ 抓线程栈jstack看线程卡在哪→ 确认根因下游慢调用占死线程→ 治本加超时 熔断 异步。套路熟了大促就不慌了。他点点头补了一句还有以后调线程数之前我一定先问自己——下游超时加了吗我笑了这才是今晚最大的收获。监控大屏恢复绿色大促流量平稳涌入。但我知道下一个卡住的请求迟早会来。不过没关系套路熟了就不怕了。