如何提升 TCP 传输数据的性能?详解

TCP 会保证每一个报文都能够抵达对方,它的机制是这样:报文发出去后,必须接收到对方返回的确认报文 ACK,如果迟迟未收到,就会超时重发该报文,直到收到对方的 ACK 为止

所以,TCP 报文发出去后,并不会立马从内存中删除,因为重传时还需要用到它

由于 TCP 是内核维护的,所以报文存放在内核缓冲区。如果连接非常多,我们可以通过 free 命令观察到 buff/cache 内存是会增大

如果 TCP 是每发送一个数据,都要进行一次确认应答。当上一个数据包收到了应答了, 再发送下一个。这个模式就有点像我和你面对面聊天,你一句我一句,但这种方式的缺点是效率比较低的

所以,这样的传输方式有一个缺点:数据包的往返时间越长,通信的效率就越低

要解决这一问题不难,并行批量发送报文,再批量确认报文即可

然而,这引出了另一个问题,发送方可以随心所欲的发送报文吗?当然这不现实,我们还得考虑接收方的处理能力

当接收方硬件不如发送方,或者系统繁忙、资源紧张时,是无法瞬间处理这么多报文的。于是,这些报文只能被丢掉,使得网络效率非常低

为了解决这种现象发生,TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是滑动窗口的由来

接收方根据它的缓冲区,可以计算出后续能够接收多少字节的报文,这个数字叫做接收窗口。当内核接收到报文时,必须用缓冲区存放它们,这样剩余缓冲区空间变小,接收窗口也就变小了;当进程调用read 函数后,数据被读入了用户空间,内核缓冲区就被清空,这意味着主机可以接收更多的报文,接收窗口就会变大

因此,接收窗口并不是恒定不变的,接收方会把当前可接收的大小放在 TCP 报文头部中的窗口字段,这样就可以起到窗口大小通知的作用

发送方的窗口等价于接收方的窗口吗?如果不考虑拥塞控制,发送方的窗口大小「约等于」接收方的窗口大小,因为窗口通知报文在网络传输是存在时延的,所以是约等于的关系

从上图中可以看到,窗口字段只有 2 个字节,因此它最多能表达65535 字节大小的窗口,也就是 64KB 大小

这个窗口大小最大值,在当今高速网络下,很明显是不够用的。所以后续有了扩充窗口的方法:在 TCP 选项字段定义了窗口扩大因子,用于扩大 TCP 通告窗口,其值大小是 2^14,这样就使 TCP 的窗口大小从16 位扩大为 30 位(2^16 * 2^ 14 = 2^30),所以此时窗口的最大值可以达到 1GB

Linux 中打开这一功能,需要把 tcp_window_scaling 配置设为 1(默认打开):

要使用窗口扩大选项,通讯双方必须在各自的 SYN 报文中发送这个选项:

  • 主动建立连接的一方在 SYN 报文中发送这个选项
  • 而被动建立连接的一方只有在收到带窗口扩大选项的 SYN 报文之后才能发送这个选项

这样看来,只要进程能及时地调用 read 函数读取数据,并且接收缓冲区配置得足够大,那么接收窗口就可以无限地放大,发送方也就无限地提升发送速度

这是不可能的,因为网络的传输能力是有限的,当发送方依据发送窗口,发送超过网络处理能力的报文时,路由器会直接丢弃这些报文。因此,缓冲区的内存并不是越大越好

如何确定最大传输速度?

在前面我们知道了 TCP 的传输速度,受制于发送窗口与接收窗口,以及网络设备传输能力。其中,窗口大小由内核缓冲区大小决定。如果缓冲区与网络传输能力匹配,那么缓冲区的利用率就达到了最大化

问题来了,如何计算网络的传输能力呢?

相信大家都知道网络是有「带宽」限制的,带宽描述的是网络传输能力,它与内核缓冲区的计量单位不同:

  • 带宽是单位时间内的流量,表达是「速度」,比如常见的带宽 100 MB/s
  • 缓冲区单位是字节,当网络速度乘以时间才能得到字节数

这里需要说一个概念,就是带宽时延积,它决定网络中飞行报文的大小,它的计算方式:

比如最大带宽是 100 MB/s,网络时延(RTT)是 10ms 时,意味着客户端到服务端的网络一共可以存放 100MB/s * 0.01s = 1MB 的字节

这个 1MB 是带宽和时延的乘积,所以它就叫「带宽时延积」(缩写为BDP,Bandwidth Delay Product)。同时,这 1MB 也表示「飞行中」的TCP 报文大小,它们就在网络线路、路由器等网络设备上。如果飞行报文超过了 1 MB,就会导致网络过载,容易丢包

由于发送缓冲区大小决定了发送窗口的上限,而发送窗口又决定了「已发送未确认」的飞行报文的上限。因此,发送缓冲区不能超过「带宽时延积」

