多线程环境下使用HttpClient代理造成线程阻塞-踩坑记

案发现场

因业务所需在多线程环境下使用HttpClient组件对某个HTTP服务发起请求,项目运行一段时间之后,有用户反馈系统无法正常登录,于是博主先上服务器查看日志,发现日志最后打印是停留在几个小时前,先用ps -ef|grep命令查看系统进程是处于正常运行,然后再用jvisualvm工具查看发现服务器主机CPU使用率在90%之上并未呈现出下降趋势,查看实时线程有一批线程是一直处于运行状态且运行时间占比也非常高,初步判断是线程内部代码异常导致被阻塞。

问题排查

一开始review代码,也没找到具体原因,因为该出异常的地方都有进行try catch捕捉,也怀疑过是因为创建线程数过多而导致服务器内存资源不够,引起JVM频繁触发GC而导致业务线程被暂停,但是通过GC日志的输出结果看也是正常的。因为HttpClient是Java访问http服务常用的包,之前使用一直没有出现什么问题,就是在前几天写了一个多线程访问http服务代码,程序刚开始启动后执行是没有问题,按正常逻辑执行,只是过了一段时间后系统进入假死状态。

最后,通过dump线程堆栈和内存堆栈,再对堆栈数据进行分析。从分析结果看,确认是因为socket连接在读取数据时被阻塞引起线程一直处于RUNNABLE状态。

下面附上当时出问题的代码

于是在网上搜寻了关于socket阻塞,看看是否有其他人也出现这个问题,果然搜到了,竟然是jdk的一个bug,下面贴上jdk官方的bug公告

​bugs.openjdk.java.net/browse/JDK-

标题是 SocketInputStream.socketRead0 can hang even with soTimeout set,和我遇到的问题是同一个问题,这个问题官方在某些版本里面已经修复了这个问题

无奈的是我使用的JDK版本是JDK8u251,没有在修复版本里面,不过找到了一个不用升级版本,也可以解决这个问题的方法,在httpClient对象创建的时候设置一个SocketConfig并且新增校验代理是否可用逻辑,代码如下

这样代码就能正常运行,再也没有出现上面的阻塞问题。

后面也总结了一下,这里的坑在于使用HttpHost进行代理请求时设置的SocketTimeout方法由于JDK的bug并没有生效,需在创建httpClient处再次设置SocketTimeout才能解决Socket超时的问题。因为其他因素升级jdk版本比较麻烦,暂时以这个方法来解决,不过大家最好还是升级到已经修复的版本,从而避免这个问题发生。