千兆公网带宽的TCP sendbuffer与receivebuffer调整

不知道有多少人留意过,使用千兆公网带宽传输数据时,只要距离稍微远点,就无法跑满千兆。对于这种情况,可能第一反应是“线路繁忙,公网带宽不足,跑不满很正常”。不过如果你有在不同地区的多台千兆设备间传数据的经历的话,你大概会发现,最大传输速度跟延迟成负相关。

首先丢出个公式:

TCP有ARQ机制,已发出的数据要收到ACK后才能丢弃。因为至少要等一个RTT才能收到ACK,所以sendbuffer要至少能存放一个RTT内能发出的数据量。另外,TCP的拥塞控制也会根据接收方的receivebuffer大小限制数据的发出速率。因此,如果想达到100%的带宽利用率,双方的buffer size都得符合上述公式。

根据Linux kernel的document,TCP sendbuffer max size默认为4MB,所以RTT低于多少时才能保证千兆呢?

RTT = 32ms,只要超过32ms,你的千兆带宽就不能跑满。万兆网络的话,这个数还得除以十:3.2ms,基本上只能在局域网内跑满(我猜即使buffer够了,万兆在公网还真的跑不满……)。

这里再丢出两条公式:

为什么会有另外两条公式?因为开头给出的只是理论值,实际上的buffer,并非仅用来储存payload,因此需要扣除非payload部分。

sendbuffer:

第一条计算sendbuffer的公式的系数0.7,是我测出来的,并不是准确值,为什么会是接近这个值,我还在寻找答案。

receivebuffer:

计算receivebuffer的可能有点复杂,对于tcp_adv_win_scale,kernel文档解释如下:

tcp_adv_win_scale – INTEGER
Count buffering overhead as bytes/2^tcp_adv_win_scale
(if tcp_adv_win_scale > 0) or bytes-bytes/2^(-tcp_adv_win_scale),
if it is <= 0.

Possible values are [-31, 31], inclusive.

Default: 1

值的应用以及公式的出处,可以在这里找到:https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/net/tcp.h?h=v5.10.12#n1395

简单来讲,就是receivebuffer有一部分是作为buffering overhead的,其大小通过tcp_adv_win_scale进行控制。所以扣除这一部分才是实际用来存放payload的size。

buffer在内核中的设定:

TCP的sendbuffer与receivebuffer分别在net.ipv4.tcp_wmem和net.ipv4.tcp_rmem中设定:

三个值分别是min,default和max,kernel会在min和max自动调整,程序也可以通过setsockopt() API设定。

下面是对buffer的调整尝试:

A到B之间的RTT是146ms,A的sendbuffer max size是默认4MB,测试下B从A下载文件的速度:

调整A的sendbuffer:

再到B上测试下速度:

快的确是快了,但只有21.2 MB/s,好像并不是预期的结果,如果你抓包看看A和B之间的数据包,你会发现,B的窗口大小上限是3 MB,见下图的Win:

所以这限制了未被ACK的数据量,A不能发送超出B接收能力的量,通过窗口大小和RTT可以反推出速度:3MB / 0.146s ≈ 20.5MB/s,接近curl的速度。

TCP窗口大小是receivebuffer的具体表现,查看一下B的receivebuffer:

如果把数值代入receivebuffer的公式,你可以算得receivebuffer的值是接近6MB,跟当前tcp_rmem的max size一致,所以B目前的行为是符合这两个数值的预期的。

下面调整下B的receivebuffer:

这时抓包,可以看到B的窗口大小上限是18M:

0.146s * 112MB/s = 16.352,可以确定目前的瓶颈是物理带宽而不是buffer。

再找了个与A的RTT是68ms的C:

A的sendbuffer已经调整过了,足以应付68ms的RTT,但C的receivebuffer还没调整:

新的速度接近原来的两倍,把数值代入公式:0.068s * rate = 12MB – (12MB / 2^1),得rate = 88.2353MB/s,接近实际速率。

另外,这里再提一个参数“tcp_slow_start_after_idle”,kernel document的解释如下:

tcp_slow_start_after_idle – BOOLEAN
If set, provide RFC2861 behavior and time out the congestion
window after an idle period. An idle period is defined at
the current RTO. If unset, the congestion window will not
be timed out after an idle period.

Default: 1

这个功能默认是启用的,连接空闲一个RTO后,后面会从新进入慢启动状态,长连接要保证稳定性能的话,置0。

享用TB级的容量

我的电脑在2012923日,对配置进行了升级,但是因为预算,并没有更换硬盘,继续用我用了五年多的希捷 160GB SATAII的硬盘。

电脑其他的配件的性能上去了,使用过程中我逐渐地感到了硬盘所带来的瓶颈。有时要进行大量的IO,虽然CPURAM占用都不太多,但是仍然会一卡一卡的。并且最近下行带宽升级到了4Mbps,下载的大东西也多了,计算机里面的四个分区都变红了……

最近,在《电脑报》上关注到,硬盘的价格在逐渐下降,1TB的已经跌破500元大关,于是,就有了升级硬盘的打算……

2013128日下午,花了460软妹币,入手了我的第二个硬盘:希捷Barracuda 1TB 7200 64MB 单碟(ST1000DM003


由于机箱不太给力,真正能放硬盘的位置,找了许久才找到能对准螺丝口的,旧的160GB的硬盘还好好的,没有坏道之类的,就是性能低了点,可以继续用。

最后,进入系统,初始化硬盘,选择MBR。后来重装系统时,从光盘启动选择了UEFI开头的,结果Windows7的安装程序帮我改成GPT了,在我进行分区超过了四个后才发现,不过已经写入很多数据了,再次初始化为MBR就要clean整个硬盘了……

HD Tune Pro测试了一下,性能还是挺给力的:

Barracuda 1TB

超越160GB的太多了:

16GB

到此,电脑全面升级完毕……