Linux网络优化之TCP优化

TCP(Transmision Control Protocol)即传输控制协议, 位于TCP/IP协议栈的第三层(L3), 是一种提供了可靠连接的字节流协议; TCP是目前使用最为广泛的协议之一, HTTP/MQTT/FTP等诸多应用层协议都是基于TCP实现的, 更多关于TCP协议相关的具体内容可以参考标准文档RFC793以及早前写的一篇聊一聊TCP协议.

在上一篇文章中讲到了高速以太网如1Gbps/10Gpbs中Linux网络L2(链路层)的一些优化方法, 包括了offload(卸荷)以及scaling(缩放)两种技术. 随着高速网络的不断普及, 1Gbps/10Gpbs以太网已经被广泛使用, 40Gbps/100Gbps也已经制定标准, TCP也在随着网络带宽的提升而不断进化.这篇文章我们就来看下如何在高速以太网下对TCP相关的参数的进行调优.

TCP窗口大小

TCP为了协调客户端与服务端数据的发送/接收, 使用窗口机制来确保两者保持一致的速率, 确保快速的发送方不会超过慢速的接收方. 接收方需要通过窗口更新(Window Update)消息来告知发送方当前可以发送多少数据. 当接收方应用处理数据时, 可向发送方发送窗口更新消息; 窗口更新消息最快需要一个返回时间(RTT-Round Trip Time), 因此窗口越大, 同一个RTT可以接收的数据就越多; 类似地, RTT越大, 则同样的时间可以接收的数据会越少. 为此, 我们可以得到如下公式:

1
2
3
    
吞吐量 <= 窗口大小/RTT

TCP原始设计中, 窗口大小为65535字节(64KiB - 1), 这是发送方在收到窗口更新之前允许发送的最大数据量. 但随着网络带宽越来越高, 1Gbps/10Gbps的网络已经得到广泛使用, 这个窗口值无法让高速网络的带宽得到有效利用. 为了让高速网络的带宽得到充分利用, 一般需要通过带宽时延成乘积BDP(Bandwidth-delay product)公式来计算真实的TCP窗口大小.

1
2
3
4

BDP(位)= 带宽(位/秒)* RTT(秒)


假如, 我们有一个1Gbps的网络, RTT为10ms, 通过BDP公式可以大致计算出网络实际的带宽:

1
2
3
4
5
6

带宽(bit/s) = BDP/RTT
= (65535Byte * 8bit/Byte)/10ms
= 524280 bit/0.01s
= 5242800 bit/s

也就是说, 如果按照TCP原始的64KiB的窗口设定, 一个千兆网络的实际吞吐量才不到50Mbps, 这远没有达到1000Mbps的理论值. 那么, 要如何解决这个问题了? 标准协议通过增加一个窗口缩放选项来扩张TCP窗口大小.

TCP窗口缩放

为了改变TCP原有设计对窗口大小的限定, RFC7323引入了扩展协议, 在TCP头中增加一个Window Scaling Factor的参数, 可以使窗口大小扩展到1,073,725,440字节(接近1GiB).具体可以参考RFC7323中的说明.

要在Linux中确认是否开启了窗口缩放功能, 可以通过命令:

1
2
3

sudo sysctl net.ipv4.tcp_window_scaling

如果这个值为1, 则表示开启了该选项(看4.4的内核版本已经默认打开了);如果没有打开, 要开启该选项, 只需要通过下述命令打开即可:

1
2
3

sudo sysctl -w net.ipv4.tcp_window_scaling=1

调整TCP窗口大小

现在开启了窗口缩放选项, 我们就可以根据BDP来计算给定理论带宽与延迟情况下, 对应的TCP窗口大小了. 还是以1Gbps网络为例, 假设延迟(可以通过ping来大致判断)为3ms, 那么对应的窗口大小应

1
2
3
4
5
6
7

窗口大小 = (理论带宽 * 延迟)/8bit
= (1000Mbps * 0.03)/8bit
= 3750000b
= 3.6Mb


就是说至少需要将TCP窗口的大小设置为3.6Mb大小才能确保实际吞吐量达到网络的理论带宽. 在Linux中, 一般需要设置如下几个参数来设定TCP窗口大小:

1
2
3
4
5
6

net.core.rmem_max
net.core.wmem_max
net.ipv4.tcp_rmem
net.ipv4.tcp_wmem

其中前两个参数rmem_max/wmem_max表示应用最大可用的TCP窗口大小, tcp_rmem/tcp_wmem表示单个应用允许分配的最大TCP窗口大小(对应有三个值, 分别表示最小/默认/最大的TCP窗口大小). 为了保证TCP的性能与理论比较接近, 我们可以将上述四个参数都按照上述计算得到的TCP窗口进行设定:

1
2
3
4
5
6
7

MaxExpectedBDP=3750000
sudo sysctl -w net.core.rmem_max=$MaxExpectedBDP
sudo sysctl -w net.core.wmem_max=$MaxExpectedBDP
sudo sysctl -w net.ipv4.tcp_rmem="4096 87380 $MaxExpectedBDP"
sudo sysctl -w net.ipv4.tcp_wmem="4096 16384 $MaxExpectedBDP"

或者可以通过直接写对应/proc的文件节点来设定上述参数, 如下:

1
2
3
4
5
6
7
8

MaxExpectedBDP=3750000
echo $MaxExpectedBDP > /proc/sys/net/core/wmem_max;
echo $MaxExpectedBDP > /proc/sys/net/core/rmem_max;
echo 4096 1048576 $MaxExpectedBDP > /proc/sys/net/ipv4/tcp_rmem;
echo 4096 1048576 $MaxExpectedBDP > /proc/sys/net/ipv4/tcp_wmem;
echo 1 > /proc/sys/net/ipv4/tcp_window_scaling;

除了上述根据BDP公式来优化TCP窗口大小之外, 对于高速以太网, 通常还可以通过开启TCP时间戳(增加一次往返RTT时间的估算准确度), 对TCP头进行压缩(减少TCP传输数据的大小)等方式来优化, 具体可以参考RFC7323.

总结

本文首先介绍了TCP在高速网络中需要解决的窗口不足的问题, 然后阐述了TCP标准协议是如何解决该问题的。接着着重说明了如何在Linux中配置TCP参数,确保TCP性能达到最优。

参考资料