最近工作中使用的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. cmd进入pycharm所创建的虚拟环境

    进入cmd命令,进入虚拟环境所在文件夹.(pycharm每创建一个新项目就会创建一个虚拟环境,位于项目下venv下Script) E:\virtualenv\crawl1\Scripts>act ...

  2. 辗转相除 求最大公约数!or 最小公倍数

    求最大公约数和最小公倍数的经典算法--辗转相除法描述如下: 若要求a,b两数的最大公约数和最小公倍数,令a为a.b中较大数,b为较小数,算法进一步流程: while(b不为0) { temp=a%b: ...

  3. Spring第一课:依赖注入DI(二)

    DI Dependency Injection ,依赖注入 is a :是一个,继承. has a:有一个,成员变量,依赖. class B { private A a;   //B类依赖A类 } 依 ...

  4. Java线程池工作原理

    前言 当项目中有频繁创建线程的场景时,往往会用到线程池来提高效率.所以,线程池在项目开发过程中的出场率是很高的. 那线程池是怎么工作的呢?它什么时候创建线程对象,如何保证线程安全... 什么时候创建线 ...

  5. Qt5之事件学习总结

    首先要明白一个概念,事件和信号并不一样,比如单击一下鼠标,就会产生鼠标事件(QMouseEvent),是对这个动作的描述,而因为按钮被按下了,按钮会发出clicked()的单击信号(是按钮控件产生的) ...

  6. RHCS集群架构之mysql及共享存储iscsi

    server1 172.25.7.1(配置Nginx.ricci和luci) server2 172.25.7.2(Apache.iscsi) server3 172.25.7.3(Apache) s ...

  7. java变量类型和常量类型

    变量类型 局部变量 实例变量 类变量 public class 变量类型 { //属性:变量 //必须先定义再使用,并初始化 //布尔型:默认值为false //3. 类变量(静态变量) static ...

  8. OpenCV 之 透视 n 点问题

    透视 n 点问题,源自相机标定,是计算机视觉的经典问题,广泛应用在机器人定位.SLAM.AR/VR.摄影测量等领域 1  PnP 问题 1.1  定义 已知:相机的内参和畸变系数:世界坐标系中,n 个 ...

  9. MySQL——获取元数据

    ---------------------------------------------------------------------------------------------------- ...

  10. Robot Framework(6)- BuiltIn 测试库常用的关键字列表

    如果你还想从头学起Robot Framework,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1770899.html 前言 所有关键字 ...