记一次linux 服务器丢包故障排查
开发联调过程中,使用到了测试环境。测试环境部署在单独的服务器上,由单独的域名访问。但相同的代码和环境,生产环境的服务能够正常访问,而测试环境api经常出现访问超时,一直处在pending状态,而在服务端无论是nginx还是应用层的log都难觅踪影。
有意思的是,这个现象只有在使用公司网络访问测试域名才会出现。并且复现并没有规律,与具体服务api无关,往往是出现连续几个请求pending,一段时间后恢复。同一时间用云服务进行压测,并没有出现不可用。
因此,一开始我就一口咬定是公司网络的问题,加上公司的网络环境确实很差劲。但同一个时间,公司的网络访问其他域名的服务一直正常,狠狠的打了脸。首先还是考虑是服务本身的问题,排查了负载情况,重新部署,分离了多个测试服务。但依旧没有丝毫改善。
推翻了错误的假设后,一时间没有什么思路,决定首先复现这个问题。在本地通过公司网络和ECS不断压测,有大概5%的请求复现了这个问题。而服务器确实没有接收到请求,可以断定不是应用层的问题。
首先怀疑是DNS的问题,用ping和traceroute测试后否定这个假设。下一步通过httpstat工具分析请求的每步耗时。终于有蛛丝马迹,tcp连接建立的时间高达67000ms。问题就出在tcp连接上。
接下来通过tcpdump在服务端进行定位,但由于请求很多,并没有找到有用的信息,又陷入了僵局。在网上看了大量的资料,幸运地看到了一篇文章,https://www.sdnlab.com/17530.html。
按照文中的思路进行排查,最终定位到是PAWS机制引起的问题。原因其实处在tcp的握手和发包的机制上,具体的机制这里不再赘述。关键的两个机制是:1.如何判断数据包的顺序。2.TIME_WAIT的作用。
tcp协议中,序号(seq,ack)的作用是保证连接的可靠和数据包的有序,但现实中往往会出现丢包,或者某些包迷路。例如序列号为s1的包A 因为网络问题没有到达,客户端重发s1包B,但因为带宽足够,在一次回话中,序列号满,序号s1被重新使用,s1包A又到达,服务端就会将错误的数据包传递下去。为了解决这个问题,PAWS引入了timestamp作为判断包是否合法的条件。当前的数据包的timestamp 小于 记录的已处理的最大timestamp,则这个数据包是延迟接收,可以丢弃的。
TIME_WAIT的目的主要是有两点:1.保证非主动关闭连接的一端能够可靠的关闭连接。2.保证迷路的包能够在2*MSL时间内消失,避免影响同一组端口的新连接。
PAWS机制可以解决上述的第二个问题,快速回收TIME_WAIT状态的连接,节约系统资源。而恰恰是这个优化导致了我们一开始说的丢包问题。
公司的网络通过NAT设备访问测试域名,在服务端看来就是同一个ip建立的不同连接。而不同客户端设备的时间戳并不能保证一致。Per-Host保存的时间戳会更新成较大的那个。tcp_tw_recycle开启状态下,所有的比系统保存的Per-Host小的包都将会被丢弃。表现在宏观上就是上述的pending,连接一直无法建立的现象
通过命令验证是否是该原因
netstat -s |grep -e “passive connections rejected because of time stamp” -e “packets rejects in established connections because of timestamp”
查看和修改tcp_tw_recycle
sysctl net.ipv4.tcp_tw_recycle
sysctl net.ipv4.tcp_timestamps
sysctl -w net.ipv4.tcp_tw_recycle=0
关闭了tw_recycle后,观察了一段时间,上述的现象都已经消失。至此,测试环境服务访问异常的问题终于解决。经历了一番折腾,如果没有这篇文章,很难能定位到这个问题。说明底层的知识是非常重要而且永远不会过时的。