首页 > 其他分享 >Connection reset原因分析和解决方案

Connection reset原因分析和解决方案

时间:2022-12-12 18:11:42浏览次数:68  
标签:reset 解决方案 TCP Connection 服务器 连接 客户端

现象描述

在使用 HttpClient 调用后台 resetful 服务时,“Connection reset” 是一个比较常见的问题,有同学跟我私信说被这个问题困扰很久了,今天就来分析下,希望能帮到大家。例如我们线上的网关日志就会抛该错误:

 

从日志中可以看到是 Socket 套接字在 read 数据时抛出了该错误。

原因分析

导致 “Connection reset” 的原因是服务器端因为某种原因关闭了 Connection,而客户端依然在读写数据,此时服务器会返回复位标志 “RST”,然后此时客户端就会提示 “java.net.SocketException: Connection reset”。

TCP回顾

可能有同学对复位标志 “RST” 还不太了解,这里简单解释一下:

TCP 建立连接时需要三次握手,在释放连接需要四次挥手;例如三次握手的过程如下:

  1. 第一次握手:客户端发送 syn 包(syn=j)到服务器,并进入 SYN_SENT 状态,等待服务器确认;
  2. 第二次握手:服务器收到 syn 包,并会确认客户的 SYN(ack=j+1),同时自己也发送一个 SYN 包(syn=k),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态;
  3. 第三次握手:客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK (ack=k+1),此包发送完毕,客户端和服务器进入 ESTABLISHED(TCP 连接成功)状态,完成三次握手。

可以看到握手时会在客户端和服务器之间传递一些 TCP 头信息,比如 ACK 标志、SYN 标志以及挥手时的 FIN 标志等。

除了以上这些常见的标志头信息,还有另外一些标志头信息,比如推标志 PSH、复位标志 RST 等。其中复位标志 RST 的作用就是 “复位相应的 TCP 连接”。

TCP 连接和释放时还有许多细节,比如半连接状态、半关闭状态等。详情请参考这方面的巨著《TCP/IP 详解》和《UNIX 网络编程》

 原因阐述

前面说到出现 “Connection reset” 的原因是服务器关闭了 Connection [调用了 Socket.close () 方法]。大家可能有疑问了:服务器关闭了 Connection 为什么会返回 “RST” 而不是返回 “FIN” 标志。原因在于 Socket.close () 方法的语义和 TCP 的 “FIN” 标志语义不一样:发送 TCP 的 “FIN” 标志表示我不再发送数据了,而 Socket.close () 表示我不在发送也不接受数据了。问题就出在 “我不接受数据” 上,如果此时客户端还往服务器发送数据,服务器内核接收到数据,但是发现此时 Socket 已经 close 了,则会返回 “RST” 标志给客户端。当然,此时客户端就会提示:“Connection reset”。详细说明可以参考 oracle 的有关文档:http://docs.oracle.com/javase/1.5.0/docs/guide/net/articles/connection_release.html

另一个可能导致的 “Connection reset” 的原因是服务器设置了 Socket.setLinger (true, 0)。但我检查过线上的 tomcat 配置,是没有使用该设置的,而且线上的服务器都使用了 nginx 进行反向代理,所以并不是该原因导致的。关于该原因上面的 oracle 文档也谈到了并给出了解释。

此外啰嗦一下,另外还有一种比较常见的错误 “Connection reset by peer”,该错误和 “Connection reset” 是有区别的:

  • 服务器返回了 “RST” 时,如果此时客户端正在从 Socket 套接字的输出流中读数据则会提示 Connection reset”;

  • 服务器返回了 “RST” 时,如果此时客户端正在往 Socket 套接字的输入流中写数据则会提示 “Connection reset by peer”。

“Connection reset by peer” 如下图所示:

 

问题解决

前面谈到了导致 “Connection reset” 的原因,而具体的解决方案有如下几种:

  • 出错了重试;

  • 客户端和服务器统一使用 TCP 长连接;

  • 客户端和服务器统一使用 TCP 短连接。

出错重试

首先是出错了重试:这种方案可以简单防止 “Connection reset” 错误,然后如果服务不是 “幂等” 的则不能使用该方法;比如提交订单操作就不是幂等的,如果使用重试则可能造成重复提单。

统一建立长连接

