最近工作中使用的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,可以看到在构造IdleConnectionEvictor时sleepTime和maxIdleTime为同一个值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. CrackMe-Cycle

    转载自 OllyDBG入门教程   PS:自己逆在 最后的时候总会崩,不知道为什么. 我们输入用户名 CCDebuger,序列号 78787878,点上面那个"Check"按钮,呵 ...

  2. 【转】时冲的CSDN:Linux系统各个目录的作用

    请各位移步原文链接:时冲的CSDN 以下仅用于个人梳理,排版方便阅读记忆(原文更优): from my typora: 文章目录 Linux文件系统 LINUX有四种基本文件系统类型: 1.普通文件: ...

  3. MAC下Jetbrains编译器无法打开问题解决

    这段时间不知道怎么回事,每次打开Rider必定闪退,毫无头绪,只好暂时放弃使用Rider,试用了一段时间Visual Studio. 可惜...虽然大学时候觉得VS天下第一,但是用惯了JB的编译器,再 ...

  4. spring-data-redis 动态切换数据源

    最近遇到了一个麻烦的需求,我们需要一个微服务应用同时访问两个不同的 Redis 集群.一般我们不会这么使用 Redis,但是这两个 Redis 本来是不同业务集群,现在需要一个微服务同时访问. 其实我 ...

  5. tf.app.run() 运行结束时,报错:SystemExit exception: no description

    环境:Python3.6.6 + tensorflow-gpu 源码如下: import tensorflow as tf def main(): print("hello tf.app.r ...

  6. openresty(nginx) 配置 http与https使用同一个端口,禁止 IP 直接访问

    准备好工作目录 mkdir work cd work mkdir conf logs 准备好 conf/nginx.conf 配置文件, 把 your.domain 换成你自己的域名 user abc ...

  7. JVM详解(四)——类加载过程

    类加载过程 https://www.cnblogs.com/aqsaycode/p/14885023.html

  8. C# ASP.NET RAZOR 链接SQLSERVER

    @using System.Data.SqlClient; @using System.Data;//必须引用 <html> <body> <h1>Learn Sq ...

  9. 羽夏笔记——Win32(非WinAPI)

    写在前面   本笔记是由本人独自整理出来的,图片来源于网络.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章有帮助你 ...

  10. charles 抓包修改app页面数据

    1,首先给手机安装Charles证书,安装官方的来,在无线网配置项目,输入手动代理地址,后开启飞行模式刷新网络, 2,在浏览器输入chls.pro/ssl 下载并安装证书,此时电脑端charles 会 ...