本文仅作为学习记录,非商业用途,侵删,如需转载需作者同意。
从物理机迁移到容器之后,网络监控中发现,数据包重传的数量,在容器中也比物理机中多很多。
一、问题再现
在容器中使用iperf3 命令,向容器外部发送一下数据,输出结果中的 Retr 列,表示重传的数据包。
# iperf3 -c 192.168.147.51
Connecting to host 192.168.147.51, port 5201
[ 5] local 192.168.225.12 port 51700 connected to 192.168.147.51 port 5201
[ ID] Interval Transfer Bitrate Retr Cwnd
[ 5] 0.00-1.00 sec 1001 MBytes 8.40 Gbits/sec 162 192 KBytes
…
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 9.85 GBytes 8.46 Gbits/sec 162 sender
[ 5] 0.00-10.04 sec 9.85 GBytes 8.42 Gbits/sec receiver
iperf Done.
上面的结果看到,有162个重传的数据包。
网络中发生了数据包重传,可能是数据包在网络中丢了,也有可能是数据包乱序导致的。
最直接的办法就是用 tcpdump 抓包,对于大流量的网络,tcpdump瞬间会有几个GB 的数据,而且会造成额外的系统开销,生产环境不太适合。
还有一种: netstat 命令查看协议栈中的丢包和重传情况。
-bash-4.2# nsenter -t 51598 -n netstat -s | grep retran
454 segments retransmited
442 fast retransmits
-bash-4.2# nsenter -t 51598 -n netstat -s | grep retran
616 segments retransmited
604 fast retransmits
一共发生了 162 次(604-442)快速重传(fast retransmits),这个数值和 iperf3 中的 Retr 列里的数值是一样的。
二、问题分析
2.1、快速重传(fast retransmit)
TCP 协议中,发送端(sender)向接收端(receiver)发送一个数据包,接收端(receiver)都回应ACK。
如果超过一个协议栈规定的时间(RTO),发送端没有收到ACK包,那么发送端就会重传(Retransmit)数据包。
不过等待一个超时之后再重传数据,对实际应用来说太慢了.
所以TCP协议有定义了快速重传(fast retransmit):如果发送端收到3个重复的 ACK ,那么发送端就可以立刻重新发送 ACK 对应的下一个数据包。
图中信息解释:接收端没有收到 Seq 2 这个包,但是收到了 Seq 3-5 的数据包,那么接收端在回应 Ack 的时候,Ack 的数值只能是2 。 这是因为按顺序来说收到 Seq 1 的包之后,后面 Seq 2 一直没有到,所以接收端就只能一直发送Ack 2。
当发送端收到3个重复的Ack 2后,就可以马上重新发送 Seq 2 这个数据包了,而不用再等到重传超时之后了。
========
虽然TCP 快速重传的标准定义是需要收到3次重复的Ack ,不过你会发现在Linux 中常常收到一个 Dup Ack (重复的Ack)后,就马上重传数据了。这是什么原因呢??
是因为 SACK (Selective Acknowledgement) :跟普通的 ACK 相比较,SACK 会把接收端收到的所有包的序列信息,都反馈给发送端。如下图信息:
有了SACK ,对于发送端来说,在收到 SACK 之后就知道了接收端收到了哪些数据,没有收到哪些数据。
Linux内核中有个判断(如下面的函数):如果在接收端收到的数据和还没有收到的数据之间,两着数据量差的太大的话(超过了 reordering * mss_cache ),也可以马上重传数据。
这里的数据量差是根据 bytes 来计算的,而不是按照数据包的数目来计算的,所以你会看到即使只收到一个 SACK ,Linux 也可以重发数据包
static bool tcp_force_fast_retransmit(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
return after(tcp_highest_sack_seq(tp),
tp->snd_una + tp->reordering * tp->mss_cache);
}
如果再用 netstat 查看 “reordering” ,就可以看到大量的 SACK 发现的乱序包。
-bash-4.2# nsenter -t 51598 -n netstat -s | grep reordering
Detected reordering 501067 times using SACK
其实云平台网络环境中,网络包乱序 + SACK 之后,产生的数据包重传的量要远远高于网络丢包引起的重传
比如下图展示: Seq2 与 Seq3 两个包乱序的话,那么立刻就会引起 Seq2 的立刻重传。
2.2、Veth 接口的数据包的发送
网络包乱序会造成数据包重传,容器的 veth 接口配置有没有可能引起数据包的乱序。
上一节中:通过veth 接口从容器向外发送数据包,会触发 peer veth 设备去接收数据包,这个接收的过程就是一个网络的 softirq 的处理过程。
在触发softirq 之前,veth 接口会模拟硬件接收数据的过程,通过 enqueue_to_backlog() 函数把数据包放到某个CPU 对应的数据包队列里(softnet_data)
static int netif_rx_internal(struct sk_buff *skb)
{
int ret;
net_timestamp_check(netdev_tstamp_prequeue, skb);
trace_netif_rx(skb);
#ifdef CONFIG_RPS
if (static_branch_unlikely(&rps_needed)) {
struct rps_dev_flow voidflow, *rflow = &voidflow;
int cpu;
preempt_disable();
rcu_read_lock();
cpu = get_rps_cpu(skb->dev, skb, &rflow);
if (cpu < 0)
cpu = smp_processor_id();
ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);
rcu_read_unlock();
preempt_enable();
} else
#endif
{
unsigned int qtail;
ret = enqueue_to_backlog(skb, get_cpu(), &qtail);
put_cpu();
}
return ret;
}
从上面的代码可以看到,在缺省的情况下(也就是没有 RPS 的情况下)
enqueue_to_backlog() 把数据包放到了 "当前运行的CPU " (get_cpu())对应的数据队列中。
如果是从容器里通过 veth 对外发送数据包,那么这个"当前运行的CPU " 就是容器中发送数据进程的CPU。
对于多核系统,这个发送数据的进程可以在多个CPU上进行切换运行。进程在不同的CPU上把数据放入队列并且 raise softirq 之后,因为每个CPU上处理 softirq 是个异步操作,所以两个CPU network softirq handler 处理这个进程的数据包时,处理的先后顺序并不能保证。
所以 veth对 的发送数据方式,增加了容器向外发送数据出现乱序的几率。
2.3、RSS和RPS
对于 veth 接口的这种发包方式,有办法减少一下乱序的几率吗?
其实,我们在上面 netif_rx_internal() 那段代码中,有一段在"#ifdef CONFIG_RPS"中的代码。
这段代码中在调用 enqueue_to_backlog() 的时候,传入的CPU并不是当前运行的CPU,而是通过 get_rps_cpu() 得到的CPU,那么这会有什么不同呢?这里的RPS是什么呢?
要解释RPS,需要先看一下 RSS,这个RSS 不是我们之前说的内存RSS,而是和网卡硬件相关的概念,它是 Receive Side Scaling 的缩写。
现在的网卡性能越来越好,从原来一条RX 队列扩展到了N条RX 队列,而网卡的硬件中断也从一个硬件中断,变成了每条 RX 队列都会有一个硬件中断。
每个硬件中断可以由一个CPU来处理,对于多核系统,多个CPU 可以并行的接收网络包,这样就大大提高了系统的网络处理能力。
在网卡硬件中,可以根据数据包的 4元组 或者 5元组信息来保证同一个数据流,比如一个TCP 流的数据始终在 一个RX 队列中,这样也能保证同一流不会出现乱序的情况。
比如下面这个图描述了 RSS 是怎么样工作的。
RSS 的实现在网卡硬件和驱动里面,而 RPS(receive Packet steering) 其实就是在软件层面实现类似的功能。它主要实现的代码框架就在上面的 netif_rx_internal() 代码里。
就像下面的这张示意图里描述的这样:在硬件中断后,CPU2 收到了数据包,再一次对数据包计算一次 4元组的hash 值,得到这个数据包与 CPU1 的映射关系。
接着会把这个数据包放到 CPU1 对应的 softnet_data 数据队列中,同时向CPU1 发送一个IPI 的中断信号。
这样一来,后面CPU1 就会继续按照Network softirq 的方式来处理这个数据包了。
RSS 和 RPS 的目的都是把数据包分散到更多的CPU上处理,使得系统有更强的网络包处理能力。在把数据包分散到各个CPU上时,保障了同一个数据流在同一个CPU上,这样就可以减少包的乱序。
结合RPS的知识,veth 对外发送数据的时候,在 enqueue_to_backlog() 的时候选择CPU的问题。
显然如果对应的veth 接口上打开了RPS的配置,那么对于同一个数据流,就可以始终选择同一个CPU了。
打开 RPS的方式:在/sys 目录下,在网络接口设备接收队列中修改队列里的 rps_cpus 的值。 rps_cpus 是一个16进制的数,每个bit 代表一个CPU。
我们在一个 12CPU 的节点上,想让 host 上的 veth 接口在所有的 12 个 CPU 上,都可以通过 RPS 重新分配数据包。那么就可以执行下面这段命令:
默认是0 都没有开启。
# 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
三、重点小结
快速重传(fast retransmits)的定义:如果发送端收到3个重复的ACK,那么发送端就可以立刻重新发送ACK 对应的下一个数据包,而不用等待发送超时。
在Linux中看到的收到一个重复的ACK 就快速重传的,这是因为Linux下对 SACK 做了一个特别的判断后,就可以立刻重传数据包。
容器中的快速重传分析,重传大部分是由包的乱序触发的。
容器的veth 网络接口可能会增加数据包乱序的几率。
RPS和RSS作用类似,都是把数据包分散到更多的CPU上处理,使得系统有更强的网络包处理能力,它们的区别是RSS 工作在网卡的硬件层,而RPS 工作在Linux内核的软件层。
把数据包分散到各个CPU时,RPS 保证了同一个数据流在同一个CPU上,这样可以减少包的乱序。我们可以在veth 网络接口上开启RPS 这个特性。
RPS 的配置会带来额外的系统开销,在某些网络环境中会引起 softirq CPU 使用率增大。
TCP 的乱续包,并不一定都会产生数据包的重传。
想要减少网络数据的重传,还可以考虑协议栈中其他参数的设置,比如
/proc/sys/net/ipv4/tcp_reordering
补充:
发送方只要连续收到3个重复确认(ACK)就应当立即重传未被确认的报文段。
其中这个数字3,是RFC的建议。Linux kernel通过参数net.ipv4.tcp_reordering来控制,默认值为3。另外Linux可能还会根据乱序测量的结果来更新实际工作的重复确认门限值。它的范围最终会在[net.ipv4.tcp_reordering, net.ipv4.tcp_max_reordering]之间。
四、评论
1、
问题:
RPS 的配置还是会带来额外的系统开销,在某些网络环境中会引起 softirq CPU 使用率的增大。
老师请教一下这里的某些网络环境指的是什么网络环境?具体增大softirq CPU 使用率的原因是什么呢?
回答:
RPS会对数据包重新计算hash, 然后把数据包重新分派到新的cpu对应的队列,之后还需要用IPI的中断通知新的cpu, 而在IPI中断之后,就需要再做一次softirq。这样对于高频率收包的情况下, softirq就会明显的增大。
在实际应用的时候,对于物理网络接口,如果已经有了RSS的情况,一般就不需要再打开RPS了。
2、
问题:
试了下,CONFIG_RPS、rps_cpus 开启时,iperf3 测试仍然存在快速重传情况,没有明显改观。是不是漏了什么?
回答:
其实除了RSS/RPS外,你还可以去看一下RFS的概念,这个和这一讲的关系不大,并且逻辑要更加复杂,就没有在文档中说明了。
你测试iperf3的环境是什么?如果改了veth的rps_cpus配置,没有效果,那么还要再分析有没有其他情况造成了快速重传。
3、
问题:
网络插件(flanneld、calico、ovs)和iptables的关系有点乱,网络插件也有自己的转发表,比如ovs的流表和iptables的规则是什么关系呢?
回答:
ovs和iptables的规则是独立的。
数据包在协议栈中传递的时候有专门的hook点来处理对应的规则,比如iptables规则在netfilter的pre, in, out等hooks点上处理,ovs规则在ovs相关的device上处理。
4、
问题:
有一点不适很明白:“我们的用户把他们的应用程序从物理机迁移到容器之后,从网络监控中发现,容器中数据包的重传的数量要比在物理机里高了不少”
我理解,这个是因为容器的环境没有设置那个RPS导致的?也就是物理机器如果没设置也会有类似的问题吧
回答:
物理机上的网卡和驱动一般都有RSS, 一般不需要RPS。
5、
乱序重传,最终会影响到延时和吞吐量。
6、
问题:
rps_cpus 是一个 16 进制的数,每个 bit 代表一个 CPU。那么12个CPU,为啥是FFF?请教一下
回答:
一个F是4个bits, 总共三个F, 那么是4 * 3 = 12
7、
问题:
请教老师一个问题,设置veth pair的rps,在两端host和container都需要配置吗?
回答:
可以根据节点上网络的情况来决定在哪一端配置rps.