最近工作中使用的HttpClient工具遇到的Connection Reset异常。在客户端和服务端配置不对的时候容易出现问题,下面就是记录一下如何解决这个问题的过程。

出现Connection Reset的原因

1.客户端在读取数据,服务端不再发送新数据(服务器主动关闭了数据)

为什么会出现服务端主动关闭连接?

经过排查线上服务器配置,发现单一个连接空闲时间超过60s,服务器就会将其关闭。如果刚好客户端在使用该连接则客户端就会收到来自服务端的连接复位标志

既然明白了服务端关闭的连接的原因,那为什么客户端会使用空闲时间为60s的连接呢?

排除了HttpClient的配置后发现,项目中的HttpClient使用连接池,虽然设置了池的最大连接数,但是没有配置空闲连接驱逐器(IdleConnectionEvictor)。到这里原因就已经很明朗了,就是httpClient的配置有问题。

解决思路:

如果说服务端会吧空闲时间超过60s的空闲连接关闭掉,导致了connection reset 异常。要解决这个问题,那只要客户端在服务器关闭连接之前把连接关闭掉那就不会出现了。所以按着这个思路我对httpClient的配置进行了修改。

解决方案1:

为HttpClient添加空闲连接驱逐器配置

新加了evictIdleConnections(40, TimeUnit.SECONDS)配置

HttpClients
.custom()
// 默认请求配置
.setDefaultRequestConfig(customRequestConfig())
// 自定义连接管理器
.setConnectionManager(poolingHttpClientConnectionManager())
// 删除空闲连接时间
.evictIdleConnections(40, TimeUnit.SECONDS)
.disableAutomaticRetries(); // 关闭自动重试

正常情况下到这里问题就解决了,但是现实是线上再次出现了Connection Reset异常。继续排查...

思考:虽然更新配置后再次出现“连接重置”异常,不过出现频率相较于没改之前还是要低不少。所以改的配置还有用的,肯定是什么地方没有配好。为了一探究竟,我翻阅了HttpClient关于IdleConnectionEvictor驱逐器的源码发现了问题所在。

源码解读:

源码1:

// org.apache.http.impl.client.HttpClientBuilder
public class HttpClientBuilder {
// .....省略无关代码....
// 关注build方法,这这个方法里面启动了空闲连接驱逐器
public CloseableHttpClient build() {
// 。。。。省略代码。。。。
if (!this.connManagerShared) {
if (closeablesCopy == null) {
closeablesCopy = new ArrayList<Closeable>(1);
}
final HttpClientConnectionManager cm = connManagerCopy; if (evictExpiredConnections || evictIdleConnections) {
// 在这里实例化了IdleConnectionEvictor。maxIdleTime和maxIdleTimeUnit就是我们在配置httpclient时
// 传入的 40 和 TimeUnit.SECONDS
final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(cm,
maxIdleTime > 0 ? maxIdleTime : 10, maxIdleTimeUnit != null ? maxIdleTimeUnit : TimeUnit.SECONDS,
maxIdleTime, maxIdleTimeUnit);
closeablesCopy.add(new Closeable() { @Override
public void close() throws IOException {
connectionEvictor.shutdown();
try {
connectionEvictor.awaitTermination(1L, TimeUnit.SECONDS);
} catch (final InterruptedException interrupted) {
Thread.currentThread().interrupt();
}
} });
// 调用start()发放启动了线程驱逐器
connectionEvictor.start();
}
closeablesCopy.add(new Closeable() { @Override
public void close() throws IOException {
cm.shutdown();
} });
}
// 。。。。省略无关代码。。。。。
}
}
  1. evictIdleConnections(40, TimeUnit.SECONDS)配置的参数在HttpClientBuilder.builder方法中用于实例化IdleConnectionEvictor对象的构造参数

  2. 调用了connectionEvictor.start()方法启动了线程驱逐器

源码2:

// org.apache.http.impl.client.IdleConnectionEvictor
public final class IdleConnectionEvictor {
// 。。。。省略无关代码。。。。
// HttpClientBuilder.build()内实例化IdleConnectionEvictor调用了该构造方法
public IdleConnectionEvictor(
final HttpClientConnectionManager connectionManager,
final long sleepTime, final TimeUnit sleepTimeUnit,
final long maxIdleTime, final TimeUnit maxIdleTimeUnit) {
this(connectionManager, null, sleepTime, sleepTimeUnit, maxIdleTime, maxIdleTimeUnit);
}
// 。。。。省略无关代码。。。。
}
关键的参数列表
  1. sleepTime:延时检查时间
  2. maxIdleTime:最多空闲时间

