1、问题前端用户突然使用系统非常卡,直至不能使用,也不报错,查看CPU,CPU Interrupt Time 百分之70多持续半小时,
2、在这半小时过程中,集中报错为:
1、org.apache.catalina.connector.ClientAbortException: java.io.IOException: 断开的管道
2、Caused by: org.apache.catalina.connector.ClientAbortException: java.io.IOException: Connection reset by peer
3、查询数据库awr报告基本正常,没有大占用CPU的sql,基本正常,暂时排除数据库问题
GC基本也是正常,没有FULLGC,也可以排除一些内存泄漏问题,只能怀疑Tomcat线程池了
4、tomcat线程号目测最大也才 [http-nio-50603-exec-118],目测线程池没满, 但是其中还有一些自定义线程 :[WhifExecutor-9] 这种线程
怀疑1:
-
外部服务调用阻塞
-
WhifExecutor-9
这类线程表明应用可能使用了自定义线程池调用外部服务(如HTTP API、RPC、文件存储服务等) -
外部服务响应变慢或阻塞,导致Tomcat工作线程被挂起,最终客户端超时断开(触发
Connection reset
) -
特征:卡顿时,线程dump会显示大量线程阻塞在外部调用(如HTTP连接、JDBC驱动等
-
5、看调用外部接口基本都是HTTP请求,tomcat线程发起,也就调用了五六次,每次调用基本卡4分钟,
线程编号≠活跃线程数
-
Tomcat的线程编号是顺序递增但不连续的
-
线程复用机制可能导致编号停留在较低数值(如118),即使实际活跃线程数已经达到瓶颈
怀疑已经到达瓶颈了,WhifExecutor-9线程 等自定义线程池可能已耗尽(比如核心线程10个,队列100,但外部服务阻塞导致所有线程卡死)
最可能的罪魁祸首
外部服务阻塞 + 无超时设置 + 线程池隔离缺失的三重组合
-
即使只有5-6个请求调用4分钟慢接口:
-
占用5-6个Tomcat线程
-
可能占满
WhifExecutor
自定义线程池(如核心线程5个) -
触发客户端重试制造更多请求
-
最终导致健康检测、监控等基础请求也无法响应
-
为什么重启能临时解决?
重启后:
-
所有阻塞线程被强制终止
-
TCP连接重置
-
线程池回到初始状态
虽然线程池物理隔离,但可能通过以下方式间接影响Tomcat性能:
关联场景 | 影响机制 |
---|---|
竞争CPU资源 | 自定义线程池消耗大量CPU时,Tomcat线程无法获得足够计算资源 |
总结:
所以基本上:tomcat发起得外部服务阻塞+上自定义线程池竞争CPU导致,tomcat线程池即使没满,tomcat也没有cpu时间片去从线程池中拿线程,导致服务卡、宕机
CPU竞争引发卡顿的核心原理
1. 操作系统级线程调度
-
所有线程(包括Tomcat和自定义线程)共享CPU时间片
-
当自定义线程池(如
WhifExecutor
)的线程疯狂占用CPU时:-
Tomcat线程虽然存在于线程池中
-
但无法获得足够的CPU时间片执行任务
-
表现为请求响应延迟甚至超时
-
自定义线程池行为 | 对Tomcat的影响 |
---|---|
执行CPU密集型计算 | Tomcat线程被"饿死",无法处理简单HTTP请求 |
无节制的死循环 | 整个应用失去响应(包括健康检查接口) |
大量上下文切换 | CPU忙于切换线程,实际吞吐量下降 |
核心证据:从日志中看有6个慢请求
-
TCP连接耗尽:
-
每个慢请求保持TCP连接4分钟(240秒)
-
客户端端口复用周期通常为60-120秒(Linux默认)
-
6个慢请求 × 240秒 = 需要至少1440个临时端口
(超过默认net.ipv4.ip_local_port_range
范围)
-
-
客户端重试风暴:
-
慢请求客户端超时后自动重试(如Nginx默认60秒超时)
-
产生指数级新请求:
6请求 × 每60秒重试 × 4分钟 = 至少24次额外请求
-
-
负载均衡器踢节点:
-
健康检查失败(因为部分请求超时)
-
流量转移到其他节点 → 剩余节点压力更大 → 雪崩
-
数学证明卡顿必然发生
使用**利特尔法则(Little's Law)**计算:
并发请求数 = 到达率 × 平均响应时间
-
慢请求:6个 × 240秒 = 1440并发占用
-
正常请求:100个/秒 × 0.05秒 = 5并发占用
总计:1440 + 5 = 1445 >> 200(max-threads
)
👉 即使只有6个慢请求,实际并发需求远超线程池容量!