然后是客户端和服务器统一使用 TCP 长连接:客户端使用 TCP 长连接很容易配置(直接设置 HttpClient 就好),而服务器配置长连接就比较麻烦了,就拿 tomcat 来说,需要设置 tomcat 的 maxKeepAliveRequests、connectionTimeout 等参数。另外如果使用了 nginx 进行反向代理或负载均衡,此时也需要配置 nginx 以支持长连接(nginx 默认是对客户端使用长连接,对服务器使用短连接)。

使用长连接可以避免每次建立 TCP 连接的三次握手而节约一定的时间,但是我这边由于是内网,客户端和服务器的 3 次握手很快,大约只需 1ms。ping 一下大约 0.93ms(一次往返);三次握手也是一次往返(第三次握手不用返回)。根据 80/20 原理,1ms 可以忽略不计;又考虑到长连接的扩展性不如短连接好、修改 nginx 和 tomcat 的配置代价很大(所有后台服务都需要修改);所以这里并没有使用长连接。ping 服务器的时间如下图:

 

统一建立短连接

最后的解决方案是客户端和服务器统一使用 TCP 短连接:我这边正是这么干的,而使用短连接既不用改 nginx 配置,也不用改 tomcat 配置,只需在使用 HttpClient 时使用 http1.0 协议并增加 http 请求的 header 信息(Connection: Close),源码如下:

1 httpGet.setProtocolVersion(HttpVersion.HTTP_1_0);
2 httpGet.addHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_CLOSE);

最后再补充几句,虽然对于每次请求 TCP 长连接只能节约大约 1ms 的时间,但是具体是使用长连接还是短连接还是要衡量下,比如你的服务每天的 pv 是 1 亿,那么使用长连接节约的总时间为:

1亿*1ms=10^8*1ms=10^5*1s=10^5*1h/3600≈27.78h

神奇的是,亿万级 pv 的服务使用长连接一天内节约的总时间为 27.78 小时(竟然大于一天)。

所以使用长连接还是短连接大家需要根据自己的服务访问量、扩展性等因素衡量下。但是一定要注意:服务器和客户端的连接一定要保持一致,要么都是长连接,要么都是短连接。

知识拓展

稍微补充下,有时改成短链接不一定能完全解决该问题,因为在http请求发送和返回响应肯定是需要时间的,在服务器高并发环境下很容易触发安全策略或者其他策略导致链接强制断开(比如服务器限制了单个ip连接数),在不用考虑幂等问题时,可以采用重试机制。

这里使用okhttp4使用拦截器重试,这样能大概率解决所有问题

1 private static OkHttpClient okHttpClient =  new OkHttpClient.Builder()
2     .connectTimeout(0, TimeUnit.SECONDS)
3     .readTimeout(0, TimeUnit.SECONDS)
4     .retryOnConnectionFailure(true)
5     .addInterceptor(myOkHttpRetryInterceptor)
6     .build();

其中myOkHttpRetryInterceptor可以仿照这个博客编写

 1 package com.gomefinance.esign.httpretry;
 2  
 3 import lombok.extern.slf4j.Slf4j;
 4 import okhttp3.Interceptor;
 5 import okhttp3.Request;
 6 import okhttp3.Response;
 7  
 8 import java.io.IOException;
 9 import java.io.InterruptedIOException;