结合源码1和源码2,可以看到在构造IdleConnectionEvictorsleepTimemaxIdleTime为同一个值40秒,在这里还看不出什么问题,继续。

源码3:

// org.apache.http.impl.client.IdleConnectionEvictor
public final class IdleConnectionEvictor {
// 省略无关代码
// 重载的构造方法
public IdleConnectionEvictor(
final HttpClientConnectionManager connectionManager,
final ThreadFactory threadFactory,
final long sleepTime, final TimeUnit sleepTimeUnit,
final long maxIdleTime, final TimeUnit maxIdleTimeUnit) {
this.connectionManager = Args.notNull(connectionManager, "Connection manager");
this.threadFactory = threadFactory != null ? threadFactory : new DefaultThreadFactory();
this.sleepTimeMs = sleepTimeUnit != null ? sleepTimeUnit.toMillis(sleepTime) : sleepTime;
this.maxIdleTimeMs = maxIdleTimeUnit != null ? maxIdleTimeUnit.toMillis(maxIdleTime) : maxIdleTime;
// 使用threadFactory线程构造器构造了一个守护线程
this.thread = this.threadFactory.newThread(new Runnable() {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
// 挂起线程时间是我们传入的时间40秒
Thread.sleep(sleepTimeMs);
// 执行检查代码,关闭过期连接
connectionManager.closeExpiredConnections();
if (maxIdleTimeMs > 0) {
// 关闭超过空闲时间的空闲连接,参数传入我们配置的40秒
connectionManager.closeIdleConnections(maxIdleTimeMs, TimeUnit.MILLISECONDS);
}
}
} catch (final Exception ex) {
exception = ex;
} }
});
} // HttpClientBuilder中调用的start()方法
public void start() {
thread.start();
}
}

通过源码3我们可以看到,检查线程的执行周期时间和最大过期时间都是我们传入的40秒。在这里停顿一下思考一下,服务器的空闲连接关闭时间是60s,我们配置的时间是40s,那这样配置会不有出现什么问题?

线程相隔40s执行一下回收任务,那在不执行回收任务的停止的40秒里面出了connection reset异常了怎么吧?问题就明了。

问题复现时序:
  1. 00:00:00 --- 启动IdleConnectionEvictor.start(),挂起检查线程,不执行检查代码
  2. 00:00:10 --- 10秒后的连接池新建了一个连接
  3. 00:00:12 --- 连接耗时2s,用完后返回线程池,假设之后都没有再被使用了
  4. 00:00:40 --- 第一次sleep挂起时间到期,执行检查任务。发现没有过期连接,下一次回收任务发生在 00:01:20
  5. 00:01:12 --- 这时恰好客户端使用那个空闲的连接,服务端关闭了该连接。在这里爆发了connection reset 异常
  6. 00:01:20 --- 第二次sleep挂起时间到期,执行检查任务。

结论:

服务端空闲连接关闭时间是60s,我们客户端配置的最大空闲时间值应该小于30s才能避免这个问题

解决方案2:

在解决方案1的基础上,把40s时间改为20s,顺利解决了该问题。

