现象

最近线上业务反馈,服务不定时的出现502报错。登陆到网关日志平台查询httpcode等于502的,还真的发现同一个服务出现的概率还是蛮高的。所以开始了502的排查之旅。

抓包吧

为了复现问题,直接在测试环境进行压测该服务。压测过程中随着时间的增长,偶尔是能出现的;这就非常好了,只要能复现出问题,那么问题就已经解决一大半了。

登陆服务器抓包

首先确认该服务器的网卡是哪张(ifconfig命令)

    tcpdump -i eth0 tcp  port 8080 and host xxx(pod的ip)  -w (服务名)_502.cap

关于tcpdump的具体使用方法可以参考这里1

TCP原理

在分析抓到的包之前,我们先温习下tcp的三次握手和四次挥手

tcp的握手和挥手

三次握手

简单理解

这里以一个日常的问候,大概表述下3次握手的过程: A和B 是老熟人,正好在路上碰到了,进行了下面的对话

A: hi B吃了吗?(发送SYN)

B:我吃了,你呢?(回应A的SYN也就是发送ACK,并发送自己的SYN)

A:我也吃了 (回应B的SYN,发送ACK回复) 。。。然后他们就可以开始实质性的交流了

实质过程

1、客户端发起连接请求,发送 SYN包(SYN=i)到服务器,并进入到SYN-SEND状态,等待服务器确认

2、服务器收到SYN包后,必须确认客户的 SYN(ack=i+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器进入SYN-RECV状态

3、客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此后客户端和服务器进入ESTABLISHED状态,双方可以开始传送数据。

为什么是3次

这里一定有人会好奇,为什么非得3次,2次或者4次不行吗? 首先我们说下2次为什么不行,还是以上面的打招呼为例子

  • 2次

A:hi B吃了吗?

B:我吃了,你呢?

然后呢?然后就没有然后了,A可能已经被风吹跑了,这交流还能继续么。

  • 4次

4次当然可以了,多确认了一下而已。问题是没有这个必要,第二次B在回复的时候我们已经知道B要继续和A交流了,相当于被固定住了;等A第三次回复之后我们知道A也要和B继续交流了,相当于A也被固定住了,这回风吹不跑了,就可以愉快的聊天了。

上面是一个拟人话的表述,对于实际的tcp握手过程还有更多的细节需要了解,比如连接过程中会出现哪些状态,连接数量受哪些参数影响,这些参数又是怎么影响来连接,怎么查看这些连接参数等等,具体我这里放一个链接2

四次挥手

简单理解

这里我还是以2个人的对话拟人化下过程

A:B,那今天我们就谈到这吧,我不说话了(发送FIN)

B:A,我同意你不说话(发回ACK)

B: A,那我也不说话(发回B的FIN,LAST_ACK)

A:我也同意你不说话(发回ACK)

实质过程

1、客户端发送一个FIN,用来关闭客户端到服务器的数据传送,客户端进入FIN_WAIT_1状态。

2、服务器收到FIN后,发送一个ACK给客户端,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),服务器进入CLOSE_WAIT状态,而客户端进入FIN_WAIT2状态。

3、服务器发送一个FIN,用来关闭服务器到客户端的数据传送,服务器进入LAST_ACK状态。

4、客户端收到FIN后,客户端进入TIME_WAIT状态,接着发送一个ACK给服务器,确认序号为收到序号+1,服务器进入CLOSED状态,完成释放。

分析

有了上面的基础,我们再来看结果图就好理解了

 上面这个截图是抓到的rst的请求,而且这个时间点是我们在网关日志上正好看到502的时间点。

xxx.84 为我们的客户端

xxx.149 为我们的服务端

从头到尾看下来,前面是正常的握手,建立连接,利用keep-alive机制,多次复用端口发送数据。问题出现在倒数第三行开始,倒数第三行客户端回复完上面的请求后,等过了5s才再次请求服务端(也就是倒数第二行),这时候服务端直接回复了一个rst,网关502.

那么为什么服务端会回复rst?这个5s有是个什么时间呢?

什么时候会回复RST

