使用HTTP Client 遇到的问题

169 阅读3分钟
本文已参与「新人创作礼」活动,一起开启掘金创作之路。
使用HTTP Client 遇到的问题

问题

场景:几乎每个系统都有异步调用三方服务的功能,所负责的系统基于阻塞队列实现了一个消息队列,来调用三方服务。为了确保幂等性,队列是顺序消费。这就导致一个问题,一旦其中一个消息被阻塞,后面的消息就无法消费。当队列满时,也无法向队列中添加消息。

解决问题

首先想到的是,是不是消息队列实现的底层机制有问题。比如消息队列是通过while(true)轮训+sleep睡眠来实现生产者和消费者的持续存取数据。

既然消息被阻塞,是否是因为sleep(睡)过头了?后来一想,即使CPU进行了分片处理,也不至于睡眠那么就不会被唤醒。同时也不太可能是线程被interrupted掉。因为,如被interrupted掉了,整个队列就挂了,不会延迟后恢复正常。

在此处困惑了很久,看了消息队列实现的源码很久,没有突破。于是,与朋友探讨了一下,一句话提醒了我:可能不是睡眠的问题,而是生产者或消费者的问题。

于是仔细扒日志,发现还真是的:生产者向队列中丢了一次数据,持续很长时间没有再丢数据;消费者在生产者向队列丢数据之后几分钟还有消费的日志。很明显,生产者是被消费者阻塞了。

初步结论:消费者消费时间过长,导致队列满了,生产者向队列添加数据时被阻塞。

经验性猜测:消费者中有HTTP请求,HTTP请求可能长时间持有连接未释放。

问题根源

当分析定位到是HTTP请求的原因,就很好解决了。首先分析了日志,发现的确有一个HTTP请求,请求前打印了请求参数,但始终没看到返回结果的打印。扒日志终于看到,返回结果的日志是在15分钟之后打印出来的,日志内容为对方服务异常。

再看看代码,发现HTTP请求是基于HTTP Client实现的,而当初写这段代码的人并没有设置超时时间。为了保持业务脱敏,找了一段类似的代码:

public static String doPostWithJSON(String url, String json) throws Exception {
    CloseableHttpClient client = HttpClients.createDefault();
    HttpPost httpPost = new HttpPost(url);
    httpPost.setHeader("Content-Type","application/json;charset=UTF-8");
    StringEntity se = new StringEntity(json,  Charset.forName("UTF-8"));
    se.setContentType("application/json");
    httpPost.setEntity(se);
    CloseableHttpResponse response =  client.execute(httpPost);
    HttpEntity entity = response.getEntity();
    String result = EntityUtils.toString(entity, "UTF-8");
    return result;
}

像上述代码一样,设置了请求参数,但未指定HttpPost的超时时间。看似正常的代码,隐藏着一个巨大的坑,导致的结果就是HTTP请求一直等待。

对方设置的超时时间为15分钟,还给返回了结果。如果对方未设置超时时间,可能就一直等待了,当业务量比较大时,会导致灾难的发生!

HTTP Client的超时设置

找问题往往是最难,当找到问题时,解决起来就容易多了。HTTP Client的不同版本有不同的设置超时时间的方式,这也算是HTTP Client的又一大弊端吧,API版本变动太大。

4.3以后版本的配置:

RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(10000).build();
httpGet.setConfig(requestConfig);

其中,setConnectTimeout为连接超时时间,单位为毫秒。

setSocketTimeout为请求获取数据的超时时间,单位毫秒。如果访问一个接口,指定时间内无法返回数据,就直接放弃此次调用。