共计 2373 个字符,预计需要花费 6 分钟才能阅读完成。
前言
作为软件开发者,咱们晓得所有看似失常的零碎,不知埋藏着多少坑。明天跟大家分享一个实战过程中遇到的 HTTP Client 使用不当导致的坑。
笔者通过问题的表象一路追踪上来,最终找到导致问题的本源: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 版本的配置:
httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,10000);
httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT,10000);
4.3 当前版本的配置:
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(10000).build();
httpGet.setConfig(requestConfig);
其中,setConnectTimeout 为连贯超时工夫,单位为毫秒。
setSocketTimeout 为申请获取数据的超时工夫,单位毫秒。如果拜访一个接口,指定工夫内无奈返回数据,就间接放弃此次调用。
其余版本的应用,倡议参考一下相干的 API 阐明了。
小结
在实际的过程中,咱们常常会遇到一些莫名其妙的问题,而这些问题导致的起因可能是实践经验有余,也可能是对 API 的应用不够纯熟。而教训来自哪里?来自像本文这样一个个问题的排查、总结、积攒而取得。
比方,读完本篇文章,你曾经晓得了:当应用 HTTP Client 时肯定要设置超时工夫。同时,你必定也能触类旁通,但凡在 HTTP 调用时都须要考虑一下超时工夫及对应的异样解决。
博主简介:《SpringBoot 技术底细》技术图书作者,热爱钻研技术,写技术干货文章。
公众号:「程序新视界」,博主的公众号,欢送关注~
技术交换:请分割博主微信号:zhuan2quan