Loading

记录后台的一次系统卡死

问题表象

后台程序,运行了几天后,进程仍然存在,电脑的cpu和内存都正常,但是后台的接口无法访问,就像假死一样,重启后又能恢复正常。

分析过程

先看日志

除了业务上的正常log输出外,并无错误log,但是部分log比较扎眼,发现websocket每隔6分钟会重连一次,重连连绷了?

为什么会重连,6分钟哪来的?

如下图

image-20240118133547275

使用nginx代理websocket链接,配置了proxy_send_timeout为360s的情况下,如果超过6分钟不响应会主动断开,前端做了断开重连的机制,所以造成了6分钟重连一次的现象,审查了后台websocket连接加入和连接断开的代码,并无逻辑异常,所以暂时搁置先排查是否是线程死锁或者内存溢出

工具分析

使用jvisualvm工具分析,发现无线程死锁,无内存溢出,线程数量正常,从线程Dump来甚至没有阻塞状态的线程,那么到底是哪里的问题?在没什么头绪的情况下,就模拟卡死前程序一直在做的事情,从日志看就是:重连websocket,这一重连发现复现了,后台卡死了,问题锁定在websocket的相关逻辑,但是就像前文说的,这里并无逻辑问题,那么有没有耗时操作?

其实是有的,这里调用了webservice的接口来获取某个状态,但是实际调用的时候这个接口没有任何问题,连接速度返回速度都正常,但是在注掉调用webservice的逻辑之后系统就正常了,无论怎么重连都不会假死

把逻辑还原,在方法前后加入log,再次重连,在脱敏后,日志大致如下

[INFO] [XNIO-1 I/O-4] xxx.xxx.xxx.xxx : before call webservice
[INFO] [XNIO-1 I/O-4] xxx.xxx.xxx.xxx : after call webservice
[INFO] [XNIO-1 I/O-3] xxx.xxx.xxx.xxx : before call webservice
[INFO] [XNIO-1 I/O-3] xxx.xxx.xxx.xxx : after call webservice

[INFO] [XNIO-1 I/O-2] xxx.xxx.xxx.xxx : before call webservice

[INFO] [XNIO-1 I/O-3] xxx.xxx.xxx.xxx : before call webservice
[INFO] [XNIO-1 I/O-3] xxx.xxx.xxx.xxx : after call webservice
[INFO] [XNIO-1 I/O-1] xxx.xxx.xxx.xxx : before call webservice
[INFO] [XNIO-1 I/O-1] xxx.xxx.xxx.xxx : after call webservice

[INFO] [XNIO-1 I/O-4] xxx.xxx.xxx.xxx : before call webservice

[INFO] [XNIO-1 I/O-1] xxx.xxx.xxx.xxx : before call webservice
[INFO] [XNIO-1 I/O-1] xxx.xxx.xxx.xxx : after call webservice
[INFO] [XNIO-1 I/O-1] xxx.xxx.xxx.xxx : before call webservice
[INFO] [XNIO-1 I/O-1] xxx.xxx.xxx.xxx : after call webservice
[INFO] [XNIO-1 I/O-1] xxx.xxx.xxx.xxx : before call webservice
[INFO] [XNIO-1 I/O-1] xxx.xxx.xxx.xxx : after call webservice

[INFO] [XNIO-1 I/O-3] xxx.xxx.xxx.xxx : before call webservice

[INFO] [XNIO-1 I/O-1] xxx.xxx.xxx.xxx : before call webservice

发现,其中只打印了before call webservice的线程号在后续再也不会出现,总共四个用完就假死

为什么只有四个?

我猜想和cpu核心数有关,测试的电脑cpu是四核,在了解了相关资料后,确实如此

通过查阅资料可知

  • XNIO开头的都是undertow线程,undertow是基于XNIO框架实现的
  • XNIO-1 I/O 线程:是服务中的非阻塞线程,线程数默认等于CPU核数

类比来说,XNIO-1 I/O线程类似NIO体系中的"BOSS"线程(负责和连接建立相关的线程),如果所有该类线程都由于某种原因阻塞且不释放,那么后续的所有网络事件都无法得到处理,这将导致系统无法继续服务新的网络请求,从功能层面看就像“卡死”了一样。

所以应当避免使用该类线程执行可能阻塞的操作

还有问题

但是,就像前文说的,webservice单独使用并无问题,无论使用soupui直接还是postman间接调用,接口的连接速度和响应速度都是正常的。难道是XNIO-1 I/O类线程和webservice调用会特殊触发bug嘛?而且该线程实际是阻塞状态但是在线程Dump中依旧是Runnable状态也很奇怪

但是结合线程Dump来看:

image-20240118144223592

最后卡在Native Method系统调用上,所以状态不一致可能是由于该阻塞发生在Java虚拟机外部资源上,操作系统级别的系统调用,JVM无法精确感知到这种“native”级别的阻塞也是有可能的。

总结

避免让XNIO-1 I/O类线程执行可能阻塞的逻辑

posted @ 2024-01-18 14:59  VoidCm  阅读(51)  评论(0编辑  收藏  举报