【Bug】httpClient使用代理后线程挂起

背景:

需要去监控某个网站,所以写了一个爬虫程序,被爬取的链接是Https,使得的是网上的代理,按ip使用量计费,该计费模式确实好用!

框架:httpClient 4.5.10

Java: Java 9

implementation ‘org.apache.httpcomponents:httpclient:4.5.10‘

问题:

然后问题出现了,因为是一个监控程序,所以需要不断的轮询,然后开了10个左右线程轮询,结果跑了半小时后,10个线程全部刮起,thread dump一下发现每个线程都如下:

"" daemon prio=5 tid=0x2a nid=NA runnable
  java.lang.Thread.State: RUNNABLE
      at java.net.SocketInputStream.socketRead0(SocketInputStream.java:-1)
      at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
      at java.net.SocketInputStream.read(SocketInputStream.java:171)
      at java.net.SocketInputStream.read(SocketInputStream.java:141)
      at sun.security.ssl.InputRecord.readFully(InputRecord.java:465)
      at sun.security.ssl.InputRecord.read(InputRecord.java:503)
      at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:975)
      - locked <0x1c8d> (a java.lang.Object)
      at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:933)
      at sun.security.ssl.AppInputStream.read(AppInputStream.java:105)
      - locked <0x1c8e> (a sun.security.ssl.AppInputStream)
      at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:137)
      at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:153)
      at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:280)
      at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:138)
      at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:56)

WTF?一直卡在 java.net.SocketInputStream.socketRead0 方法上,难道是我没有设置超时逻辑?于是赶紧去查找代码,加上逻辑:

HttpClient httpClient = HttpClientBuilder.create()
                                             .setConnectionManager(connManager)
                                             .setConnectionTimeToLive(20000L, TimeUnit.MILLISECONDS)
                                             .setRetryHandler(getRetryHandler())
                                             .setDefaultCredentialsProvider(getProxyProvider())
                                             .build();

自信的跑了一段时间后,无用还是挂着,于是上网看看,发现很多人都遇到过这种事,但没人说明是为啥?有几种可能:

  1. httpClient某个版本的bug,在某个版本后修复了,然后检查发现,我的版本是最新的,没问题
  2. 某博主也是用的代理,情况和我一摸一样,然后搞不出问题,所以就绕过去了,kill thread ,这真是神操作

解决:

因为是用的是框架,对框架也不熟,所以直觉告诉我需要先考虑是框架的问题,所以开始对httpClient的使用开始研究,太恶心了,这个框架每个版本的使用方法都不太一样;最终我发现两种设置超时的方法,一个是掌控应用层的RequestConfig,第二种是掌控Socket的SocketConfig,这个时候我已经猜到了问题原因,这次信心慢慢,于是我加入了以下代码,发现问题神奇的解决了。

BasicHttpClientConnectionManager connManager = new BasicHttpClientConnectionManager();
    connManager.setSocketConfig(SocketConfig.custom().setSoTimeout(2000).build());
    HttpClient httpClient = HttpClientBuilder.create()
                                             .setConnectionManager(connManager)
                                             .setConnectionTimeToLive(20000L, TimeUnit.MILLISECONDS)
                                             .setRetryHandler(getRetryHandler())
                                             .setDefaultCredentialsProvider(getProxyProvider())
                                             .build();

分析:

抱着知其所以然的心态,为什么还需要加上这个配置呢?我开始深思研究他们的源码:

相关推荐