千兆公网带宽的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。

Mbps≠MB/s

前几个星期,在学校无聊,抓起手机,看起了微博来,突然看到一个人的评论:

看了内容之后,我马上忍不住喷了……

为啥喷呢?开始说明原因之前,先来给大家介绍下数据的计量单位的换算关系吧:

8Bit=1Byte

1024Bit=1Kbit=128Byte

1024Kbit=1Mbit=128KByte

1024Mbit=1Gbit=128MByte

以此类推……

Bit和Byte之间的比值是8比1。

至于微博中所提到的Gbps、Mbps的意思就是Gbit/s,指的是带宽。

也就说,微博中所说的1Gbps(1Gbit/s)带宽,根据Bit和Byte的8比1的关系,理论上的最大传输速度是1Gbps(Gbit/s)/8=1024Mbps(Mbit/s)/8=128MB/s

说简单点的,就是用带宽除以八就得出真正的传输速度了!

目前64GB(64GByte)的SSD,读写速度都不下200MB/s,而128GB的就是64GB的SSD双倍带宽,速度有500MB/s习以为常!

因此,而微博中的那位仁兄说:“SSD读写没那么快。”他说SSD没那么快,很明显是认为1Gbps=1GB/s,这样是错误的。

==============

平时大家去开宽带时,那些工作人员都会说多少钱4M的,多少钱又8M的……其实这些指的都是带宽,很多不知道的小白,就误以为速度是4MB/s,或者8MB/s了……

4M的宽带,也就是4Mbps的带宽了,那么理论上的最大的下载速度就是4Mbps/8=0.5MB/s=512KB/s……

为啥说是理论上呢?目前中国网络环境不好,大多数是用电话线接入,线路老化,加上可能运营商超售……实际运营中,速度有带宽除以十以上的速度已经灰常好了!如果连除以十的速度都不到的话,去营业厅闹事吧……

因此,看了本文之后,希望大家不要再被中国电信、联通什么的人忽悠了。也不在再误会带宽和速度的关系了……