HttpClient遭遇Connection Reset异常,如何正确配置?的更多相关文章

  1. 一次SocketException:Connection reset 异常排查

    问题描述 上一期的需求上线之后,线上多了一个异常:Connection reset.如下: [2017-03-22 00:45:00 ERROR] [creativeAuditTaskSchedule ...

  2. 解决Jedis链接报超时异常和connection reset异常的方法

    一.链接池配置 <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig" ...

  3. 异常记录 Connection reset

    连接重置Connection reset 异常java.net.SocketException: Connection reset 详细信息 java.net.SocketException: Con ...

  4. HttpClient : java.net.SocketException: Connection reset

    1. 问题排查 httpclient : 4.5.5 排查过程 : 一次SocketException:Connection reset 异常排查 主要原因 : 调用 http 请求时发生了 Sock ...

  5. 高并发下载tomcat下的文件时,发生java.net.SocketException: Connection reset解决方案

    (1)问题产生:使用500个线程并发下载tomcat工程中的一个文件时,服务器出现java.net.SocketException: Connection reset异常, 客户端出现connect ...

  6. Netty 中 IOException: Connection reset by peer 与 java.nio.channels.ClosedChannelException: null

    最近发现系统中出现了很多 IOException: Connection reset by peer 与 ClosedChannelException: null 深入看了看代码, 做了些测试, 发现 ...

  7. 记一次httpclient Connection reset问题定位

    问题:某业务系统在运行一段时间后,某个API一定概率偶现Connection reset现象. 问题定位: 首先想到的是要本地复现出这个问题,但一直复现不出来. 1.根据线上问题相关日志判断应该是有部 ...

  8. java.sql.SQLException: Io 异常: Connection reset

    当数据库连接池中的连接被创建而长时间不使用的情况下,该连接会自动回收并失效,但客户端并不知道,在进行数据库操作时仍然使用的是无效的数据库连接,这样,就导致客户端程序报“ java.sql.SQLExc ...

  9. spring+ibatis问题1—— 程序报错:java.sql.SQLException: Io 异常: Connection reset by peer, socket write error; ”或“java.sql.SQLException 关闭的连接”异常

    转自:http://blog.sina.com.cn/s/blog_1549fb0710102whz2.html spring+ibatis程序测试时报错:java.sql.SQLException: ...

随机推荐

  1. ES6——简单的多态

    简单的多态 多态: 1.同一个接口,在不同情况下做不一样的事情:相同的接口,不同的表现: 2.接口本身只是一组定义,实现都是子类里面:需要子类去实现的方法(子类只需重写与父类同名的方法,即可达到覆盖的 ...

  2. C# ArrayPool 源码解读之 byte[] 池化

    一:背景 1. 讲故事最近在分析一个 dump 的过程中发现其在 gen2 和 LOH 上有不少size较大的free,仔细看了下,这些free生前大多都是模板引擎生成的html片段的byte[]数组 ...

  3. redhat9 linux 网卡无法激活排障

    先查看自己的系统版本! [root@localhost root]# uname -a Linux localhost 2.4.20-8 #1 Thu Mar 13 17:54:28 EST 2003 ...

  4. 《手把手教你》系列技巧篇(二十三)-java+ selenium自动化测试-webdriver处理浏览器多窗口切换下卷(详细教程)

    1.简介 上一篇讲解和分享了如何获取浏览器窗口的句柄,那么今天这一篇就是讲解获取后我们要做什么,就是利用获取的句柄进行浏览器窗口的切换来分别定位不同页面中的元素进行操作. 2.为什么要切换窗口? Se ...

  5. sublime text 3 中文排序插件

    ST3 的排序不支持中文按拼音排序,所以需要搞一个插件来支持这一特性 pypinyin 这个库可以把中文转成拼音,可惜不支持 python3.3,而 ST3 内置的 python 就是 3.3 我系统 ...

  6. WinForm控件常用设置(转)

    本来想自己整理一份,但找到了一份挺全的,就直接用到直接找吧 A0 ---- 通用A1 ---- Form 类A2 ---- Control 类A3 ---- MessageBox 类A4 ---- B ...

  7. vue+element+echarts柱状图+列表

    前端由vue+element搭建框架,引入vue和element的index.js和css就可以写页面: 页面和js可以echarts官网实例看下都是有的,主要看下如何动态赋值: 柱状图和列表: &l ...

  8. wrap()包裹被选元素的内容

    <!doctype html><html><head><meta charset="utf-8"><title>修改代码 ...

  9. 如何将 Ubuntu 版本升级到新版本

    @ 目录 0.将 Ubuntu 版本升级到新版本的注意事项 1.以图形方式升级到 Ubuntu 20.04(适用于桌面用户) 2.使用命令行升级到 Ubuntu 21.10 本教程通过从 Ubuntu ...

  10. CodeForce-702C Cellular Network(查找)

    Cellular Network CodeForces - 702C 给定 n (城市数量) 和 m (灯塔数量): 给定 a1~an 城市坐标: 给定 b1~bm 灯塔坐标: 求出灯塔照亮的最小半径 ...