最近工作中使用的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. Git配置多账户

    一般情况下,公司代码company_repos/会存放在公司内部的gitlab上,个人代码privacy_repos/会放在github上.因此我们会有两个git账户:公司账号zioyi@campan ...

  2. CNN的Pytorch实现(LeNet)

    CNN的Pytorch实现(LeNet)   上次写了一篇CNN的详解,可是累坏了老僧我.写完后拿给朋友看,朋友说你这Pytorch的实现方式对于新人来讲会很不友好,然后反问我说里面所有的细节你都明白 ...

  3. pgsql基本介绍

    join on 与数学原理 pgsql切换数据库 直接输入 \C youdatabasename 即可 \d 表名 -- 得到表结构 select * from tablename查看表的数据 相信有 ...

  4. git所遇到的问题

    出现这种情况,或 ERROR: Repository not found. fatal: 无法读取远程仓库. 解决办法如下: 1.先输入$ git remote rm origin(删除关联的orig ...

  5. Mybatis-plus<二>通用CRUD,分页

    Mybatis-plus<二>通用CRUD,分页 与博客Mybatis-plus<一>为同一个Springboot项目. Demo GitHub下载地址:https://git ...

  6. 洛谷P1314 聪明的质监员 题解

    题目 聪明的质监员 题解 这道题和之前Sabotage G的那道题类似,都是用二分答案求解(这道题还要简单一些,不需要用数学推导二分条件,只需简单判断一下即可). 同时为了降低复杂度,肯定不能用暴力求 ...

  7. Linux新加磁盘并挂载到目录

    步骤:1.分区  ----> 2.格式化  ----> 3.挂载 一.查看当前情况 1. 2. 二.磁盘分区 fdisk /dev/sdb 1.输入n,表示添加一个新的分区 2. e ex ...

  8. C#开源类库SimpleTCP

    目录 简介 使用方法 实现客户端 实现服务端 总结 简介 工作中经常遇到需要实现TCP客户端或服务端的时候,如果每次都自己写会很麻烦且无聊,使用SuperSocket库又太大了.这时候就可以使用Sim ...

  9. word文档转成图片

    1:先把word文档转成pdf格式  这个是在word中转成pdf格式,保存好 2:再把pdf格式转成图片 在这个链接中打开https://smallpdf.com/cn/pdf-converter, ...

  10. vue el-transfer新增拖拽排序功能---sortablejs插件

    <template> <!-- target-order="unshift"必须设置,如果不设置的话后台穿的value值得顺序会被data重置 -  --> ...