前言
作为软件开发者,咱们晓得所有看似失常的零碎,不知埋藏着多少坑。明天跟大家分享一个实战过程中遇到的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