发送缓冲区与带宽时延积的关系:

  • 如果发送缓冲区「超过」带宽时延积,超出的部分就没办法有效的网络传输,同时导致网络过载,容易丢包
  • 如果发送缓冲区「小于」带宽时延积,就不能很好的发挥出网络的传输效率

所以,发送缓冲区的大小最好是往带宽时延积靠近

怎样调整缓冲区大小?

在 Linux 中发送缓冲区和接收缓冲都是可以用参数调节的。设置完后,Linux 会根据你设置的缓冲区进行动态调节

1、调节发送缓冲区范围

先来看看发送缓冲区,它的范围通过 tcp_wmem 参数配置;

上面三个数字单位都是字节,它们分别表示:

  • 第一个数值是动态范围的最小值,4096 byte = 4K
  • 第二个数值是初始默认值,16384 byte ≈ 16K
  • 第三个数值是动态范围的最大值,4194304 byte = 4096K(4M)

发送缓冲区是自行调节的,当发送方发送的数据被确认后,并且没有新的数据要发送,就会把发送缓冲区的内存释放掉

2、调节接收缓冲区范围

而接收缓冲区的调整就比较复杂一些,先来看看设置接收缓冲区范围的 tcp_rmem 参数:

上面三个数字单位都是字节,它们分别表示:

  • 第一个数值是动态范围的最小值,表示即使在内存压力下也可以保证的最小接收缓冲区大小,4096 byte = 4K
  • 第二个数值是初始默认值,87380 byte ≈ 86K
  • 第三个数值是动态范围的最大值,6291456 byte = 6144K(6M)

接收缓冲区可以根据系统空闲内存的大小来调节接收窗口:

  • 如果系统的空闲内存很多,就可以自动把缓冲区增大一些,这样传给对方的接收窗口也会变大,因而提升发送方发送的传输数据数量
  • 反之,如果系统的内存很紧张,就会减少缓冲区,这虽然会降低传输效率,可以保证更多的并发连接正常工作

发送缓冲区的调节功能是自动开启的,而接收缓冲区则需要配置

tcp_moderate_rcvbuf 为 1 来开启调节功能:

3、调节 TCP 内存范围

接收缓冲区调节时,怎么知道当前内存是否紧张或充分呢?这是通过tcp_mem 配置完成的:

上面三个数字单位不是字节,而是「页面大小」,1 页表示 4KB,它们分别表示:

  • 当 TCP 内存小于第 1 个值时,不需要进行自动调节
  • 在第 1 和第 2 个值之间时,内核开始调节接收缓冲区的大小
  • 大于第 3 个值时,内核不再为 TCP 分配新内存,此时新连接是无法建立的

一般情况下这些值是在系统启动时根据系统内存数量计算得到的。根据当前 tcp_mem 最大内存页面数是 177120,当内存为 (177120 * 4) /1024K ≈ 692M 时,系统将无法为新的 TCP 连接分配内存,即 TCP 连接将被拒绝

4、根据实际场景调节的策略

在高并发服务器中,为了兼顾网速与大量的并发连接,我们应当保证缓冲区的动态调整的最大值达到带宽时延积,而最小值保持默认的 4K不变即可。而对于内存紧张的服务而言,调低默认值是提高并发的有效手段

同时,如果这是网络 IO 型服务器,那么,调大 tcp_mem 的上限可以让 TCP 连接使用更多的系统内存,这有利于提升并发能力。需要注意的是,tcp_wmem 和 tcp_rmem 的单位是字节,而 tcp_mem 的单位是页面大小。而且,千万不要在 socket 上直接设置 SO_SNDBUF 或者SO_RCVBUF,这样会关闭缓冲区的动态调整功能

相关推荐
Joey_Chen1 小时前
【What · Why · How】浅析select/poll/epoll与IO多路复用
linux·服务器
“αβ”2 小时前
线程安全的单例模式
linux·服务器·开发语言·c++·单例模式·操作系统·vim
zc-code2 小时前
HTTP性能优化实战:从协议到工具的全面加速指南
网络·网络协议·http·缓存·性能优化·html
gnawkhhkwang2 小时前
clock_nanosleep系统调用及示例
linux
破碎的南瓜3 小时前
OSPF笔记
网络·笔记·智能路由器
渡我白衣3 小时前
综合:日志的实现
linux
IsPrisoner3 小时前
深入分析计算机网络传输层和应用层面试题
网络·学习
德迅云安全杨德俊3 小时前
应用加速游戏盾的安全作用
网络·安全·游戏·ddos
嶔某3 小时前
网络:基础概念
linux·服务器·网络·c++
ArabySide3 小时前
【Linux】Ubuntu上安装.NET 9运行时与ASP.NET Core项目部署入门
linux·ubuntu·.net