并发编程-线程卡死问题排查与解决 淩亂°似流年 2022-10-13 12:36 270阅读 0赞 ## 目标 ## * 线程卡死问题排查 参考: Java并发编程(第十章,第四章) [Future.get卡死,线程池的一个坑点][Future.get] 可以参考,注意点 ### 前言 ### 发现线程卡死的业务场景:有一个数据接口,因整个业务流程比较耗时,采用了异步形式。异步线程将业务数据存到数据库,并将数据发给Mq,在后续流程中再消费。在消费MQ数据时,会获取文件存储系统上的图片整合业务数据,生成PDF文件。这里通过异步处理,拆分各个业务,以此提升用户体验。 ### 问题发现和解决方式 ### 某日发现MQ 队列中出现消息堆积,有一百多条数据没有确认回执。程序开始不再消费数据。开始分析日志,排查问题。通过观察日志,从某一时刻,不再消费数据,排查上次流程Mq的消费情况,发现程序日志打印了一半日志就消失了。排查测试服务器,发现连接对象存储服务开始出现超时情况。 不由的引发思考:难道是因为出现异常导致线程中断了吗?但是代码中的有异常处理的逻辑。再排查代码,发现有一处代码,捕获了异常,仅仅打印了错误,但是没有再抛出去,但是仍然在日志中还是没有找到该日志信息。 (1)为什么MQ 消息会阻塞,不再消费? 通过排查代码,发现确认回执是在消费数据一进来就回执了,并且对该消息无论是否成功,都会丢掉。后续业务流程中的异常,会修改数据库状态,有一个定时任务会重发这些异常数据到MQ。不存在,没有确认MQ的消息导致不再消费数据的问题。 (2)是连接对象存储服务超时造成的吗?为什么? 在咨询了对象存储服务团队,告知由于昨天网络升级,现在会出现网络抖动,导致无法正确访问对象存储服务。他们也正在解决此问题。但是在这次失败的消费mq数据的流程中,并没有发现访问对象存储失败的异常日志。并且关于对象存储服务的配置中,明确加入了超时时间,不会因为网络问题导致程序卡死。 通过上述分析,没有得到更多的结论,考虑重启一次服务器看能否消费MQ 的数据,重启之后,仅仅消费了两条数据后,就不再消费数据了。大致可以猜到是程序卡死了,但是问题在哪里,只能再分析代码和排查日志, // 伪代码 // 监听队列,消费数据 @RabbitListener(xxx) public void receive(@Payload Data date,Message message,Channel channel){ // 确认回执 channel.basicAck(message.getMessageProperties.getDeliveryTag() ,flase); try{ // 执行生成pdf凭证文件的方法 createPdf(data); }catch(Exception e){ // 出现异常:回滚数据,修改数据状态 db.update(data); } } public void createPdf(Data data){ // 从对象存储获取图片url (http://s3.xx.xx/m01.png) String url = ObjectAs3.getUrl(data.filepath); // 合并数据,生成html /** 这个html 类似: <html> <div>姓名:<span>张三</span></div> <div>图片:<img src="http://s3.xx.xx/m01.png"/> </html> */ String html = createHtml(data,url); // 生成pdf凭证文件 try{ // ------------------ 线程卡死 --------------------- // 调用了开源工程 openhtmltopdf 的方式,将html转为pdf run(html); // ------------------- 线程卡死 ---------------------- }catch(Exception e){ log.error("生成失败:{}",e); } // 推送数据 } 终于在对某一次失败流程中,不断的刷新日志,半小时后出现了如下的异常日志,联想到请求对象存储服务的图片,没有拉下来,这次的网络请求,可能是造成线程假死的关键。 Cant't read image file ; unexpected problem for URI 'http://s3.xx.xx/m01.png' java.io.IOException: Unrecognized Image format at com.openhtmltopdf.pdfboxout.PdfBoxImage.<init>(PdfBoxImage.java:54) at com.openhtmltopdf.pdfboxout.PdfBoxUserAgent.getImageResource(PdfBoxUserAgent.java:99) at com.openhtmltopdf.pdfboxout.PdfBoxReplacedElementFactory.createReplacedElement(PdfBoxReplacedElementFactory.java:79) at com.openhtmltopdf.render.BlockBox.createReplaced(BlockBox.java:730) at com.openhtmltopdf.render.BlockBox.calcDimensions(BlockBox.java:854) at com.openhtmltopdf.render.BlockBox.calcDimensions(BlockBox.java:830) at com.openhtmltopdf.render.BlockBox.collapseBottomMargin(BlockBox.java:1415) at com.openhtmltopdf.render.BlockBox.collapseBottomMargin(BlockBox.java:1438) at com.openhtmltopdf.render.BlockBox.collapseBottomMargin(BlockBox.java:1438) at com.openhtmltopdf.render.BlockBox.collapseBottomMargin(BlockBox.java:1438) at com.openhtmltopdf.render.BlockBox.collapseMargins(BlockBox.java:1336) 随后,对象存储服务网络恢复,我们尝试重启了一台服务,瞬间堆积的MQ数据,被消费完毕。到此,这个问题暂时得到解决。为了防止以后再次发生该问题,还是要想办法解决此问题。随后,我们在github上对该开源工程[openhtmltopdf][] 提了一个 [Issues][] ,作者做出了解答,告知我: 目前默认的协议处理程序非常简单,使用了new URL(…).openStream() 方法,作者测试该HttpUrlConnection 的默认超时时间(ConnectTimeout ReadTimeout) 都是 0 。意味着可能会发生发送请求后没有超时时间导致线程假死。所以我们需要设置一个合理的超时时间。 Currently the default protocol handler is very simple. It just uses new URL(...).openStream(). 作者也提供了解决办法,使用自定义的协议处理程序(比如 okhttp),参见文档 https://github.com/danfickle/openhtmltopdf/wiki/Integration-Guide 作者也拉了一个分支,修改了默认协议处理程序的超时时间。参见 https://github.com/danfickle/openhtmltopdf/commit/5c07c8614ed94584f6fe7b655bc47c7f82123ff3 大家也可以测试下: public static void main(String[] args) { URL url = new URL("https://w.wallhaven.cc"); URLConnection urlConnection = url.openConnection(); // HttpURLConnection urlConnection.connect(); // 返回的流:HttpInputStream urlConnection.getInputStream(); // 获取超时时间 int connectTimeout = urlConnection.getConnectTimeout(); System.out.println(connectTimeout); // 0 int readTimeout = urlConnection.getReadTimeout(); System.out.println(readTimeout); // 0 } 至此,只需要增加使用自定义协议的处理程序代码即可解决此问题,这个问题算是解决。那么我们对这次线上问题的排查,做下归纳和总结。 ### 疑问 ### 1. 连接对象存储服务配置了超时时间为什么没有生效? 超时时间配置其实是生效的,只不过访问的图片链接是对象存储分享出来的公共访问的链接。没有直接使用对象存储提供的链接服务的程序代码。 2. 如何检测线程假死,有什么好的策略? 线程假死,一般多发生在长时间循环调用,递归调用,死循环。这些线程卡死大概都能在测试环境感知到,使用java提供的Jconsole或者一些检测命令(jstack)等。这次触发的网络超时,确实没有想到。应该在测试案例中,增加对断网情况下的测试。 在使用线程时,是否要考虑加入假死(时间非常长)的逻辑判断?这个值得思考。 3. IO流不关闭带来的影响,怎么确保流被关闭? 在增加自定义的协议处理程序时,发现会获取输入流(InPutStream),返回出去。也就是我们需要的图片资源。这个也引发我的思考,这些流被正确关闭了吗?如果流一直不释放,会占用着内存。怎么确保流被关闭,没有什么好的思路,有大佬有这方面经验可以告知下。不过,如果程序员规范编码,应该不会发生此问题 下一篇会尝试解答此篇问题! > 听说点赞关注的人,身体健康,一夜暴富,升职加薪迎娶白富美!!! > > [点我领取每日福利][Link 1] > 微信公众号:**耿子blog** > GitHub地址:**[gengzi][]** > ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4ODE3NzM5_size_16_color_FFFFFF_t_70_pic_center] [Future.get]: https://www.jianshu.com/p/538ca64efaca [openhtmltopdf]: https://github.com/danfickle/openhtmltopdf [Issues]: https://github.com/danfickle/openhtmltopdf/issues/700 [Link 1]: https://gengzi.github.io/Code-animal/#/./docs/fuli/README [gengzi]: https://github.com/gengzi [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4ODE3NzM5_size_16_color_FFFFFF_t_70_pic_center]: /images/20221005/efed8e588e02421295abf860a938ae7d.png
相关 Java并发编程问题:死锁与解决策略 在Java并发编程中,死锁是一个严重的问题。简单来说,死锁是两个或更多的进程因争夺资源而造成的一种互相等待的现象,若无外力干涉它们都将无法继续执行。 解决死锁的主要策略包括: 旧城等待,/ 2024年09月19日 16:21/ 0 赞/ 4 阅读
相关 并发编程:Java多线程常见问题与解决方案 在Java中进行多线程编程时,可能会遇到一些常见的问题。以下是一些问题以及相应的解决方案: 1. 同步问题 - 线程安全问题:当多个线程同时访问同一对象或资源时,可能会 骑猪看日落/ 2024年09月17日 09:15/ 0 赞/ 18 阅读
相关 Java并发编程的多线程问题:死循环与恢复机制 在Java并发编程中,多线程可能会导致各种问题,包括死循环。这里我们重点讨论死循环以及如何通过恢复机制来避免。 1. **死循环**: - 在单线程环境中,如果程序设计 左手的ㄟ右手/ 2024年09月15日 06:24/ 0 赞/ 6 阅读
相关 Java并发编程:线程同步与死锁示例 在Java并发编程中,线程同步和死锁是两个重要的概念。 1. 线程同步: 线程同步主要是通过原子操作、synchronized关键字或者Lock接口来实现的。这些机制保证了在 短命女/ 2024年09月14日 21:45/ 0 赞/ 18 阅读
相关 Java并发编程:线程同步与死锁实例 在Java并发编程中,线程同步和死锁是两个重要的概念。下面我们将通过实例来详细讲解。 1. **线程同步**: - **synchronized关键字**:用于修饰方法 本是古典 何须时尚/ 2024年09月06日 01:42/ 0 赞/ 18 阅读
相关 Python并发编程之线程(死锁) 前言:本博文主要讲解Python并发编程中线程的死锁。死锁就是使用互斥锁不当而产生的后果,那么我们应该如何避免、解决呢?请看下文详细讲解! 文章目录 淩亂°似流年/ 2022年11月27日 09:00/ 0 赞/ 195 阅读
相关 并发编程-线程卡死问题排查与解决 目标 线程卡死问题排查 参考: Java并发编程(第十章,第四章) [Future.get卡死,线程池的一个坑点][Future 淩亂°似流年/ 2022年10月13日 12:36/ 0 赞/ 271 阅读
相关 winfrom 多线程卡死问题 目录 原因分析 解决方式 一 添加 async await 二 添加同步方法去调用异步方法 原因分 迷南。/ 2022年10月05日 11:44/ 0 赞/ 208 阅读
相关 解决线程死锁问题 在编写多线程程序时,必须注意资源的使用问题。如果两个线程(多个线程时情况类似)分别拥有不同的资源,而同时又需要对方释放资源才能继续运行时,就会发生死锁。本实例演示了一种解决死锁 旧城等待,/ 2022年04月11日 13:20/ 0 赞/ 228 阅读
还没有评论,来说两句吧...