背景:
根据Tcp的理论计算,Tcp最佳状态下传输是流水并行的,传输时间等于传输数据耗时+TTL,即千兆网卡的环境下
传输1MB数据需要: 1000ms/100MB*1MB+TTL=10ms+TTL,同机房传输1MB耗时10毫秒,跨机房理论耗时14毫秒
传输4MB数据需要: 1000ms/100MB*4MB+TTL=40ms+TTL,同机房传输4MB需要耗时40毫秒,跨机房理论耗时44毫秒
在我的生产环境,同机房的两个机器之间ping耗时0.15毫秒;两个机器之间读1MB数据和4MB的数据延时极度不稳定,在10毫秒~300毫秒之间波动。
另外一个跨机房使用了专线的环境,两台机器之间ping耗时4毫秒,但两个机器之间读1MB数据和4MB的数据延时也极度不稳定,在40毫秒~500毫秒之间波动。
这个现象看起来就像:网卡压力小时性能差,网卡压力大时性能反而好。
一开始怀疑是网卡驱动有问题,
通过修改网卡驱动参数,关闭NAPI功能,同机房的传输延时有所提升,具体的操作:Disable掉NAPI功能 ,即更改 ethtool -C ethx rx-usecs 0 ,但这个方案有缺点:使得cpu中断请求变多。
另外一个方案:修改tcp的初始化拥塞窗口,强制将初始化拥塞窗口设置为3,即: ip route | while read p; do ip route change $p initcwnd 3;done
这两种方案可以将同机房的读延时至于理论计算水平。
但这两种方案,都无法解决跨机房的长延时问题。进一步追踪如下:
我们测试的延时高,是因为没有享受Tcp高速通道阶段甚至一直处于Tcp慢启动阶段。
我做了下面5步尝试,具体过程如下:
STEP1】 最开始的测试代码:
每次请求建立一个Tcp连接,读完4MB数据后关闭连接,测试的结果:平均延时174毫秒:每次都新建连接,都要经历慢启动阶段甚至还没享受高速阶段就结束了,所以延时高。
STEP2】 改进后的测试代码:
只建立一个Tcp连接,Client每隔10秒钟从Server读4MB数据,测试结果:平均延时102毫秒。
改进后延时还非常高,经过观察拥塞窗口发现每次读的时候拥塞窗口被重置,从一个较小值增加,tcp又从慢启动阶段开始了。
STEP3】改进后的测试代码+设置net.ipv4.tcp_slow_start_after_idle=0:
只建立一个Tcp连接,Client每隔10秒钟从Server读4MB数据,测试结果:平均延时43毫秒。
net.ipv4.tcp_slow_start_after_idle设置为0,一个tcp连接在空闲后不进入slow start阶段,即每次收发数据都直接使用高速通道,平均延时43毫秒,跟计算的理论时间一致。
STEP4】我们线上的业务使用了Sofa-Rpc网络框架,这个网络框架复用了Socket连接,每个EndPoint只打开一个Tcp连接。
我使用Sofa-Rpc写了一个简单的测试代码,Client每隔10秒钟Rpc调用从Server读4MB数据,
即:Sofa-Rpc只建立一个Tcp连接+未设置net.ipv4.tcp_slow_start_after_idle(默认为1),测试结果:延时高,跟理论耗时差距较大:transbuf配置为32KB时,平均延时93毫秒。
STEP5】
Sofa-Rpc只建立一个Tcp连接+设置net.ipv4.tcp_slow_start_after_idle为0,测试结果: transbuf配置为1KB时,平均延时124毫秒;transbuf配置为32KB时,平均延时61毫秒;transbuf配置为4MB时,平均延时55毫秒
使用Sofa-Rpc网络框架,在默认1KB的transbuf时延时124毫秒,不符合预期;
使用Sofa-Rpc网络框架,配置为32KB的transbuf达到较理想的延时61毫秒。32KB跟Sofa-Rpc官方最新版本推荐的transbuf值一致。
结论:
延时高是由于Tcp传输没享受高速通道阶段造成的,
1】需要禁止Tcp空闲后慢启动 :设置net.ipv4.tcp_slow_start_after_idle = 0
2】尽量复用Tcp socket连接,保持一直处于高速通道阶段
3】我们使用的Sofa-Rpc网络框架,需要把Transbuf设置为32KB以上
另附linux-2.6.32.71内核对tcp idle的定义:
从内核代码153行可见在idle时间icsk_rto后需要执行tcp_cwnd_restart()进入慢启动阶段,
Icsk_rto赋值为TCP_TIMEOUT_INIT,其定义为
#define TCP_TIMEOUT_INIT ((unsigned)(3*HZ)) /* RFC 1122 initial RTO value */