最近工作中使用的HttpClient工具遇到的Connection Reset异常。在客户端和服务端配置不对的时候容易出现问题,下面就是记录一下如何解决这个问题的过程。
1.客户端在读取数据,服务端不再发送新数据(服务器主动关闭了连接)
经过排查线上服务器配置,发现当一个连接空闲时间超过60s,服务器就会将其关闭。如果刚好客户端在使用该连接则客户端就会收到来自服务端的连接复位标志通知
排查了HttpClient的配置后发现,项目中的HttpClient使用连接池,虽然设置了池的最大连接数,但是没有配置空闲连接驱逐器(IdleConnectionEvictor)。到这里原因就已经很明朗了,就是httpClient的配置有问题。
如果说服务端会吧空闲时间超过60s的空闲连接关闭掉,导致了connection reset 异常。要解决这个问题,那只要客户端在服务器关闭连接之前把连接关闭掉那就不会出现了。所以按着这个思路我对httpClient的配置进行了修改。
为HttpClient添加空闲连接驱逐器配置
新加了<code>evictIdleConnections(40, TimeUnit.SECONDS)</code>配置
正常情况下到这里问题就解决了,但是现实是线上再次出现了Connection Reset异常。继续排查...
思考:虽然更新配置后再次出现“连接重置”异常,不过出现频率相较于没改之前还是要低不少。所以改的配置还有用的,肯定是什么地方没有配好。为了一探究竟,查了HttpClient关于<code>IdleConnectionEvictor</code>驱逐器的源码发现了问题所在。
<code>evictIdleConnections(40, TimeUnit.SECONDS)</code>配置的参数在<code>HttpClientBuilder.builder</code>方法中用于实例化<code>IdleConnectionEvictor</code>对象的构造参数
调用了<code>connectionEvictor.start()</code>方法启动了线程驱逐器
sleepTime:延时检查时间
maxIdleTime:最多空闲时间
结合源码1和源码2,可以看到在构造<code>IdleConnectionEvictor</code>时<code>sleepTime</code>和<code>maxIdleTime</code>为同一个值40秒,在这里还看不出什么问题,继续。
通过源码3我们可以看到,检查线程的执行周期时间和最大过期时间都是我们传入的40秒。在这里停顿一下思考一下,服务器的空闲连接关闭时间是60s,我们配置的时间是40s,那这样配置会不有出现什么问题?
线程相隔40s执行一下回收任务,相当于80秒的的周期内会做两次回收动作。但是60s在其中最多只能回收掉一次,还是可能存在回收不掉的情况,在不执行回收任务停止的40秒里面出了connection reset异常了怎么吧?问题就明了了。
00:00:00 --- 启动<code>IdleConnectionEvictor.start()</code>,挂起检查线程,不执行检查代码
00:00:10 --- 10秒后的连接池新建了一个连接
00:00:12 --- 连接耗时2s,用完后返回线程池,假设之后都没有再被使用了
00:00:40 --- 第一次sleep挂起时间到期,执行检查任务。发现没有过期连接,下一次回收任务发生在 00:01:20
00:01:12 --- 这时恰好客户端使用那个空闲的连接,服务端关闭了该连接。在这里发生了connection reset 异常
00:01:20 --- 第二次sleep挂起时间到期,执行检查任务。
服务端空闲连接关闭时间是60s,我们客户端配置的最大空闲时间值应该小于30s才能避免这个问题
在解决方案1的基础上,把40s时间改为20s,顺利解决了该问题。
沐风的原创文章