TCP的的发送端在收到连续三个同样的ACK的时候会进行重传,这就是tcp的快速重传。

在linux上,使用的是SACK,就是在ACK的基础上还携带者有接收端已经收到的包的信息,这样发送端接收到的时候就可以了解接收端已经介绍到了哪些数据。

在 Linux 内核中会有个判断(你可以看看下面的这个函数),大概意思是这样的:如果在接收端收到的数据和还没有收到的数据之间,两者数据量差得太大的话(超过了 reordering X mss_cache),也可以马上重传数据。这样会导致即使只收到一个SACK也会发生重传的现象。

在云平台的这种网络环境里,网络包乱序 +SACK 之后,产生的数据包重传的量要远远高于网络丢包引起的重传

veth数据包的发送

既然知道了乱序会引起重传。而容器默认使用veth作为虚拟网络接口来发送数据,那么veth会不会引起网络包的乱序呢?

veth是模拟硬件的,对于一个物理网络设备来说,会在接收到网络包之后触发中断,然后让cpu取走网络包。那么veth模拟的过程就是将数据包准备好放入cpu的数据包队列当中,然后触发softirq中断来让cpu取走

veth在默认的情况下会将数据包放入当前运行的cpu队列当中,对于一个多核系统,在进程执行过程中肯定会有cpu的切换,这就是导致了在数据发送的过程中,会将数据放入到多个cpu的队列当中,然后触发softirq中断,而cpu处理中断的过程又是异步的,就导致最终发送的网络包的数据是不能保证的。

因此,可以得出结论,veth对会导致容器网络包乱序的可能性变大。

RSS与RPS

这是减少veth网络包乱序的一种方式。

这里的RSS并不是内存的指标RSS,而是Receive Side Scaling。这与网卡的硬件有关,随着网卡性能的提升,又原来的一个RX队列变为多条,一个硬件中断变为每个RX一个硬件中断,是的多核CPU可以并行处理,提升性能。在网卡硬件当中,可以根据数据包的四元组或五元组来保证同一个流的数据在同一个rx队列当中,保证了tcp流的有序性。

RSS是硬件层面的,RPS则是软件层面的实现,在收到数据包之后会根据四元组做一个hash,然后放入对应的cpu数据包队列当中,保证将同一个流的数据包发送给同一个cpu。在veth当中,可以选择打开RPS,这样就会减少网络包的乱序了。

打开 RPS 的方法挺简单的,只要去 /sys 目录下,在网络接口设备接收队列中修改队列里的 rps_cpus 的值。rps_cpus是16进制的数,每个bit代表一个cpu。

下面是让宿主机上的veth能够在所有的cpu上通过rps重新分配数据包的命令。fff表示的就是12个cpu

# cat /sys/devices/virtual/net/veth57703b6/queues/rx-0/rps_cpus
000
# echo fff > /sys/devices/virtual/net/veth57703b6/queues/rx-0/rps_cpus
# cat /sys/devices/virtual/net/veth57703b6/queues/rx-0/rps_cpus
fff

不过虽然减少了网络包乱序的概率,但是使用rps同样会增加softirq的处理耗时,所以是否开启需要依据实际情况。有时候就算发生乱序也不一定会进行重传。

在物理机上的网卡和驱动一般都有RSS的情况下,RPS一般是用不到的