写点什么

Connection reset

  • 2022 年 5 月 09 日
  • 本文字数:3054 字

    阅读完需:约 10 分钟

Connection reset

一、什么是 Connection reset

在 TCP 首部中有 6 个标志位,其中一个标志位为 RST,用于“复位”的。无论何时一个报文 段发往基准的连接( referenced connection)出现错误,TCP 都会发出一个复位报文段。如果双方需要继续建立连接,那么需要重新进行三次握手建立连接。



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


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 连接”。


二、Connection reset 的原因

导致此异常的原因,总结下来有三种情况:

1.服务器端偶尔出现了异常,导致连接关闭

解决方法:采用出错重试机制

2.服务器端和客户端使用的连接方式不一致

解决方法:服务器端和客户端使用相同的连接方式,即同时使用长连接或短连接

3.如果是 HTTPS,那么还存在 TLS 版本不一致

解决方法:服务器端和客户端使用相同的 TLS 版本


三、线上问题排查过程

3.1 第一阶段

cash 服务上线两天就有一条报警 connection reset 的报警,第一反应就是服务重启,看发版记录,确实在报警时间段确实进行了服务发版,但是看项目代码已经做了平滑发布。

平滑发布解决方案


方案一:spring 处理(本服务采用)

Spring Boot 需要 2.3 及以上

server.shutdown=gracefulspring.lifecycle.timeout-per-shutdown-phase=20s
复制代码

方案二:k8s 处理

https://docs.spring.io/spring-boot/docs/current/reference/html/deployment.html#deployment.cloud.kubernetes.container-lifecycle

spec:  containers:  - name: "example-container"    image: "example-image"    lifecycle:      preStop:        exec:          command: ["sh", "-c", "sleep 10"]
复制代码

3.2 第二阶段

过了几天又出现了几次,排查继续通过看日志发现 Caused by: javax.net.ssl.SSLException: Connection reset,而且目标服务没有收到请求,没有日志输出。

怀疑是 ssl 安全协议不一致通过https://www.ssllabs.com/ssltest/analyze.html查询域名支持的 ssl 版本,项目是 jdk1.8 默认是 sslv1.2 通过对比排除是 ssl 版本不一致导致。


3.3 第三阶段

上线 15 天通过日志查询发现一共出现过 7 次,通过发生频率,确定为网路不稳定导致。所以通过重试解决网络不稳定的问题。


方案一:前提条件被调用接口需要做幂等处理
@Configurationpublic class FeignConfiguration {    @Bean    public Retryer feignRetryer() {        return new Retryer.Default(100, 100, 2);    }}
复制代码

这样不仅仅 RST 异常会重试,readTimeout 等异常也会重试。


方案二:

重写 Retryer.Default 判断报错方法是否是 GET,如果是 GET 进行重试,其他方法不重试。


@Slf4jpublic class FeignRetryer extends Retryer.Default { public FeignRetryer() { super(100, 100, 2); }
@Override public void continueOrPropagate(RetryableException e) { //get 方法可以重试,其他方法不重试 if (e.method() == Request.HttpMethod.GET) { super.continueOrPropagate(e); } else { throw e; } }

@Override public Retryer clone() { return new FeignRetryer(); }}
@Configurationpublic class FeignConfiguration { @Bean public Retryer feignRetryer() { return new FeignRetryer(); }}
复制代码

仅对 get 接口异常重试,不区分 RST 异常还是其他异常,post 不重试。但是如果使用不规范 get 也会涉及幂等问题。


方案三

这种方式比较稳妥,只对 RST 重试,而且当报 RST 后,被请求的接口是没有收到请求的,所以也不涉及幂等问题。通过递归判断异常类型和异常信息中是否包含关键字决策是否重试。


@Slf4jpublic class FeignRetryer extends Retryer.Default {    private static final String KEYWORD = "Connection reset";
/** * 重试三次 */ public FeignRetryer() { super(100, 100, 3); }
@Override public void continueOrPropagate(RetryableException e) { if (ExceptionUtil.isExceptionCauseContainKey(e, SocketException.class,KEYWORD)) { super.continueOrPropagate(e); } else { throw e; } }

@Override public Retryer clone() { return new FeignRetryer(); }}
@Configurationpublic class FeignConfiguration { @Bean public Retryer feignRetryer() { return new FeignRetryer(); }}


@Slf4jpublic class ExceptionUtil { /** * cause 是否包含关键字key * * @param throwable * @param key * @return */ public static boolean isExceptionCauseContainKey(@NotNull Throwable throwable, @NotNull Class<?> expectedType, String key) { while (throwable != null) { log.info("#ExceptionUtil.isExceptionCauseContainKey expectedType:{} message:{}", expectedType, throwable.getMessage()); if (expectedType.isInstance(throwable) && throwable.getMessage().contains(key)) { return true; } throwable = throwable.getCause(); } return false; }}
复制代码


大家在各种异常重试时,是怎么做的呢?

欢迎在评论区分享你的观点~


关于领创集团(Advance Intelligence Group)

领创集团成立于 2016 年,致力于通过科技创新的本地化应用,改造和重塑金融和零售行业,以多元化的业务布局打造一个服务于消费者、企业和商户的生态圈。集团旗下包含企业业务和消费者业务两大板块,企业业务包含 ADVANCE.AI 和 Ginee,分别为银行、金融、金融科技、零售和电商行业客户提供基于 AI 技术的数字身份验证、风险管理产品和全渠道电商服务解决方案;消费者业务 Atome Financial 包括亚洲领先的先享后付平台 Atome 和数字金融服务。2021 年 9 月,领创集团宣布完成超 4 亿美元 D 轮融资,融资完成后领创集团估值已超 20 亿美元,成为新加坡最大的独立科技创业公司之一。


往期回顾 BREAK AWAY

Spring data JPA 实践和原理浅析

如何解决海量数据更新场景下的 Mysql 死锁问题

企业级 APIs 安全实践指南 (建议初中级工程师收藏)

Cypress UI 自动化测试框架

serverless让我们的运维更轻松


▼ 如果觉得这篇内容对你有所帮助,有所启发,欢迎点赞收藏:

1、点赞、关注领创集团,获取最新技术分享和公司动态。

2、关注我们的公众号 & 知乎号「领创集团 Advance Group」或访问官方网站,了解更多企业动态。


发布于: 刚刚阅读数: 2
用户头像

智慧领创美好生活 2021.08.12 加入

AI技术驱动的科技集团,致力于以技术赋能为核心,通过科技创新的本地化应用,改造和重塑金融和零售行业,以多元化的业务布局打造一个服务于消费者、企业和商户的生态圈,带来个性化、陪伴式的产品服务和优质体验。

评论

发布
暂无评论
Connection reset_Connection reset_领创集团Advance Intelligence Group_InfoQ写作社区