1. 问题现象
客户端请求和长连接服务端建立连接时偶现建立连接失败。错误原因为:dial tcp ip:8008: i/o timeout
。
2. 问题排查
- 发生问题时服务是正常处理其他请求的,系统的SYN_RECV是正常范围内,dmesg也没有系统异常log等等等一切看上去都很正常,但是就是偶尔有个请求连接不上。
- 同事发现在服务端主动断开连接后立即重连复现此问题的概率很大。遂手写客户端模拟此操作,经多次测试确实是。
- 不熟悉客户端代码,但能够在本地开发环境复现此问题,大大的方便了问题排查,使用手写的客户端模拟建立请求,加上wireshark抓包发现客户端发送了建立连接的SYN包,但是没有收到服务端回复,随即客户端触发重传机制,多次重发SYN包还是没有收到ACK回复。此时问题原因缩小到网络丢包及服务端过滤数据包上。
- 服务器端使用tcpdump抓包后放在wireshark中打开发现,服务端收到了客户端所有的的SYN包,但是一律没有回复ACK。因此排除网络丢包问题,回归到机器和服务本身。
抓包命令
tcpdump -i eth0 host ip -nn -w ip.pcap
- 梳理服务端主动断开逻辑,一种是15秒钟客户端没有心跳请求,另一种是长连接异常例如EOF、i/o timeout等。这些是长连接服务应该处理的逻辑,观察代码也没发现异常。此时将问题定位到机器网络配置上。
- 根据复现逻辑的
服务端主动断开连接后
,梳理网络配置,重点查看有关连接、重试、回收等配置后发现,服务所在机器配置了快回收。
cat /etc/sysctl.conf
net.ipv4.tcp_tw_recycle = 1
3. 问题原因
在TCP协议规范中,主动断开连接一方会进入TIME_WAIT状态,此状态会等待2MSL时间(60S),如果主动关闭的连接比较多就会有很多处于TIME_WAIT状态的连接存在,占用服务器资源,影响网络性能。一个解决方案是可以打开net.ipv4.tcp_tw_recycle = 1
配置,就能加快TIME_WAIT状态的回收。
但是为了避免新连接收到旧连接的数据,在开启此选项时,连接进入TIME_WAIT状态后,会记录此连接对端的信息,其中最主要的是IP地址和时间戳。这样新连接建立时就会对比同个ip下时间戳是否滞后,如果滞后则认为是旧连接的数据就会过滤掉。
回想问题原因,确实是这样,在公司的办公网络中大家的出口ip只有一个,这时去访问服务,就会出现这个问题,切换成其他网路就会正常。
4. 修复问题
显而易见,想要解决此问题就需要去除net.ipv4.tcp_tw_recycle
配置。或者修改另一个配置net.ipv4.tcp_tw_timeout
,此配置可以修改处于TIME_WAIT状态的等待时间,但是修改此时间与TCP/IP协议的quiet time概念相违背,可能导致系统将旧数据当做新数据接收,或者复制的新数据当做旧数据拒绝。参考
当负载均衡启用NAT模式时,客户端TCP请求到达负载均衡,修改目的地址(IP+端口号)后便转发给后端服务器,而客户端时间戳数据没有变化。对于后端Web Server,请求的源地址是负载均衡,所以从后端服务器的角度看,原本不同客户端的请求经过LVS的转发,就可能会被认为是同一个连接,加之不同客户端的时间可能不一致,所以就会出现时间戳错乱的现象,于是后面的数据包就被丢弃了。