这两天质量的同学反馈说iperf
测试时结果很差,跟实际的千兆带宽差别很大。确认了半天,发现内核的各项参数都已经完全按照千兆的目标速率进行配置了,那为什么还是会出现TCP/UDP
带宽不足的问题? 记得当时优化参数时,自己摸底测试的TCP
结果挺好的,都达到了预期的900Mbps
以上,看起来最近有什么修改导致了这个测试结果差异。
偶然的一个机会查看内核配置时,发现最近有人打开了trace
功能,看起来很可能是这个修改导致了网络性能的下降了。拿早前未开启trace
功能的版本一对比,果真是trace
功能影响了TCP
的带宽。但是测试UDP
还是会出现如下结果,看起來好像网络很差(注意对UDP
测试需要通过设置-b
参数来限制可能的带宽,否则也可能出现测试结果偏差):
- 使用
iperf
默认的的包大小测试(看源码,对UDP
来说,默认的包大小为1024
, 最大为65507
)
1 |
|
由于UDP
没有流控,整体的丢包率1.3%
还不错,但带宽明显太小了,只有理论值的1/5
不到。从这个结果可以看到,实际带宽不足是因为发送端的速率太小了,导致整个通道未被充分占满。这就好比一个粗大的水管,但实际传输的水流太小了,容量利用率不够。要想提高利用率,只能想法提升发送端的速率。iperf
中,我们可以通过提高包大小来达到这一目的:
- 使用最大的
UDP
包大小(65507
)进行测试
1 |
|
这次测试带宽明显有所提升,但接收端的丢包率却达到了惊人的39%
。为什么会出现如此高的丢包率了?这还是要从UDP
的传输机制说起了。我们都知道,UDP
是无连接的数据报文传输协议,也没有类似TCP
滑动窗口的流量控制机制,因此快速的一方很有可能会淹没慢速的一方。这个时候就需要适当的调整慢速一端的socket
缓冲区大小了。
对于UDP
来说,主要有如下几个参数会影响实际传输时socket
缓冲区的大小(可以参考内核文档ip-sysctl.txt
):
net.core.wmem_default/net.core.wmem_max
: 默认、最大的发送缓冲区大小(bytes),如果没用通过setsockopt
來设置SO_SNDBUF
的值,则会使用内核默认的缓冲区大小net.core.rmem_default/net.core.rmem_max
: 默认、最大的接收缓冲区大小(bytes), 类似的,如果没有通过SO_RCVBUF
来设置发送缓冲区的值,则使用内核默认的net.core.rmem_default
值net.ipv4.udp_mem="min pressure max"
: 系统中所有UDP
连接socket
可以使用的最小、压力、最大页数(页大小一般为4K
),默认值一般在系统启动的时候根据当前可用内存计算出来的net.ipv4.udp_rmem_min/net.ipv4.udp_wmen_min
: 系统中单个UDP
连接socket
可用的最小内存(bytes), 默认是4K
。
要想减少测试中的丢包率,就需要提高慢速端(也就是接收端)的缓冲区大小,确保其有足够的空间来保存接收到的数据,不至于来不及处理而被内核丢弃。我们可以选择在发送端或者接收端设置缓冲区的大小,对于iperf
只能通过-w
控制客户端的缓冲区大小, 这里我们直接在客户端设置发送缓冲区的大小为10M
:
1 |
|
可以看到,丢包率速率下降了到预期的程度。如果我们要在服务端设置接收缓冲区的大小,可以有两种方式: 一种是通过设置net.core.rmem_default
大小,一种是修改iperf3
的源码,确保服务端也可以通过-w
来设置缓冲区的大小。
总结
网络的带宽优化涉及了Linux内核协议栈和网卡驱动,充分理解网络数据的传输流程以及TCP/UDP
等传输协议的机制,才有可能找到性能的瓶颈。总的来说,这些优化很值得很深入研究,可以让人完整的把各个知识点都梳理串联起来。