标志位种类

  • SYN 同步标志 表明连接建立的标志

  • ACK 确认标志 表明确认号有效,否则忽略确认号

  • RST 复位标志 表明TCP连接出现连接出现错误,数据包非法拒绝连接

  • URG 紧急标志 表明此数据包处于紧急状态应该优先处理

  • PSH 推标志 表明应该尽快交付给应用进程,而不必等到缓存区填满才推送

  • FIN 结束标志 表明释放一个连接

为什么回复RST

  • 向一个未被监听的端口发送数据

  • 对方已经调用 close 关闭连接

  • 存在一些数据未处理(接收缓冲区),请求关闭连接时,会发送RST强制关闭

5s是什么时间

上面我们看到了,是请求复用端口时,5s再请求导致的连接异常,我们不应该关心为什么客户端5s之后才再次访问,因为这不是我们服务能控制的,客户端是访问自由的;我们更关心的是这个5s是什么。通过代码发现是该服务是个springboot项目,代码中设置了tomcat的connectionTimeout时间正好为5s;同时在的keepAliveTimeout没有显式设置时,也是的keepAliveTimeout的时间4; 

什么原因?

表面原因

服务端TCP关闭连接,客户端日志中会收到2种报错

  • connect reset 客户端在从连接中读数据时,服务端关闭,导致报错

  • connect reset by peer 客户端在往连接中写数据时,服务端关闭,导致报错 基于上面的理论我们去网关的错误日志中查找error log;还真的在返回RST的时间点找到了connect reset by peer的报错 

表面原因就是, 服务端在5s超时时直接关闭了和客户端保持的长连接。但是此时客户端并不知道服务端已经关闭了连接,依然继续请求,导致这个请求到服务端时,服务端已经不认识这个请求了所以就发送了RST请求

那根本原因呢

问题到了这里其实还需要继续分析下,服务器超时关闭的原理是什么,如果每次都是这样超时就关闭而且不通知客户端岂不是会频繁出现连接不可用;

于是我手动模拟了一个长连接,这个长连接会每隔6秒发送一个请求给服务,下面是我的抓包

xxx.173 客户端

xxx.107 是服务端

可以看到只要到达5s应用的超时时间,应用是会返回一个正常FIN给客户端进行正常的挥手的;客户端在下次请求时又会重新发起正常的握手连接。

但是我们还是分析根本原因 这里主要从底层慢慢展开

TCP连接的状态
状态种类5:
LISTEN:侦听来自远方的TCP端口的连接请求

SYN-SENT:再发送连接请求后等待匹配的连接请求(客户端)

SYN-RECEIVED:再收到和发送一个连接请求后等待对方对连接请求的确认(服务器)

ESTABLISHED:代表一个打开的连接

FIN-WAIT-1:等待远程TCP连接中断请求,或先前的连接中断请求的确认

FIN-WAIT-2:从远程TCP等待连接中断请求

CLOSE-WAIT:等待从本地用户发来的连接中断请求

CLOSING:等待远程TCP对连接中断的确认

LAST-ACK:等待原来的发向远程TCP的连接中断请求的确认

TIME-WAIT:等待足够的时间以确保远程TCP接收到连接中断请求的确认

CLOSED:没有任何连接状态

查询目前服务器中tcp连接的状态
netstat -nt | awk '/^tcp/ {++state[$NF]} END {for(key in state) print key,"\t",state[key]}'

 也可以使用ss命令

ss -4 |awk '{++state[$2]} END {for(key in state) print key, "\t",state[key]}'

关闭TCP连接的流程6

TCP的关闭涉及到2方,我们这里描述为主动断开方和被动端开方

  • 全流程 

  • 主动断开方流程7

(1)应用层调用Socket.close()函数发起关闭连接请求

(2)发送FIN到对端,关闭写通道,自己进入FIN_WAIT1状态

(3)等待对端的确认ACK到来,接受到ACK后进入FIN_WAIT2状态;如果在超时时间内没有收到确认ACK直接进入CLOSED状态

(4)如果在FIN_WAIT1状态时收到了对端的FIN则进入CLOSING状态(双发都发出了关闭连接请求,比较少见)

(5)在FIN_WAIT2接受到了对端FIN后进入TIME_WAIT状态;如果在超时时间内没有收这个FIN则直接进入CLOSED状态

