使用HTTPclient保持长连接
httpclient保持长连接
首先解释一下什么是长连接
当我们向一台服务器发起请求时,我们需要和对方建立一条通道,去传输数据,所谓的短连接,就是说我们建立起了通道,然后在传输完数据,就把通道摧毁,下次需要的时候再重新去建立通道。
长连接呢,就是指,我们建立了一条通道,传递完数据后,先不摧毁,下次如果还需要传输数据,就复用这条通道。
因为通道的建立是需要花费时间的,所以长连接的优势就在于响应速度快,但是服务器压力大,因为同时有很多人在向服务器建立通道,即便有些通道已经传输完数据了,由于长连接的原因,通道也不会被摧毁;短连接呢,则是,响应速度慢,服务器压力小。
由于现在更多的是强调用户的体验,所以长连接目前是最常见的。
如何在java中实现一个长连接呢
其实很简单,只需要在请求的请求头中加入特定的参数 :“connection”:"keep-alive"即可。这样如果对方支持长连接的话,那么这个连接就会保持长连接了。
问题的关键就来了,在一次压测某个https请求响应速度的代码中,我发现了,当对方响应数据为null,也就是responsebody中带的数据为null时,响应速度特别快,大概在5ms左右,但是一旦对方返回了响应数据,本次响应就可能达到了20ms。
然后请运维同事抓包,发现每次连接,都会耗费时间在用户认证上,其实也就是从某个方面反应出,每次都是新建立了一个连接。
httppost httppost = new httppost("xxxxx"); httppost.addheader("connection", "keep-alive"); closeablehttpclient httpclient = null; for(int i =0 ;i<5000;i++){ long t1 = system.currenttimemillis(); closeablehttpresponse response = httpclient.execute(httppost); long t2 = system.currenttimemillis(); }
上了一段简单的代码,表示一下大概的逻辑,实际的压测代码还包括了线程池,连接池的完整参数等等,如果需要的可以留言或者翻看本人的其他的博客,有写线程池和连接池。
就是这样一段代码,导致每次连接都是新建立的连接,那么原因是什么呢,我们先上代码:
for (int i = 0; i<5000;i++){ long t1 = system.currenttimemillis(); closeablehttpresponse response = httpclient.execute(httppost); if(null != response.getentity()){ entityutils.consume(response.getentity()); } long t2 = system.currenttimemillis(); }
我们只需要简单加上三行代码,就可以解决这个问题了,这是为什么呢,让我们点进去源码看一下
这么一看, 其实这个方法也并没有做什么,只是简单的取到了流去关闭,为什么就保持长连接了呢。
后来仔细读http连接的原理才得知,当一个连接建立,响应数据时,会封装closeablehttpresponse这个对象里面,其中的entity对象就是包含着响应体的数据,我们需要用流去获取。如果你不去获取,那么这个数据就会存在于这个对象中,连接池就会认为,这个通道里有未处理的数据,然后它不会去复用这个通道,而是选择重建一个通道。这就完美解释了为什么压测时,对方返回null时,响应速度特别快,而携带返回数据时,响应速度特别慢了。
那么再仔细想想,为什么我们平常不知道这个知识点,却从来没有报过错呢,那是因为正常情况下,我们都是需要会对response做处理,比如string responsecontent = entityutils.tostring(response.getentity(), standardcharsets.utf_8); 类似这种,我们点进源码看,其实也是取到了流,并做了关闭操作。平常还是要多阅读源码,想想源码。
httpclient因为保持永久长连接造成连接吊死的问题
httpclient使用了连接池,如果没有设置keep-alive策略,poolinghttpclientconnectionmanager会默认使用永久连接。
最近在调用京东api时,发现一个请求开始是可以获取到数据的,但隔了两分钟后再请求就会出现read timeout异常。
对比请求成功和请求失败的日志后发现,请求成功的有以下日志“connection: keep-alive”,“connection can be kept alive indefinitely”;但请求失败的却打印“shutdown connection”,“connection discarded”。
每次失败后再请求都会成功。因此推测中应该是对方服务器端禁止长连接,当连接到达一定时间会就会断开。
后来上网找到keep-alive策略的代码。
添加策略后,问题解决
connectionkeepalivestrategy keepalivestrategy = new connectionkeepalivestrategy() { @override public long getkeepaliveduration(httpresponse response, httpcontext context) { headerelementiterator it = new basicheaderelementiterator(response.headeriterator(http.conn_keep_alive)); while (it.hasnext()) { headerelement he = it.nextelement(); string param = he.getname(); string value = he.getvalue(); if (value != null && param.equalsignorecase("timeout")) { try { return long.parselong(value) * 1000; } catch (numberformatexception ignore) { } } } httphost target = (httphost) context.getattribute(httpclientcontext.http_target_host); if ("bizapi.jd.com ".equalsignorecase(target.gethostname())) { return 60 * 1000; } else { return 300 * 1000; } closeablehttpclient httpclient = httpclientbuilder.setconnectionmanager(pollingconnectionmanager) .setkeepalivestrategy(keepalivestrategy).setdefaultrequestconfig(defaultrequestconfig).build();
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。
上一篇: 年薪50W阿里架构师,总结了100个JS高频面试题,不愧是高薪大佬
下一篇: Java并发容器介绍