1.背景
现在的Java EE 系统通常都依赖于远程服务,需要通过网络请求获取远程服务。不管是使用什么协议(HTTP,HTTPS,JDBC 等)请求远程服务,Java VM最终都将委托给Socket去实现。首先会通过connect方法与远程服务建立连接,连接建立成功后就可以 write/read 与远程服务实现数据通信。
Socket.connect()、Socket.write()、Socket.read() 方法调用都被称为阻塞IO调用。这些调用由于网络延时、服务器响应缓慢等问题往往会导致线程挂起。
2.问题
从远程并发读取多张图片到服务器,之后转成字节流输送到三方服务。
项目初始启动后,能上传一些图片。也有日志打印,之后不打印日志,也不上传图片。
为了分析原因,只有到生产查看日志
3.排查
登陆服务器查询java应用的pid,ps -ef|grep "java"
,找到pid
使用命令jstack pid|grep "picSubmit" -A 20
,找到该线程池的所有线程当前状态的日志。如下
线程池中的所有线程,都是这个状态。一直运行着,并未造成阻塞。之后找到我们自己的代码,有一个readRemoteFile,1948行。
定位到改代码是while ((n = fis.read(b)) != -1) {
这一行。结合上下文发现,如果n不是-1,该循环会一直开着。因此造成线程一直运行着,也不报错。
从日志我们还发现,fis.read。是调用了sun.net.www.protocol.http.HttpURLConnection$HttpInputStream.read(HttpURLConnection.java:3066)
的实现。而我们的HttpURLConnection没有设置读超时和链接超时。所以线程一直被占用着无法释放。
4.原因
经过排查,定位到问题后,知道是网络没有超时,数据没拿到。为啥会出现这样的原因呢,是因为生产环境没有外网,服务要网文外网全都走的同一个代理通道。这样就会造成网络很差,而我们的图片又大。再带宽一定下,使用的变的多,就造成了个别线程阻塞。这就是为啥,一开始有的能传,有的不能传。最后由于积少成多,所有线程池都攒满了,就不会有线程来运行任务了,又由于使用了阻塞并且没有阻塞超时。因此就一直卡着。但是由于tomcat框架设置了超时,一定时间后,主线程任然会关闭,不会造成影响。而内部线程池则一直在等待中。
5.解决方案
1.修改打断,设置超时时间latch.await(20, TimeUnit.SECONDS);
2.设置http超时时间
private static byte[] readRemoteFile(String filePath) {
byte[] buffer = null;
try {
URL url = new URL(filePath); // 创建URL
URLConnection conn = url.openConnection(); // 试图连接并取得返回状态码
conn.connect();
HttpURLConnection http = (HttpURLConnection) conn;
http.setReadTimeout(5000);
http.setConnectTimeout(3000);
if (http.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream fis = new DataInputStream(conn.getInputStream());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int n;
while ((n = fis.read(b)) != -1) {
bos.write(b, 0, n);
}
fis.close();
bos.close();
buffer = bos.toByteArray();
}
} catch (Exception e) {
logger.error("获取远程图片失败:"+e.getCause());
return null;
}
return buffer;
}