有些程序虽然或者但是它已经死了--生产环境死锁定位

814 阅读2分钟

前言:系统主要提供websocket长链接服务。主要功能是替业务方建立和管理与浏览器的长链接,把业务方推送消息到浏览器端代码参考spring提供的websocket方案。 但是最近业务方反馈系统响应,观察系统资源占用正常,在一次系统假死后让运维协助导出堆栈信息。

惊喜不惊喜意外不意外,竟然存在死锁,线上问题能发现问题,说明问题还不是最坏的情况。那就搓搓小手那就处理吧。

确认死锁到底是那些锁

把上述形象一下如下图
MessageBroker-3先获取0x000000070745e248(Object的实例)锁然后去尝试获取0x0000000701a9e708(SessionHandler的实例)锁在这之前0x0000000701a9e708已经被MessageBroker-4获取到了,同时MessageBroker-4也在尝试获取0x000000070745e248,双方都无法获取到已经被对方持有的锁,所以程序僵持在这里,程序对外展示的现象就是假死无法响应请求。 因为Object的实例现在还无法确定,不妨先看看SessionHandler的代码,
唯一用到锁的地方,猜测另一把Object锁应该在session.close()方法,尝试打开发现实现类有很多,而且深度较深。那还是再看看哪里调用removeSession方法吧。
想查看哪里调用afterConnectionClosed方法,发现调用的地方也比较多。看来随机法不灵了。那就看下面给出的堆栈信息吧。 发现MessageBroker-3的信息比较简单,那就先拿MessageBroker-3开刀吧。
根据提示定位到
根据分析基本上确定了第二把锁就是responseLock。

分析死锁形成条件

继续追踪MessageBroker-3的逻辑

追踪代码整理MessageBroker-3的逻辑如下 websocket协议通过心跳维持长链接,发送心跳的时候会先获取这个session的responseLock锁。当发送心跳异常的时候会关闭链接,并触发afterConnectionClosed操作。因为我们在afterConnectionClosed调用removeSession操作清理内存存储的session。所以会继续获取SessionHandler锁。 MessageBroker-4则是先获取到了SessionHandler锁然后在removeSession中关闭session的操作中需要再去获取responseLock锁。在这样的情况下,双方都无法继续完成自己原油的流程,持有的锁也无法释放,程序就开始假死在这里了。

打破死锁形成条件

经过分析打破死锁形成条件可以通过去掉removeSession锁,或者把session的移除和关闭分开处理就可以解决了。解决了线上问题,长舒一口气,这一次终于不用被祭天了。