记一次工作中的重大BUG---http连接池

很晚了,但是还是决定打开电脑记录下最近一段时间遇到的问题。
事情是这样的,前段时间公司一个同事ZM休婚假,我就接手了他手上的一家比较重要的保险公司(暂时成为RB吧,我们是一家保险销售平台,上海最会保),其实一开始我是拒绝的,因为这家公司实际上的流程不是正常的,因为它是爬取的,而且据说有很多坑(事后发现并不是那样夸张吧)。

说爬取,其实也就是用httpClient模拟浏览器的行为,然后用htmlParse解析xml,然后在模拟提交表单之类。我在程序入口处将要访问的链接log一下,用浏览器去访问这个链接直接能够打开,但是httpClient却经常抛socketTimeoutException,这就奇怪了,难道是cookie有问题?RB服务器没有检测到所需要的cookie?然后这个http connect就一直被idle了?但是我是访问RB的第一个链接啊,明显不需要任何cookie啊,第一个猜想被pass掉;好吧,继续猜想!难道是没有完全模拟浏览器?为此做了如下httpHeader设置:

private void setHttpHeader(HttpMethod method, String referSuffix, boolean isAjax){   
    method.setRequestHeader("Accept", "text/html, */*; q=0.01");    
    method.setRequestHeader("Accept-Encoding", "gzip, deflate");
    method.setRequestHeader("Accept-Language","zh-CN,zh;q=0.8,en;q=0.6");
    method.setRequestHeader("Cache-Control", "no-cache");
    method.setRequestHeader("Connection", "Keep-Alive");
    method.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;");
    method.setRequestHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36");
    method.setRequestHeader("Host", "www.epicc.com.cn");
    method.setRequestHeader("Referer", BASE_URL + referSuffix);
    method.setRequestHeader("Origin","http://www.epicc.com.cn");
    if (isAjax){
        method.setRequestHeader("X-Requested-With", "XMLHttpRequest");
    }
}

每次构造get或者post的时候调用此方法来设置头,然而并没有啥用处,依然socketTimeoutException;
然后我就蒙圈了,各种想,然而并没有什么头绪!其实我当时想的是我应该用log4j打印一下httpClient的debug的log的,或许能够找到原因;
另一个同事WL先tcpdump抓了包,ZM和WL工作8年的老程序员,果然还是经验很重要,然后就分析出来了,分析发现正常情况下,http发送前都需要进行tcp握手建立连接,然后再http发送报文,然而使用了连接池后,每次http请求结束后,这些连接会被放回连接池,直到下次被使用,但是我们不知道的是,可能这个时候服务器已经把这个连接断开了,而第二次如果有请求发现连接池中有连接空闲,就未经检查就使用了这个connection,这样无法发送报文,就好抛出socketTimeoutException,这就是为什么有时候会socketTimeout,有时候不会,因为这个connection不知道有没有被服务器shutdown掉,如果关闭了,则异常呗!

如何使用?

MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
HttpConnectionManagerParams params = connectionManager.getParams();
params.setConnectionTimeout(30000);
params.setSoTimeout(60000);
params.setDefaultMaxConnectionsPerHost(1000);
params.setMaxTotalConnections(1000);
httpClient = new HttpClient(connectionManager);

当初使用MultiThreadedHttpConnectionManage(关于)是为了复用连接的连接,解决办法就是不用连接池了呗,每个service实例中都存有一个共享的httpClient,这样多次发起http请求也可以公用这个httpClient;

当然最好的解决办法或者说是使用MultiThreadedHttpConnectionManage的错误是由于没有设置正确的参数,HttpConnectionParams中有一个方法:

/**
   * Defines whether stale connection check is to be used. Disabling
   * stale connection check may result in slight performance improvement
   * at the risk of getting an I/O error when executing a request over a
   * connection that has been closed at the server side.
   * @param value <tt>true</tt> if stale connection check is to be used,
   * <tt>false</tt> otherwise.
   */
   public void setStaleCheckingEnabled(boolean value) {
      setBooleanParameter(STALE_CONNECTION_CHECK, value);
   }

设置true后每次使用连接池中的链接会先检查下链接是否可用;虽然可能会消耗15-30ms,但是这个应该比tcp握手来的快。

其实任然还有很多优化的地方,如失败重试等等;

另外整理了一下HTTP Client MultiThreadedHttpConnectionManager线程安全连接管理类源码解析
请看:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,260评论 19 139
  • http协议有http0.9,http1.0,http1.1和http2三个版本,但是现在浏览器使用的是htt...
    一现_阅读 1,929评论 0 3
  • HTTP cookie(也称为web cookie,网络cookie,浏览器cookie或者简称cookie)是网...
    留七七阅读 18,213评论 2 71
  • 赤身浴清风, 扬手敛秋意。 独观暮色晚, 静候明月还。 自恃一毫墨, 亦敢赋桂香。 只待仲秋月, 斗胆撰文章。
    微光奇迹阅读 306评论 0 5
  • 中国有句古话,叫做慈不带兵、义不行贾。当领导的人,如果没有一份“铁石心肠”,以及强硬的手腕,是难以带领下级和单位的...
    Jonpch阅读 2,212评论 0 0