(6)在TIME_WAIT状态等待2个MSL(2个报文最长存活周期)后进入CLOSED状态

  • 被动断开方流程 

(1)收到对端FIN后,关闭读通道进入CLOSE_WAIT状态

(2)在CLOSE_WAIT状态等待应用层调用close函数关闭连接

(3)如果在超时时间内调用了close,则进入LAST_ACK状态;否则直接进入CLOSED状态

(4)在LAST_ACK状态,发送FIN到对端并等待对端的确认ACK

(5)如果在超时时间内收到了确认ACK则进入CLOSED状态,否则直接进入CLOSED状态

补充:a. 默认情况下(不改变socket选项),当你调用close( or closesocket,以下说close不再重复)时,如果发送缓冲中还有数据,TCP会继续把数据发送完。 b. 发送了FIN只是表示这端不能继续发送数据(应用层不能再调用send发送),但是还可以接收数据。 c. 应用层如何知道对端关闭?通常,在最简单的阻塞模型中,当你调用recv时,如果返回0,则表示对端关闭。在这个时候通常的做法就是也调用close,那么TCP层就发送FIN,继续完成四次握手。如果你不调用close,那么对端就会处于FIN_WAIT_2状态,而本端则会处于CLOSE_WAIT状态。这个可以写代码试试。 d. 在很多时候,TCP连接的断开都会由TCP层自动进行,例如你CTRL+C终止你的程序,TCP连接依然会正常关闭,你可以写代码试试。

所以到现在为止出现RST的原因是我们的服务端在调用Socket.close()函数,关闭了发出通道,还未进入FIN_WAIT1时,接收到了客户端的请求,导致的了问题的发生。

那是因为tomcat的哪个timeout导致的服务器关闭连接呢?

  • connectionTimeout  其实是控制三次握手的超时时间,握手成功后是不管的,由keepaliveTimeout处理。

  • keepaliveTimeout  2个http请求之间的超时时间(如果开启的话)

  • 是由keepaliveTimeout导致的关闭

这个结论在查了一圈之后没得到准确的答案,没关系,我们自己测试

服务端 xxx.127

客户端 xxx.173

服务端connectionTimeout 5s,keepaliveTimeout 80s 客户端 6s用长连接发一个请求;  可以看到虽然每隔6s才发送一个请求,但是请求的端口都是复用的,而且没有超时,验证了刚才的说法

结论与解决

结论

应用的socket.close()跟TCP的FIN上有语义上的不对等。这里可以理解成是两个步骤,请求到达服务器之后,socket.close()已经先关闭了,也就是应用返回了一个-1表示end of the stream,所以服务端无法处理请求,客户端报错

持续监控

netstat -ntc

 持续监控获取服务的端口;然后出现rst后针对该端口查询该端口的状态变化,发现一直处于established状态。

解决方案

到了这里其实问题就有个解决方案,只要客户端keepalive时间比服务端的小,保证在服务端因为超时关闭该连接之前,连接已经被客户端提前关闭了。按这个思路尝试了下,压测了几次,确实没有再发现RST标志位,也没有出现502。

补充

客户端接受rst后会做些什么处理?会进入什么状态?

所有双方建立的连接将会被回收,未接收完的消息就会被丢弃

# 参考

[1] tcpdump的一些具体使用

[2] TCP干货

[3] 什么时候会回复rst

[4] tomcate 的keepAliveTimeout

[5] tcp连接的状态

[6] tcp关闭流程

[7] tcp关闭流程2

