背景
由于技术选型以及业务场景需要,我们该项目中用到了redis监听和 基于 web容器(tomcat和undertow)的websocket的服务
现象
观察JVM监控发现 应用GC占用时间过长,内存使用过高问题,
服务器报错日志
分析
出现该问题我们首先dump出jvm内存快照 分析内存dump文件,发现内存占用最高为ThreadPoolExecutor
分析ThreadPoolExecutor对象,内部队列数量高达452W(4520629)
查看队列数据,确认队列存储信息为Redis通知消息
对比代码分析,推测为应用Redis消费者无法消费消息导致,Redis消息就一直堆积,无法消费
尝试复现
tomcat容器
新建应用使用spring-websocket实现websocket接口,定义send-pool线程池模拟Redis消费线程,当连接进来时定时向send-pool线程池创建任务:输出当前线程名称及时间并且向当前所有连接下发websocket消息
单个连接send-pool线程池任务正常,但当连接数到达5个时,发现send-pool线程池输出完线程名称及时间信息后无后续输出,JVM监控线程死锁
死锁对象及行号,与问题分析时大致一致 虽未复现生产的错误信息,但线程死锁问题再次出现
undertow容器
虽未出现线程死锁,但出现大量报错信息
使用Netty实现websocket再次测试
线程输出正常、线程运行正常
总结
WebSocket下发消息时触发WebSocket下发触发IO锁,但没关闭此IO通道 Web有Redis消费者线程在通过该WebSocket通道下发消息时,遇到IO锁,一直在等待消息下发完成,进而导致线程无线等待、死锁。
- undertow websocket实现可能存在并发死锁问题
- tomcat websocket实现虽无并发死锁,但高并发也会报错
- netty websocket未发现以上问题,故确定用netty代替web容器实现web服务的websocket接口