10 import java.util.List;
11  
12 /**
13  * User: Administrator
14  * Date: 2017/9/19
15  * Description:
16  */
17  
18 @Slf4j
19 public class MyOkHttpRetryInterceptor implements Interceptor {
20     public int executionCount;//最大重试次数
21     private long retryInterval;//重试的间隔
22     MyOkHttpRetryInterceptor(Builder builder) {
23         this.executionCount = builder.executionCount;
24         this.retryInterval = builder.retryInterval;
25     }
26  
27  
28  
29     @Override
30     public Response intercept(Chain chain) throws IOException {
31         Request request = chain.request();
32         Response response = doRequest(chain, request);
33         int retryNum = 0;
34         while ((response == null || !response.isSuccessful()) && retryNum <= executionCount) {
35             log.info("intercept Request is not successful - {}",retryNum);
36             final long nextInterval = getRetryInterval();
37             try {
38                 log.info("Wait for {}",nextInterval);
39                 Thread.sleep(nextInterval);
40             } catch (final InterruptedException e) {
41                 Thread.currentThread().interrupt();
42                 throw new InterruptedIOException();
43             }
44             retryNum++;
45             // retry the request
46             response = doRequest(chain, request);
47         }
48         return response;
49     }
50  
51     private Response doRequest(Chain chain, Request request) {
52         Response response = null;
53         try {
54             response = chain.proceed(request);
55         } catch (Exception e) {
56         }
57         return response;
58     }
59  
60     /**
61      * retry间隔时间
62      */
63     public long getRetryInterval() {
64         return this.retryInterval;
65     }
66  
67     public static final class Builder {
68         private int executionCount;
69         private long retryInterval;
70         public Builder() {
71             executionCount = 3;
72             retryInterval = 1000;
73         }
74  
75         public MyOkHttpRetryInterceptor.Builder executionCount(int executionCount){
76             this.executionCount =executionCount;
77             return this;
78         }
79  
80         public MyOkHttpRetryInterceptor.Builder retryInterval(long retryInterval){
81             this.retryInterval =retryInterval;
82             return this;
83         }
84         public MyOkHttpRetryInterceptor build() {
85             return new MyOkHttpRetryInterceptor(this);
86         }
87     }
88  
89 }

 

标签:reset,解决方案,TCP,Connection,服务器,连接,客户端
From: https://www.cnblogs.com/fnlingnzb-learner/p/16976807.html

相关文章

  • 医院变电所运维云平台解决方案
    安科瑞陈盼1、概述  变电所运维云平台可以看做是电力监控系统的网络应用延伸,变电所运维云平台通过互联网,电力运维人员通过手机可以随时随地了解医院配电系统的运行情况,做......
  • 医院能耗管理系统解决方案
    安科瑞陈盼1、概述  随着碳达峰、碳中和成为政府工作主要任务,医院作为能耗密集,用能情况较为复杂的大型建筑,有效的降低能源消耗,减少能源成本,避免用能过程中的“跑冒滴漏”......
  • 医院电力监控解决方案
    安科瑞陈盼1、概述  电力监控系统实现对变压器、柴油发电机、断路器以及其它重要设备进行监视、测量、记录、报警等功能,并与保护设备和远方控制中心及其他设备通信,实时掌......
  • 图扑虚拟现实解决方案,实现 VR 数智机房
    前言如今,虚拟现实技术作为连接虚拟世界和现实世界的桥梁,正加速各领域应用形成新场景、新模式、新业态。效果展示图扑软件基于自研可视化引擎HTforWeb搭建的VR数据......
  • 开源-BDCI2018面向电信行业存量用户的智能套餐个性化匹配模型Top1解决方案和代码...
    本人经过作者同意,公布了:BDCI2018面向电信行业存量用户的智能套餐个性化匹配模型数据竞赛top1解决方案和代码。该方案利用已有的用户属性(如个人基本信息、用户画像信息等)、......
  • 高速公路电力监控解决方案
    安科瑞陈盼1、概述  近年来,我国的高速公路发展非常迅速,已形成遍布全国的高速公路网,它在对国家经济发展作出突出贡献的同时,也对高速公路管理及运营的自动化、智能化提出了......
  • 高速公路变电所运维云平台解决方案
    安科瑞陈盼1、概述  高速公路监控中心主要任务为确保高速公路的各个子系统及操控设备能正常运转,并在发生事故时能迅速反应处理,因此高速公路监控中心就是整个高速公路安全......
  • 高速公路能耗管理系统解决方案
    安科瑞陈盼1、概述  对于高速公路的而言,供配电系统是重要的电力保障,在高速公路供配电系统作用下,有效保证了隧道的正常运行、高速公路运营企业的正常经营。在最近几年中,我......
  • Android平台GB28181设备接入端预置位查询(PresetQuery)探讨和技术实现
    之前blog介绍了GB28181云台控制(PTZCmd)相关,本文主要是介绍下GB28181预置位查询。预置位这块,在处理带云台的设备非常必要,我们主要是做Android平台的GB28181的设备接入端,也可以......
  • webgl(three.js)实现室内三维定位,3D定位,3D楼宇bim、实时定位三维可视化解决方案——
    使用three.js(webgl)搭建智慧楼宇、3D定位、三维室内定位、设备检测、数字孪生、物联网3D、物业3D监控、物业基础设施可视化运维、3d建筑,3d消防,消防演......