weblogic请求服务端超时后重发一次请求_记一次后端服务偶发502的排错之旅相关推荐

  1. 巧用 maxTimeMS 服务端超时,避免承载亿级用户的腾讯云数据库MongoDB服务雪崩

    腾讯云数据库MongoDB作为一款基于开源社区MongoDB版本的文档数据库产品,其承载着公司内外包括微信.看点.QQ音乐在内的亿级用户重量级APP产品.在某些场景的使用过程中,用户在客户端请求超时后 ...

  2. 阿里云oss文件上传(简单上传、服务端签名后直传)

    前置: 自行开通阿里oss服务: https://www.aliyun.com/product/oss?spm=a2c4g.11174283.J_8058803260.125.d9387da2TjNf ...

  3. 商城项目09_品牌管理菜单、快速显示开关、阿里云进行文件上传、结合Alibaba管理OSS、服务端签名后直传

    文章目录 ①. 品牌管理菜单 ②. 快速显示开关 ③. 阿里云上传概述 ④. 使用代码进行文件上传 ⑤. 结合Alibaba来管理oss ⑥. gulimall-third-party微服务 ⑦. 服 ...

  4. 分布式电商项目二十六:使用阿里云存储的服务端签名后直传(前端联调)

    使用阿里云存储的服务端签名后直传(前端联调) 在前端显示上传的界面,可以根据人人fast-vue的结构,在src\components目录下添加upload文件夹,直接添加vue组件即可,总计三个组件 ...

  5. oss服务端签名后直传分析与代码实现

    文章目录 1.简介 1.1 普通上传方式 1.2 服务端签名后直传 3.服务端签名后直传文档 3.1 用户向应用服务器请求上传Policy和回调. 3.2 应用服务器返回上传Policy和签名给用户. ...

  6. socket服务端处理多个客户端的请求学习理解

    socket服务端处理多个客户端的请求: while(true){ Socket s=ss.accept(); new WorkThread(s).start(); } class WorkThrea ...

  7. OSI七层、TCP/IP五层、UDP、TCP的socket编程(服务端及客户端)、字节序转换、多进程以及多线程服务端的实现

    1.网络以覆盖范围划分:局域网/城域网/广域网   互联网/因特网   以太网/令牌环网--组网方式 2.在网络中必须能够为一表示每一台主机,才能实现点到点的精确通信            IP地址: ...

  8. Go 学习笔记(78)— Go 标准库 net/http 创建服务端(接收 GET、POST 请求)

    使用 net/http 标准库创建一个 http 的 restful api 的服务端,用来处理 GET.POST 等请求. 源代码如下: package mainimport ("enco ...

  9. java oss 批量传输_阿里云OSS对象存储,服务端签名后直传阿里云OSS

    继续上一章文章,这次要操作的是,浏览器请求服务要到签名后直传给OSS对象存储. 1.写好服务端的方法,传给前台相应的密钥 @Resource OSSClient ossClient; @Value(& ...

最新文章

  1. BIEE-CSS样式大全
  2. Xen Server二安装xc及管理xen主机
  3. Redis 通用 key 命令
  4. while循环练习:
  5. 查看电脑重启日志_系统日志看硬盘故障图文教程,电脑日志查看磁盘硬盘坏道问题方法...
  6. 使用Arquillian(包括JPA,EJB,Bean验证和CDI)测试Java EE 6
  7. 一个 SQL 同时验证帐号是否存在、密码是否正确
  8. IE无法打开新窗口与U盘不显示故障的解决
  9. PXE-preboot execute environment
  10. MySQL无法启动 服务没有报告任何错误
  11. 对软件工程的问题及个别软件的分析
  12. Linux程序设计-1-Linux基础
  13. 【热门主题:银魂win7主题】
  14. Quick BI产品核心功能大图(三)电子表格:新手亦可表格自由
  15. mysql最大tpmc_tpcc-mysql 压力测试 tpmc基准测试
  16. day 9.1 逻辑回归-二元回归与多元回归
  17. 太少的相濡以沫,太多的相忘江湖
  18. Java 利用hutool工具实现导出excel并合并单元格
  19. 【逻辑】500桶酒,其中1桶是毒酒,找毒酒
  20. 怎么用VLOOKUP来匹配excel表里面的数据

热门文章

  1. 配色方案没有头绪?看看给你灵感的专业指导
  2. 设计素材模板丨极简风简历模板
  3. 鸿蒙初开踏青时主要内容,鸿蒙初开踏青时
  4. jmap 定时生成linux,linux – 创建heapdumps的替代方案,性能比jmap更高?
  5. 分页设计 与 高级查询 的 结合设计
  6. CPUID — CPU Identification
  7. https://sysdig.com/blog/
  8. Redis 的 Sentinel哨兵介绍与源码分析(1):初始化部分
  9. Unwind 栈回溯详解:libunwind
  10. FD.io VPP:CentOS7下构建自己的VPP RPM包