1
2
3
4
5
6
7
作者:李晓辉

联系方式:

1. 微信:Lxh_Chat

2. 邮箱:939958092@qq.com

前面几篇博客我们聊 Linux 性能调优时,更多关注的是 CPU、内存和磁盘。今天换个方向,来看看网络性能。

不知道你有没有遇到过这样的情况:服务器 CPU 使用率不高,内存也很充足,磁盘没有明显的 I/O 瓶颈,但业务访问就是慢。SSH 连接偶尔卡顿,接口响应时间忽高忽低,两台服务器之间传输文件,速度却怎么也上不去。

这时候,你可能会想到调整 Linux 网络参数。

比如增大 TCP 缓冲区、修改网卡队列、调整中断亲和性,甚至直接把几个 sysctl 参数改成网上推荐的“大值”。

先别急。

网络调优最怕的不是参数不会改,而是还没找到瓶颈,就开始到处改参数。

缓冲区太小,可能装不下足够多的在途数据;缓冲区太大,又可能让数据在队列里排队太久。网卡队列太小可能导致丢包,但一味增加队列长度,也不一定能解决 CPU 处理不过来的问题。建议先搞清楚网络数据是怎么传输的,再学习如何测量吞吐量、分析网络瓶颈,以及使用 BDP 估算合理的 TCP 缓冲区大小。

网络调优的第一步:先测量,再修改

假设现在有两台 RHEL 服务器。

host1 是业务服务器,host2 是测试客户端。两台机器之间的网络标称带宽是 1 Gbps,但实际传输文件时,速度只有几十 MB/s。

这时候,你最应该做的是什么?

不是马上修改 net.ipv4.tcp_rmem,而是先测量两台机器之间的实际网络吞吐量。

使用 iperf3 建立性能基线

iperf3 是一个常用的网络性能测试工具,可以在两台主机之间产生测试流量,测量 TCP 或 UDP 的吞吐量等指标。

先在两台机器上安装:

1
[root@host1 ~]# dnf install -y iperf3

再安装到host2上

1
[root@host2 ~]# dnf install -y iperf3

默认监听 TCP 5201 端口,防火墙开一下

1
2
3
4
[root@host1 ~]# firewall-cmd --add-port=5201/tcp
success
[root@host1 ~]# firewall-cmd --add-port=5201/tcp --permanent
success

在 host1 上启动服务端:

1
2
3
4
[root@host1 ~]# iperf3 -s 
-----------------------------------------------------------
Server listening on 5201 (test #1)
-----------------------------------------------------------

然后在 host2 上执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@host2 ~]# iperf3 -c 192.168.8.128 -t 10
Connecting to host 192.168.8.128, port 5201
[ 5] local 192.168.8.129 port 37390 connected to 192.168.8.128 port 5201
[ ID] Interval Transfer Bitrate Retr Cwnd
[ 5] 0.00-1.00 sec 758 MBytes 6.35 Gbits/sec 26 1.06 MBytes
[ 5] 1.00-2.00 sec 782 MBytes 6.56 Gbits/sec 0 1.31 MBytes
[ 5] 2.00-3.00 sec 781 MBytes 6.55 Gbits/sec 5 1.21 MBytes
[ 5] 3.00-4.00 sec 778 MBytes 6.53 Gbits/sec 3 1.25 MBytes
[ 5] 4.00-5.00 sec 753 MBytes 6.31 Gbits/sec 1 1.27 MBytes
[ 5] 5.00-6.00 sec 760 MBytes 6.38 Gbits/sec 3 1.31 MBytes
[ 5] 6.00-7.00 sec 733 MBytes 6.15 Gbits/sec 1 1.34 MBytes
[ 5] 7.00-8.00 sec 776 MBytes 6.51 Gbits/sec 2 1.38 MBytes
[ 5] 8.00-9.00 sec 741 MBytes 6.22 Gbits/sec 4 1.39 MBytes
[ 5] 9.00-10.00 sec 750 MBytes 6.28 Gbits/sec 2 1.41 MBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 7.44 GBytes 6.39 Gbits/sec 47 sender
[ 5] 0.00-10.00 sec 7.44 GBytes 6.39 Gbits/sec receiver

iperf Done.

其中:

  • -c host1:指定测试服务端。
  • -t 10:持续测试 10 秒。
  • iperf3 -s:启动服务端,等待客户端连接。

如果没有 DNS 解析,可以直接使用 host1 的 IP 地址。

字段含义
Interval统计的时间区间,每行这里是 1 秒
Transfer这个区间传输的数据量
Bitrate这个区间测得的平均传输速率
Retr发送端统计的 TCP 重传次数
CwndTCP 拥塞窗口大小

这里有个容易忽略的细节:输出中的 GBytes 和 Gbits/sec 采用十进制单位,而计算机内存中常见的 GiB、KiB 采用二进制单位。因此6.39 Gbits/sec 则相当于约 799 MB/s。

这意味着,在这次测试条件下,两台机器之间的 TCP 数据传输速率接近每秒 800 MB。

另外,iperf3 的默认 TCP 测试主要关注吞吐量。它不能单凭这一行输出,就告诉你网络延迟、抖动和丢包分别是多少。

如果想测试反方向的传输,可以使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@host2 ~]# iperf3 -c 192.168.8.128 -t 10 -R
Connecting to host 192.168.8.128, port 5201
Reverse mode, remote host 192.168.8.128 is sending
[ 5] local 192.168.8.129 port 54162 connected to 192.168.8.128 port 5201
[ ID] Interval Transfer Bitrate
[ 5] 0.00-1.00 sec 854 MBytes 7.16 Gbits/sec
[ 5] 1.00-2.00 sec 869 MBytes 7.29 Gbits/sec
[ 5] 2.00-3.00 sec 946 MBytes 7.93 Gbits/sec
[ 5] 3.00-4.00 sec 942 MBytes 7.90 Gbits/sec
[ 5] 4.00-5.00 sec 941 MBytes 7.89 Gbits/sec
[ 5] 5.00-6.00 sec 941 MBytes 7.89 Gbits/sec
[ 5] 6.00-7.00 sec 952 MBytes 7.99 Gbits/sec
[ 5] 7.00-8.00 sec 949 MBytes 7.96 Gbits/sec
[ 5] 8.00-9.00 sec 951 MBytes 7.98 Gbits/sec
[ 5] 9.00-10.00 sec 944 MBytes 7.92 Gbits/sec
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 9.07 GBytes 7.79 Gbits/sec 24 sender
[ 5] 0.00-10.00 sec 9.07 GBytes 7.79 Gbits/sec receiver

iperf Done.

-R 表示反向测试,即由服务端向客户端发送数据。

如果怀疑单个 TCP 流无法充分利用带宽,还可以测试多个并行连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
[root@host2 ~]# iperf3 -c 192.168.8.128 -t 10 -P 4
Connecting to host 192.168.8.128, port 5201
[ 5] local 192.168.8.129 port 46650 connected to 192.168.8.128 port 5201
[ 7] local 192.168.8.129 port 46658 connected to 192.168.8.128 port 5201
[ 9] local 192.168.8.129 port 46672 connected to 192.168.8.128 port 5201
[ 11] local 192.168.8.129 port 46682 connected to 192.168.8.128 port 5201
[ ID] Interval Transfer Bitrate Retr Cwnd
[ 5] 0.00-1.00 sec 238 MBytes 1.99 Gbits/sec 89 385 KBytes
[ 7] 0.00-1.00 sec 189 MBytes 1.59 Gbits/sec 65 215 KBytes
[ 9] 0.00-1.00 sec 116 MBytes 969 Mbits/sec 14 385 KBytes
[ 11] 0.00-1.00 sec 224 MBytes 1.87 Gbits/sec 66 419 KBytes
[SUM] 0.00-1.00 sec 767 MBytes 6.43 Gbits/sec 234
- - - - - - - - - - - - - - - - - - - - - - - - -
[ 5] 1.00-2.00 sec 170 MBytes 1.43 Gbits/sec 14 414 KBytes
[ 7] 1.00-2.00 sec 196 MBytes 1.64 Gbits/sec 13 293 KBytes
[ 9] 1.00-2.00 sec 114 MBytes 952 Mbits/sec 14 272 KBytes
[ 11] 1.00-2.00 sec 311 MBytes 2.62 Gbits/sec 35 297 KBytes
[SUM] 1.00-2.00 sec 790 MBytes 6.63 Gbits/sec 76
- - - - - - - - - - - - - - - - - - - - - - - - -
[ 5] 2.00-3.00 sec 165 MBytes 1.38 Gbits/sec 22 334 KBytes
[ 7] 2.00-3.00 sec 248 MBytes 2.08 Gbits/sec 27 273 KBytes
[ 9] 2.00-3.00 sec 126 MBytes 1.06 Gbits/sec 7 266 KBytes
[ 11] 2.00-3.00 sec 232 MBytes 1.94 Gbits/sec 27 308 KBytes
[SUM] 2.00-3.00 sec 770 MBytes 6.46 Gbits/sec 83
- - - - - - - - - - - - - - - - - - - - - - - - -
[ 5] 3.00-4.00 sec 132 MBytes 1.11 Gbits/sec 9 331 KBytes
[ 7] 3.00-4.00 sec 236 MBytes 1.98 Gbits/sec 26 300 KBytes
[ 9] 3.00-4.00 sec 108 MBytes 907 Mbits/sec 12 273 KBytes
[ 11] 3.00-4.00 sec 233 MBytes 1.96 Gbits/sec 12 498 KBytes
[SUM] 3.00-4.00 sec 709 MBytes 5.95 Gbits/sec 59
- - - - - - - - - - - - - - - - - - - - - - - - -
[ 5] 4.00-5.00 sec 195 MBytes 1.64 Gbits/sec 10 451 KBytes
[ 7] 4.00-5.00 sec 197 MBytes 1.65 Gbits/sec 11 451 KBytes
[ 9] 4.00-5.00 sec 68.9 MBytes 578 Mbits/sec 26 194 KBytes
[ 11] 4.00-5.00 sec 296 MBytes 2.48 Gbits/sec 28 297 KBytes
[SUM] 4.00-5.00 sec 756 MBytes 6.35 Gbits/sec 75
- - - - - - - - - - - - - - - - - - - - - - - - -
[ 5] 5.00-6.00 sec 131 MBytes 1.10 Gbits/sec 9 314 KBytes
[ 7] 5.00-6.00 sec 222 MBytes 1.86 Gbits/sec 23 403 KBytes
[ 9] 5.00-6.00 sec 120 MBytes 1.01 Gbits/sec 8 280 KBytes
[ 11] 5.00-6.00 sec 265 MBytes 2.22 Gbits/sec 12 322 KBytes
[SUM] 5.00-6.00 sec 739 MBytes 6.20 Gbits/sec 52
- - - - - - - - - - - - - - - - - - - - - - - - -
[ 5] 6.00-7.00 sec 108 MBytes 909 Mbits/sec 15 315 KBytes
[ 7] 6.00-7.00 sec 213 MBytes 1.79 Gbits/sec 28 255 KBytes
[ 9] 6.00-7.00 sec 147 MBytes 1.24 Gbits/sec 10 250 KBytes
[ 11] 6.00-7.00 sec 296 MBytes 2.48 Gbits/sec 3 622 KBytes
[SUM] 6.00-7.00 sec 764 MBytes 6.41 Gbits/sec 56
- - - - - - - - - - - - - - - - - - - - - - - - -
[ 5] 7.00-8.00 sec 97.2 MBytes 816 Mbits/sec 15 264 KBytes
[ 7] 7.00-8.00 sec 211 MBytes 1.77 Gbits/sec 15 354 KBytes
[ 9] 7.00-8.00 sec 158 MBytes 1.33 Gbits/sec 13 351 KBytes
[ 11] 7.00-8.00 sec 281 MBytes 2.36 Gbits/sec 23 426 KBytes
[SUM] 7.00-8.00 sec 748 MBytes 6.27 Gbits/sec 66
- - - - - - - - - - - - - - - - - - - - - - - - -
[ 5] 8.00-9.00 sec 89.8 MBytes 753 Mbits/sec 9 218 KBytes
[ 7] 8.00-9.00 sec 253 MBytes 2.12 Gbits/sec 15 431 KBytes
[ 9] 8.00-9.00 sec 160 MBytes 1.34 Gbits/sec 13 339 KBytes
[ 11] 8.00-9.00 sec 260 MBytes 2.18 Gbits/sec 13 492 KBytes
[SUM] 8.00-9.00 sec 762 MBytes 6.40 Gbits/sec 50
- - - - - - - - - - - - - - - - - - - - - - - - -
[ 5] 9.00-10.00 sec 101 MBytes 849 Mbits/sec 15 219 KBytes
[ 7] 9.00-10.00 sec 234 MBytes 1.97 Gbits/sec 33 321 KBytes
[ 9] 9.00-10.00 sec 159 MBytes 1.33 Gbits/sec 12 307 KBytes
[ 11] 9.00-10.00 sec 294 MBytes 2.47 Gbits/sec 32 358 KBytes
[SUM] 9.00-10.00 sec 789 MBytes 6.62 Gbits/sec 92
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 1.39 GBytes 1.20 Gbits/sec 207 sender
[ 5] 0.00-10.00 sec 1.39 GBytes 1.20 Gbits/sec receiver
[ 7] 0.00-10.00 sec 2.15 GBytes 1.84 Gbits/sec 256 sender
[ 7] 0.00-10.00 sec 2.15 GBytes 1.84 Gbits/sec receiver
[ 9] 0.00-10.00 sec 1.25 GBytes 1.07 Gbits/sec 129 sender
[ 9] 0.00-10.00 sec 1.25 GBytes 1.07 Gbits/sec receiver
[ 11] 0.00-10.00 sec 2.63 GBytes 2.26 Gbits/sec 251 sender
[ 11] 0.00-10.00 sec 2.63 GBytes 2.26 Gbits/sec receiver
[SUM] 0.00-10.00 sec 7.42 GBytes 6.37 Gbits/sec 843 sender
[SUM] 0.00-10.00 sec 7.41 GBytes 6.36 Gbits/sec receiver

iperf Done.
[root@host2 ~]#

-P 4 表示使用 4 个并行客户端流。

不过,多个流的总吞吐量上去了,不代表单个业务连接的性能也一定改善了。并行连接可能掩盖单流的窗口限制、拥塞控制或 CPU 处理瓶颈。

为什么一定要先建立基线?

假设你修改了 TCP 缓冲区参数,测试结果从 300 Mbps 上升到 700 Mbps。这至少说明,在当前测试条件下,这次调整与吞吐量提升有关。

但如果你没有修改前的测试结果,只知道修改后的吞吐量是 700 Mbps,就很难判断这次调优究竟有没有效果。

因此,建议每次调优都遵循同一个流程:

flowchart TD
    A["确认网络拓扑和链路速率"] --> B["记录修改前的性能基线"]
    B --> C["检查丢包、重传、CPU和队列"]
    C --> D["提出一个明确的调优假设"]
    D --> E["只修改相关参数"]
    E --> F["重复相同测试"]
    F --> G{"吞吐量或延迟改善了吗?"}
    G -->|是| H["验证业务负载下的效果"]
    G -->|否| I["恢复参数并继续排查"]

这里还有一个很重要的原则:不要只看吞吐量,还要看延迟、丢包、CPU 使用率和业务响应时间。 网络性能不是单一指标。

比如吞吐量提高了,但请求延迟从 5 ms 上升到 100 ms,对于大文件传输业务来说可能可以接受,对于在线交易、API 调用和交互式 SSH 来说却未必是好事。

网络数据到底是怎么传输的?

要理解为什么网络调优会涉及缓冲区、中断和 CPU,首先得知道一个数据包从应用程序出发,最终到达另一台服务器,中间经历了什么。

发送数据:从应用程序到网卡

假设你在服务器上运行一个应用程序,需要通过 TCP 向另一台服务器发送数据。

大致过程如下:

flowchart LR
    A["应用程序调用 send()"] --> B["TCP 发送缓冲区"]
    B --> C["TCP 分段、处理序号和确认机制"]
    C --> D["IP 层封装"]
    D --> E["网络设备队列和 qdisc"]
    E --> F["网卡发送队列 / TX Ring"]
    F --> G["网卡通过 DMA 读取数据"]
    G --> H["数据从网卡发出"]

这里需要注意,实际发送路径会受到内核版本、网络设备、驱动、卸载功能以及虚拟化环境的影响,上图展示的是常见的逻辑过程。

应用程序调用 send(),并不意味着数据立刻就从网卡发出。数据可能先进入套接字发送缓冲区,再经过 TCP、IP 等协议层处理,进入网络设备的发送队列,最后由驱动和网卡完成发送。

如果某一层处理不过来,数据就可能在对应位置排队。

例如:

  • TCP 发送缓冲区受限,可能影响发送端维持足够多的在途数据。
  • 网络设备队列拥堵,可能增加排队延迟。
  • 网卡发送队列资源不足,可能限制发送效率。
  • CPU 忙于处理其他任务,也可能无法及时完成网络协议栈处理。

所以,网络传输慢,不一定是网卡带宽不够,也可能是某一层的处理能力或者队列资源不足。

接收数据:网卡收到数据后发生了什么?

接收方向的过程也很有意思。

flowchart TD
    A["网卡收到数据帧"] --> B["DMA 将数据写入内存"]
    B --> C["硬件中断 / NAPI 调度"]
    C --> D["内核通过软中断等机制处理数据包"]
    D --> E["IP 层处理"]
    E --> F["TCP 校验、排序和重组"]
    F --> G["TCP 接收缓冲区"]
    G --> H["应用程序调用 recv() 读取"]

这里有一个容易误解的地方。

并不是每收到一个数据包,CPU 就一定要处理一次独立的硬件中断。

现代 Linux 网络驱动通常会使用 NAPI 机制:网卡通过中断通知内核有数据需要处理,随后内核可以采用轮询方式批量处理数据包,从而降低高流量场景下的中断开销。

但这也意味着,如果数据包到达速度远远超过 CPU 的处理能力,接收队列、软中断处理和应用程序读取都可能出现瓶颈。

这就是为什么排查网络性能时,不能只盯着网卡速率,还要看 CPU、队列和协议栈的处理能力。

网络性能的三个常见问题:活锁、环形缓冲区和 CPU 争用

了解了数据传输过程,我们再来看三个与网络调优密切相关的问题。

什么是网络活锁?

先用一个生活中的例子理解。

假设你在一家餐厅负责接单。顾客不断进来,你每处理完一份订单,就马上有新顾客进来。你忙着接单,连已经接好的订单都没时间交给厨房。

结果看起来你一直很忙,实际上真正完成的工作却很少。

网络处理中也可能出现类似的问题。

当数据包到达速度非常快时,内核可能需要花费大量 CPU 时间处理接收事件、中断或软中断。如果这些处理持续占用 CPU,其他任务就可能得不到足够的运行机会。

在某些极端情况下,系统会表现出类似活锁的现象:CPU 非常忙碌,但有效工作进展很少。

需要注意,网络活锁并不是所有高负载网络场景的统称。CPU 忙、软中断占用高或出现丢包,并不自动意味着发生了活锁。

最小化系统没有 mpstat,可以安装相应工具包:

1
[root@host2 ~]# dnf install -y sysstat

运行看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@host2 ~]# mpstat -P ALL 1
Linux 6.12.0-55.12.1.el10_0.x86_64 (host2) 10/12/2026 _x86_64_ (2 CPU)

11:04:33 PM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
11:04:34 PM all 0.00 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00 99.00
11:04:34 PM 0 0.00 0.00 0.00 0.00 0.00 0.99 0.00 0.00 0.00 99.01
11:04:34 PM 1 0.00 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00 99.00

11:04:34 PM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
11:04:35 PM all 0.00 0.00 0.50 0.00 0.00 0.00 0.00 0.00 0.00 99.50
11:04:35 PM 0 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 99.00
11:04:35 PM 1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00

11:04:35 PM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
11:04:36 PM all 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
11:04:36 PM 0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
11:04:36 PM 1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00

11:04:36 PM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
11:04:37 PM all 0.00 0.00 0.00 0.00 0.50 0.00 0.00 0.00 0.00 99.50
11:04:37 PM 0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
11:04:37 PM 1 0.00 0.00 0.00 0.00 0.99 0.00 0.00 0.00 0.00 99.01

Average: CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
Average: all 0.00 0.00 0.12 0.00 0.12 0.25 0.00 0.00 0.00 99.50
Average: 0 0.00 0.00 0.25 0.00 0.00 0.25 0.00 0.00 0.00 99.50
Average: 1 0.00 0.00 0.00 0.00 0.25 0.25 0.00 0.00 0.00 99.50

再查看软中断统计:

1
cat /proc/softirqs

重点关注 NET_RX 和 NET_TX。

  • NET_RX:与网络接收软中断处理有关。
  • NET_TX:与网络发送软中断处理有关。

如果某个 CPU 的网络软中断计数增长特别快,同时该 CPU 的软中断占用很高,就值得进一步检查网卡队列分布、中断亲和性、RPS/RFS 配置以及流量模式。

当然,计数增长快本身不是故障。高流量服务器本来就会产生大量网络处理事件,必须结合 CPU 利用率、丢包和实际业务表现一起判断。

网卡环形缓冲区为什么会影响性能?

网卡通常会使用环形队列,也就是常说的 Ring Buffer。可以把它理解为一个循环使用的收发任务列表。网卡和驱动通过描述符记录哪些缓冲区可以使用、哪些数据已经到达、哪些发送任务尚未完成。如果接收数据持续到达,而驱动和内核来不及处理,接收资源就可能耗尽,进而导致丢包。

先查看网卡名称:

1
2
3
4
[root@host1 ~]# ip -br link
lo UNKNOWN 00:00:00:00:00:00 <LOOPBACK,UP,LOWER_UP>
ens160 UP 00:0c:29:29:c1:31 <BROADCAST,MULTICAST,UP,LOWER_UP>

先安装调试工具

1
[root@host1 ~]# dnf install ethtool

假设网卡名称为 ens160,可以查看驱动信息和队列设置:

1
2
3
4
5
6
7
8
9
10
11
[root@host1 ~]# ethtool -i ens160
driver: vmxnet3
version: 1.9.0.0-k-NAPI
firmware-version:
expansion-rom-version:
bus-info: 0000:03:00.0
supports-statistics: yes
supports-test: no
supports-eeprom-access: no
supports-register-dump: yes
supports-priv-flags: no
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@host1 ~]# ethtool -g ens160
Ring parameters for ens160:
Pre-set maximums:
RX: 4096
RX Mini: n/a
RX Jumbo: 4096
TX: 4096
TX push buff len: n/a
HDS thresh: n/a
Current hardware settings:
RX: 1024
RX Mini: n/a
RX Jumbo: 512
TX: 512
RX Buf Len: n/a
CQE Size: n/a
TX Push: off
RX Push: off
TX push buff len: n/a
TCP data split: n/a
HDS thresh: n/a

其中:

  • ethtool -i:查看网卡驱动和相关信息。
  • ethtool -g:查看网卡支持的 Ring Buffer 参数,以及当前设置和最大支持值。

如果驱动支持,也可以查看统计信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@host1 ~]# ethtool -S ens160
NIC statistics:
Tx Queue#: 0
TSO pkts tx: 6723440
TSO bytes tx: 10176253657
ucast pkts tx: 7095418
ucast bytes tx: 10210204467
mcast pkts tx: 11
mcast bytes tx: 738
bcast pkts tx: 1
bcast bytes tx: 324
pkts tx err: 0
pkts tx discard: 0
drv dropped tx total: 0
too many frags: 0
giant hdr: 0
hdr err: 0
tso: 0
ring full: 0
pkts linearized: 0
hdr cloned: 0
giant hdr: 0
xdp xmit: 0
xdp xmit err: 0
Tx Queue#: 1
...

不同驱动提供的统计项并不完全相同。可以重点寻找带有 drop、miss、no_buffer、overrun 等含义的计数器,但一定要结合具体驱动的文档判断其含义。如果确实发现接收队列资源不足,而且驱动支持更大的队列,可以评估是否增大 Ring Buffer。

假设驱动明确支持接收队列深度 2048,可以尝试:

1
ethtool -G ens160 rx 2048

这里的 2048 只是示例,不能直接照搬到所有网卡。

增大 Ring Buffer 也有代价:队列更深,可能让更多数据等待处理,进而增加排队延迟和内存占用。

而且,如果真正的问题是 CPU 根本处理不过来,那么单纯增加队列深度,可能只是让数据包在队列里多等一会儿。

因此,先确认有没有队列资源不足,再考虑增大 Ring Buffer。

CPU 争用与中断亲和性

网络处理同样需要 CPU。一台服务器可能有多个 CPU 核心,而网卡也可能提供多个接收和发送队列。驱动会根据硬件能力、中断配置和系统设置,将网络处理任务分配到不同的 CPU 上。

如果流量集中在少数队列,或者网络处理主要落在某一个繁忙的 CPU 上,就可能出现部分核心过载、其他核心却比较空闲的情况。

查看中断分布:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
[root@host1 ~]# cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
0: 43 0 0 0 IR-IO-APIC 2-edge timer
1: 0 9 0 0 IR-IO-APIC 1-edge i8042
8: 0 0 0 0 IR-IO-APIC 8-edge rtc0
9: 0 0 0 0 IR-IO-APIC 9-fasteoi acpi
12: 15 0 0 0 IR-IO-APIC 12-edge i8042
14: 0 0 0 0 IR-IO-APIC 14-edge ata_piix
15: 0 0 0 0 IR-IO-APIC 15-edge ata_piix
16: 0 0 0 0 IR-IO-APIC 16-fasteoi ehci_hcd:usb1, vmwgfx
18: 0 0 0 64 IR-IO-APIC 18-fasteoi uhci_hcd:usb2
19: 0 0 0 0 IR-IO-APIC 19-fasteoi snd_ens1371
24: 0 0 0 0 IR-PCI-MSI-0000:00:15.0 0-edge PCIe PME, pciehp
25: 0 0 0 0 IR-PCI-MSI-0000:00:15.1 0-edge PCIe PME, pciehp
26: 0 0 0 0 IR-PCI-MSI-0000:00:15.2 0-edge PCIe PME, pciehp
27: 0 0 0 0 IR-PCI-MSI-0000:00:15.3 0-edge PCIe PME, pciehp
28: 0 0 0 0 IR-PCI-MSI-0000:00:15.4 0-edge PCIe PME, pciehp
29: 0 0 0 0 IR-PCI-MSI-0000:00:15.5 0-edge PCIe PME, pciehp
30: 0 0 0 0 IR-PCI-MSI-0000:00:15.6 0-edge PCIe PME, pciehp
31: 0 0 0 0 IR-PCI-MSI-0000:00:15.7 0-edge PCIe PME, pciehp
32: 0 0 0 0 IR-PCI-MSI-0000:00:16.0 0-edge PCIe PME, pciehp
33: 0 0 0 0 IR-PCI-MSI-0000:00:16.1 0-edge PCIe PME, pciehp
34: 0 0 0 0 IR-PCI-MSI-0000:00:16.2 0-edge PCIe PME, pciehp
35: 0 0 0 0 IR-PCI-MSI-0000:00:16.3 0-edge PCIe PME, pciehp
36: 0 0 0 0 IR-PCI-MSI-0000:00:16.4 0-edge PCIe PME, pciehp
37: 0 0 0 0 IR-PCI-MSI-0000:00:16.5 0-edge PCIe PME, pciehp
38: 0 0 0 0 IR-PCI-MSI-0000:00:16.6 0-edge PCIe PME, pciehp
39: 0 0 0 0 IR-PCI-MSI-0000:00:16.7 0-edge PCIe PME, pciehp
40: 0 0 0 0 IR-PCI-MSI-0000:00:17.0 0-edge PCIe PME, pciehp
41: 0 0 0 0 IR-PCI-MSI-0000:00:17.1 0-edge PCIe PME, pciehp
42: 0 0 0 0 IR-PCI-MSI-0000:00:17.2 0-edge PCIe PME, pciehp
43: 0 0 0 0 IR-PCI-MSI-0000:00:17.3 0-edge PCIe PME, pciehp
44: 0 0 0 0 IR-PCI-MSI-0000:00:17.4 0-edge PCIe PME, pciehp
45: 0 0 0 0 IR-PCI-MSI-0000:00:17.5 0-edge PCIe PME, pciehp
46: 0 0 0 0 IR-PCI-MSI-0000:00:17.6 0-edge PCIe PME, pciehp
47: 0 0 0 0 IR-PCI-MSI-0000:00:17.7 0-edge PCIe PME, pciehp
48: 0 0 0 0 IR-PCI-MSI-0000:00:18.0 0-edge PCIe PME, pciehp
49: 0 0 0 0 IR-PCI-MSI-0000:00:18.1 0-edge PCIe PME, pciehp
50: 0 0 0 0 IR-PCI-MSI-0000:00:18.2 0-edge PCIe PME, pciehp
51: 0 0 0 0 IR-PCI-MSI-0000:00:18.3 0-edge PCIe PME, pciehp
52: 0 0 0 0 IR-PCI-MSI-0000:00:18.4 0-edge PCIe PME, pciehp
53: 0 0 0 0 IR-PCI-MSI-0000:00:18.5 0-edge PCIe PME, pciehp
54: 0 0 0 0 IR-PCI-MSI-0000:00:18.6 0-edge PCIe PME, pciehp
55: 0 0 0 0 IR-PCI-MSI-0000:00:18.7 0-edge PCIe PME, pciehp
56: 49 130 167 433 IR-PCI-MSI-0000:02:03.0 0-edge ahci[0000:02:03.0]
57: 0 0 36 0 IR-PCI-MSIX-0000:0b:00.0 0-edge nvme0q0
58: 2090 0 0 0 IR-PCI-MSIX-0000:0b:00.0 1-edge nvme0q1
59: 0 3539 0 0 IR-PCI-MSIX-0000:0b:00.0 2-edge nvme0q2
60: 0 0 673 0 IR-PCI-MSIX-0000:0b:00.0 3-edge nvme0q3
61: 0 0 0 842 IR-PCI-MSIX-0000:0b:00.0 4-edge nvme0q4
62: 0 0 0 0 IR-PCI-MSIX-0000:0b:00.0 5-edge nvme0q5
63: 0 0 0 0 IR-PCI-MSIX-0000:0b:00.0 6-edge nvme0q6
64: 0 0 0 0 IR-PCI-MSIX-0000:0b:00.0 7-edge nvme0q7
65: 0 0 0 0 IR-PCI-MSIX-0000:0b:00.0 8-edge nvme0q8
66: 0 0 0 0 IR-PCI-MSIX-0000:0b:00.0 9-edge nvme0q9
67: 0 0 0 0 IR-PCI-MSIX-0000:0b:00.0 10-edge nvme0q10
68: 0 0 0 0 IR-PCI-MSIX-0000:0b:00.0 11-edge nvme0q11
69: 0 0 0 0 IR-PCI-MSIX-0000:0b:00.0 12-edge nvme0q12
70: 0 0 0 0 IR-PCI-MSIX-0000:0b:00.0 13-edge nvme0q13
71: 0 0 0 0 IR-PCI-MSIX-0000:0b:00.0 14-edge nvme0q14
72: 0 0 0 0 IR-PCI-MSIX-0000:0b:00.0 15-edge nvme0q15
73: 0 0 0 0 IR-PCI-MSIX-0000:00:07.7 0-edge vmw_vmci
74: 0 0 0 0 IR-PCI-MSIX-0000:00:07.7 1-edge vmw_vmci
75: 0 1 0 0 IR-PCI-MSIX-0000:00:07.7 2-edge vmw_vmci
76: 207158 3181 0 45 IR-PCI-MSIX-0000:03:00.0 0-edge ens160-rxtx-0
77: 2481 10 99110 4 IR-PCI-MSIX-0000:03:00.0 1-edge ens160-rxtx-1
78: 0 109100 786 0 IR-PCI-MSIX-0000:03:00.0 2-edge ens160-rxtx-2
79: 1 9881 8 35818 IR-PCI-MSIX-0000:03:00.0 3-edge ens160-rxtx-3
80: 0 0 0 0 IR-PCI-MSIX-0000:03:00.0 4-edge ens160-event-4
NMI: 0 0 0 0 Non-maskable interrupts
LOC: 54228 46055 48973 45855 Local timer interrupts
SPU: 0 0 0 0 Spurious interrupts
PMI: 0 0 0 0 Performance monitoring interrupts
IWI: 1 1 0 1 IRQ work interrupts
RTR: 0 0 0 0 APIC ICR read retries
RES: 1850 2228 1488 1516 Rescheduling interrupts
CAL: 26939 64999 61142 54002 Function call interrupts
TLB: 102 162 60 79 TLB shootdowns
TRM: 0 0 0 0 Thermal event interrupts
THR: 0 0 0 0 Threshold APIC interrupts
DFR: 0 0 0 0 Deferred Error APIC interrupts
MCE: 0 0 0 0 Machine check exceptions
MCP: 5 6 6 6 Machine check polls
ERR: 0
MIS: 0
PIN: 0 0 0 0 Posted-interrupt notification event
NPI: 0 0 0 0 Nested posted-interrupt event
PIW: 0 0 0 0 Posted-interrupt wakeup event
PMN: 0 0 0 0 Posted MSI notification event

如果某个网卡队列对应的中断几乎全部集中在 CPU0,而 CPU0 又承担了大量其他任务,就有必要检查中断分配是否合理。

不过,这里不能简单地得出“中断和产生中断的进程必须放在同一个 CPU 上”的结论。

现代网络栈中,中断处理、NAPI 轮询、软中断、RPS/RFS 和应用程序运行位置之间的关系比较复杂。把它们放在同一个 CPU 上,有时可以改善缓存局部性;但如果该 CPU 已经很忙,也可能适得其反。

生产环境中,应该结合以下因素进行判断:

  • 网卡是否支持多队列。
  • 各队列的流量是否均衡。
  • IRQ 和软中断是否集中在少数 CPU。
  • 应用程序线程的 CPU 亲和性。
  • NUMA 节点与网卡所在位置。
  • 调整前后的延迟、吞吐量和 CPU 使用率。

RHEL 网络缓冲区:这些参数到底管什么?

前面提到了网络队列和缓冲区,现在来看最常见的一组内核参数。

先把当前配置查出来:

1
2
3
4
5
6
sysctl net.core.rmem_max
sysctl net.core.wmem_max
sysctl net.ipv4.tcp_rmem
sysctl net.ipv4.tcp_wmem
sysctl net.ipv4.tcp_mem
sysctl net.ipv4.udp_mem

这些参数看起来相似,但作用范围并不一样。

4.1 系统级 TCP 和 UDP 内存限制

先看:

1
2
3
4
5
[root@host1 ~]# sysctl net.ipv4.tcp_mem
net.ipv4.tcp_mem = 93090 124122 186180
[root@host1 ~]# sysctl net.ipv4.udp_mem
net.ipv4.udp_mem = 186183 248244 372366

这两个参数与 TCP、UDP 协议栈可使用的内存管理有关。

在常见的 Linux 内核实现中,tcp_mem 和 udp_mem 使用三个数值,分别表示不同的内存压力阈值。

以 tcp_mem 为例:

1
net.ipv4.tcp_mem = 123456 164608 246912

这里的数字仅为示例。

三个数值分别表示:

  • 第一个值:低水位,内存使用量低于该值时,通常不会因为这一机制进入内存压力状态。
  • 第二个值:压力水位,超过该值后,TCP 内存管理会更加积极地控制内存使用。
  • 第三个值:高水位,用于限制 TCP 协议栈的整体内存使用。

这里的单位通常是内存页,而不是字节。

可以使用下面的命令查看系统页大小:

1
2
[root@host1 ~]# getconf PAGESIZE
4096

在常见的 x86_64 RHEL 系统上,页大小通常为 4096 字节。

假设某个阈值为 100000 页,那么对应的字节数就是:

1
100000 × 4096 = 409600000 Bytes

约为 390.6 MiB。

但要注意,不能简单地把 tcp_mem 的第三个值理解为整个系统所有 TCP 连接能够使用的总内存,也不能认为超过第二个值就会立即丢弃所有数据。

它们属于协议栈的内存压力管理机制,具体表现还会受到内核版本、连接状态和内存管理策略影响。

另外,默认值通常会根据系统内存等因素计算或初始化,实际数值应以当前系统的 sysctl 输出为准。

net.core.rmem_max 和 net.core.wmem_max

再来看两个更常见的参数:

1
2
3
4
5
[root@host1 ~]# sysctl net.core.rmem_max
net.core.rmem_max = 212992
[root@host1 ~]# sysctl net.core.wmem_max
net.core.wmem_max = 212992

它们分别与套接字接收缓冲区和发送缓冲区的最大设置值有关。

简单理解:

  • net.core.rmem_max:套接字接收缓冲区的上限之一。
  • net.core.wmem_max:套接字发送缓冲区的上限之一。

为什么说是“上限之一”?

因为 Linux 套接字缓冲区的实际行为还与 TCP 专用参数、自动调优机制、应用程序设置以及内核版本有关。

例如,应用程序可能通过 setsockopt() 设置套接字缓冲区,也可能使用默认值让内核自动管理。

所以,即使你把 rmem_max 设置得很大,也不意味着每一个 TCP 连接都会立即分配这么多内存。

同样,系统里有很多 TCP 连接时,如果每个连接都大量使用缓冲区,整体内存占用也可能明显增加。

net.ipv4.tcp_rmem 和 net.ipv4.tcp_wmem

再看 TCP 专用参数:

1
2
3
4
net.ipv4.tcp_rmem = 4096        131072  6291456
[root@host1 ~]# sysctl net.ipv4.tcp_wmem
net.ipv4.tcp_wmem = 4096 16384 4194304

通常可以看到三个数值,例如:

1
2
net.ipv4.tcp_rmem = 4096 131072 6291456
net.ipv4.tcp_wmem = 4096 16384 4194304

这里同样只是示例,不代表所有 RHEL 版本的默认值。

这三个数值通常分别对应:

  • 最小值。
  • 默认值。
  • 自动调优可使用的最大值。

对于 TCP 接收缓冲区,tcp_rmem 决定了 TCP 接收缓冲区的相关范围;对于发送缓冲区,tcp_wmem 则决定发送缓冲区的相关范围。

Linux TCP 自动调优机制可以根据连接状况动态调整缓冲区使用量,而不是始终固定使用第二个数值。

需要特别注意,TCP 缓冲区大小并不是简单地由这几个参数中的某一个独立决定的。套接字选项、全局限制和内核的具体实现都会影响最终结果。

如果希望查看窗口缩放功能是否开启,可以执行:

1
2
[root@host1 ~]# sysctl net.ipv4.tcp_window_scaling
net.ipv4.tcp_window_scaling = 1

后面我们还会讲它与 TCP 窗口大小的关系。

先记住一个原则:缓冲区参数定义的是可用范围和限制,不代表连接一定会使用到最大值。

重点来了:什么是 BDP?为什么网络调优离不开它?

前面介绍了很多参数,但究竟应该把 TCP 缓冲区设置成多大?

如果只是根据网上的经验,把缓冲区设置成 16 MiB、64 MiB,甚至 256 MiB,显然不够严谨。

这时候就需要用到一个重要概念:BDP。

BDP 的英文是 Bandwidth-Delay Product,中文通常称为带宽延迟乘积。

它描述的是:在给定带宽和往返时延的情况下,链路上大约可以容纳多少尚未完成确认的在途数据。

计算公式为:

1
BDP = 带宽 × 往返时延(RTT)

注意,带宽与数据量必须使用一致的单位。如果带宽使用 bit/s,那么计算得到的 BDP 就是 bit;如果需要字节数,还要除以 8。

用高速公路理解 BDP

我们可以把网络想象成一条高速公路。

  • 带宽:相当于高速公路的车道通行能力。
  • RTT:相当于一辆车从出发到收到返回确认所需的时间。
  • 在途数据:相当于已经上路、但发送方还没有收到确认的数据。

如果高速公路通行能力很强,但车辆往返时间很长,那么想要充分利用这条高速公路,就必须让足够多的车辆同时在路上。

网络也是一样。

假设链路带宽是 1 Gbps,RTT 是 100 ms。

如果发送方每次只能发送很少的数据,然后就必须等待接收方确认,那么发送方就会频繁等待,链路的利用率自然上不去。

而当允许更多数据在途时,发送方就可以在等待确认的同时继续发送数据,更充分地利用链路带宽。

这就是 BDP 的核心意义。

先测量 RTT

我们可以先使用 ping 观察两台主机之间的往返时延:

1
ping -c 10 host2

假设结果中的统计信息如下:

1
rtt min/avg/max/mdev = 0.335/0.341/0.347/0.015 ms

这里:

  • min:最小往返时延。
  • avg:平均往返时延。
  • max:最大往返时延。
  • mdev:往返时延的离散程度指标。

本例平均 RTT 为 0.341 ms。

换算成秒:

1
0.341 ms = 0.000341 s

需要注意,ping 测量的是 ICMP 往返时延,不一定与 TCP 业务的实际 RTT 完全相同。对于高精度调优,最好结合真实 TCP 连接的 RTT 统计来分析。

计算 1 Gbps 链路的 BDP

现在假设:

  • 带宽:1 Gbps。
  • RTT:0.341 ms。

计算过程如下:

1
2
3
4
5
6
7
8
9
BDP = 带宽 × RTT

= 1,000,000,000 bit/s × 0.000341 s

= 341,000 bit

= 42,625 Bytes

≈ 41.63 KiB

这意味着,在上述带宽和 RTT 条件下,链路的 BDP 约为 41.63 KiB。

也就是说,如果希望单个 TCP 流持续以 1 Gbps 的速率发送数据,并且 RTT 约为 0.341 ms,那么发送方通常需要允许大约 41.63 KiB 的数据处于在途状态。

但这里有一个很容易犯的错误。

BDP 并不等于 TCP 缓冲区应该设置的唯一正确值。

它是估算在途数据需求的重要参考,而不是一条可以直接套用的参数配置公式。

实际需要的缓冲区还会受到 TCP 窗口、接收端处理速度、丢包、拥塞控制算法、套接字实现以及应用程序读写方式影响。

如果要充分利用一条高带宽、长 RTT 的链路,实际可用的 TCP 窗口通常至少需要能够覆盖相应的 BDP。

反过来,如果链路的 BDP 很小,却将所有连接的缓冲区都设置得特别大,就可能增加内存占用和排队延迟。

同样是 1 Gbps,为什么不同网络需要不同的缓冲区?

我们再看一个例子。

假设两条网络链路的带宽都是 1 Gbps。

第一条链路的 RTT 是 0.5 ms,第二条链路的 RTT 是 50 ms。

它们的 BDP 分别是:

第一条链路:

1
2
3
1,000,000,000 × 0.0005 ÷ 8
= 62,500 Bytes
≈ 61.04 KiB

第二条链路:

1
2
3
1,000,000,000 × 0.05 ÷ 8
= 6,250,000 Bytes
≈ 5.96 MiB

可以看到,两条链路的带宽相同,但 BDP 相差了 100 倍。

这也解释了为什么数据中心内的低延迟网络,与跨地域、跨数据中心的长距离网络,可能需要完全不同的 TCP 窗口和缓冲区配置。

所以,网络调优不能只看网卡标称带宽,还要看 RTT。

TCP 窗口缩放:为什么 64 KiB 有时远远不够?

现在已经知道 BDP 代表在途数据需求,接下来就要了解 TCP 窗口。

TCP 并不是每发送一个数据包,都必须等待对方确认后才能继续发送。

为了提高传输效率,TCP 允许发送方在收到确认之前,继续发送一定数量的数据。

这部分可以发送、但尚未得到确认的数据量,受到发送窗口、接收窗口和拥塞控制等因素共同约束。

其中,接收窗口用于反映接收端愿意接收多少数据;拥塞窗口则用于控制发送方认为网络可以承载多少在途数据。

最终能够发送的数据量,不能简单超过这两者所允许的范围。

传统 TCP 窗口为什么有 64 KiB 的限制?

传统 TCP 首部中的接收窗口字段只有 16 位。

因此,未使用窗口缩放选项时,窗口字段能够表示的最大值是:

1
2^16 - 1 = 65535 Bytes

也就是接近 64 KiB。

对于低带宽、低延迟网络,这通常不是特别大的问题。

但是,对于高速、长距离链路,64 KiB 可能远远不够。

举个例子,假设带宽为 1 Gbps,RTT 为 50 ms。

前面算过,这条链路的 BDP 约为 5.96 MiB。

如果 TCP 接收窗口只能达到约 64 KiB,即使网络本身没有拥塞,单个连接也可能因为窗口限制而无法充分利用带宽。

这里说的是窗口限制可能成为瓶颈,而不是所有情况下都必然只能跑出某个固定速度。

TCP 窗口缩放是怎么解决这个问题的?

为了解决这个限制,TCP 引入了窗口缩放选项。

在 RHEL 上,可以查看相关参数:

1
sysctl net.ipv4.tcp_window_scaling

常见的配置是:

1
net.ipv4.tcp_window_scaling = 1

值为 1 表示启用 TCP 窗口缩放功能。

在 TCP 三次握手期间,双方可以协商窗口缩放参数。协商成功后,后续报文中的窗口字段就可以通过缩放因子表示更大的接收窗口。

因此,实际接收窗口可以远远超过传统的 64 KiB 限制。

但要注意两点:

第一,双方必须在连接建立时支持并协商窗口缩放。单方面修改配置,不会让已经建立的连接重新协商这个选项。

第二,窗口缩放只是解决接收窗口表示范围的问题,并不意味着实际窗口一定会自动增长到某个固定大小。缓冲区上限、自动调优、接收端处理能力等因素仍然会影响最终结果。

如果 BDP 已经远远超过 64 KiB,却又受到传统 TCP 窗口限制,那么链路带宽就可能无法得到充分利用。

这也是为什么在高带宽、长 RTT 的网络中,窗口缩放和 TCP 缓冲区配置特别重要。

缓冲区是不是越大越好?小心缓冲区膨胀

讲到这里,你可能会觉得:既然更大的窗口可以让更多数据在途,那我直接把 TCP 缓冲区设置得很大,不就行了吗?

还真不行。

因为缓冲区太大,可能带来另一个问题:缓冲区膨胀,也就是 Bufferbloat。

用排队买咖啡理解缓冲区膨胀

假设一家咖啡店只有一个制作窗口,每分钟最多制作 20 杯咖啡。

平时只有几个人排队,大家很快就能拿到咖啡。

但如果店门口允许无限排队,一下子来了 200 个人,即使每个人都能成功排上队,也不代表服务速度提高了。

相反,最后排队的人可能要等很长时间。

网络也是一样。

当数据包进入网络的速度超过某个链路或设备的处理速度时,数据就可能在队列里等待。

如果队列过深,数据包不一定马上丢失,但延迟可能显著增加。

这就可能出现一种情况:

  • 大文件传输速度看起来不错。
  • SSH 输入命令却有明显延迟。
  • 网页请求响应时间忽高忽低。
  • 实时语音或交互业务出现卡顿。

为什么会这样?

因为大流量持续占用队列资源,其他数据包可能需要排在后面等待处理。

需要强调的是,TCP 套接字缓冲区和网络设备排队队列不是同一个东西。Bufferbloat 经常与瓶颈链路或设备队列的过度排队有关,并不意味着只要 TCP 缓冲区设置大,就一定发生 Bufferbloat。

但不合理的缓冲区和队列配置,确实可能加剧延迟问题。

多个 TCP 连接时,内存占用会怎样?

再考虑一种情况。

一台服务器不是只有一个 TCP 连接,而是同时运行几百个、几千个连接。

如果每个连接都可能使用数 MiB 的缓冲区,那么整体内存需求就可能很可观。

例如,假设有 1000 个活跃连接,每个连接实际使用的相关缓冲区合计达到 4 MiB,那么仅按这个简化假设计算:

1
1000 × 4 MiB = 4000 MiB

也就是约 3.91 GiB。

这只是为了说明规模效应的估算,并不意味着每个连接都会立即分配 4 MiB,也不意味着实际内存占用会与这个数值完全一致。

Linux 通常会按实际需求管理套接字内存,TCP 自动调优也会影响缓冲区的使用情况。

但对于高并发服务器来说,不能只考虑单连接性能,还必须关注所有连接叠加后的内存消耗。

所以,我们需要在吞吐量、延迟和内存使用之间找到平衡,而不是盲目追求最大缓冲区。

在 RHEL 上应该怎样调整 TCP 缓冲区?

理论已经讲得差不多了,现在来看实际操作。

先查看当前参数:

1
2
3
4
5
sysctl net.core.rmem_max
sysctl net.core.wmem_max
sysctl net.ipv4.tcp_rmem
sysctl net.ipv4.tcp_wmem
sysctl net.ipv4.tcp_window_scaling

建议先保存输出,作为修改前的记录。

接下来,根据测试结果决定是否需要调整。

例如,假设测试发现:

  • 网络带宽较高。
  • RTT 相对较大。
  • TCP 窗口或缓冲区限制了吞吐量。
  • 当前配置不足以满足目标 BDP。

那么可以考虑评估 TCP 缓冲区上限和自动调优范围。

不过,先别直接把某个固定数值写进配置文件。

先理解参数之间的关系

通常来说:

  • net.core.rmem_max 和 net.core.wmem_max 与套接字缓冲区的上限有关。
  • net.ipv4.tcp_rmem 和 net.ipv4.tcp_wmem 控制 TCP 接收、发送缓冲区的相关范围。
  • net.ipv4.tcp_window_scaling 控制是否启用 TCP 窗口缩放。

这几个参数之间存在关联,但并不是简单的“一定要全部设成相同的数值”。

此外,Linux 对自动调优缓冲区和显式设置缓冲区的处理也有所不同。对于应用程序明确指定的套接字缓冲区,还需要考虑相应的套接字选项语义。

因此,调整时应该以当前系统的内核文档和实际测试结果为依据。

不要把临时测试值直接当成生产配置

如果确实需要尝试修改,可以先在测试环境中操作。

例如,使用 sysctl -w 临时调整某个参数:

1
sysctl -w net.ipv4.tcp_window_scaling=1

这个命令会尝试将窗口缩放设置为启用状态。

但如果原来已经是 1,那它自然不会带来变化。对于窗口缩放功能,应该先确认当前值,并注意它是在 TCP 连接建立时协商的。

至于缓冲区上限,具体应设置多大,必须结合当前 RHEL 版本、内核文档、链路带宽、RTT、并发连接数量和内存容量决定。

如果准备持久化配置,可以在 /etc/sysctl.d/ 下创建单独的配置文件,再通过 sysctl --system 应用。

但在没有明确瓶颈证据之前,不建议直接复制网上的“大缓冲区”配置。

网络调优到底应该怎么做?给你一套排查思路

前面讲了这么多知识点,现在把它们串起来。

假设业务反馈:两台 RHEL 服务器之间传输大文件时速度很慢,而且 SSH 偶尔卡顿。

我们可以按照下面的顺序排查。

第一步:确认链路和基础网络状态

查看网卡信息:

1
2
3
ip -br link
ip -s link
ethtool ens160

重点关注:

  • 网卡是否处于正常状态。
  • 协商速率是否符合预期。
  • 收发错误和丢包计数是否持续增长。
  • 是否存在链路、驱动或配置异常。

第二步:建立吞吐量基线

使用 iperf3 测试:

1
iperf3 -c host1 -t 10

再测试反方向:

1
iperf3 -c host1 -t 10 -R

如果双向表现差异明显,就需要进一步检查两个方向的链路、队列和处理能力。

第三步:观察延迟和 TCP 重传

测试 RTT:

1
ping -c 10 host1

查看 TCP 统计:

1
ss -ti

ss -ti 可以展示 TCP 连接的详细信息。不同内核版本显示的字段可能不同,可以重点关注 RTT、拥塞窗口、重传相关信息等。

如果出现明显重传,应继续排查丢包、拥塞和链路质量,而不是直接把所有缓冲区调大。

第四步:检查 CPU、中断和网卡队列

1
2
3
4
5
mpstat -P ALL 1
cat /proc/softirqs
cat /proc/interrupts
ethtool -g ens160
ethtool -S ens160

这些命令分别帮助我们观察 CPU 使用情况、网络软中断分布、中断分布、Ring Buffer 配置以及驱动提供的统计信息。

如果发现单个 CPU 长期繁忙、某些队列异常或者接收资源不足,就可以针对相应的问题继续分析。

第五步:根据 BDP 判断 TCP 窗口是否可能成为瓶颈

使用测得的链路带宽和 RTT 估算 BDP。

例如:

1
BDP = 带宽 × RTT ÷ 8

如果带宽以 bit/s 计,RTT 以秒计,结果就是字节数。

接下来检查当前 TCP 缓冲区范围和窗口缩放配置,判断它们是否可能限制在途数据量。

如果 BDP 远大于可用窗口,就需要进一步验证窗口限制是否确实存在。

第六步:每次只修改一个主要变量

比如确认缓冲区上限确实不足,就只调整相关缓冲区参数,然后重复相同的 iperf3 测试。

如果怀疑网卡接收队列不足,则应在确认驱动支持、资源确实不足之后,再评估 Ring Buffer 调整。

每次修改后,都应该比较:

  • 吞吐量是否提高。
  • RTT 和业务延迟是否恶化。
  • 重传和丢包是否减少。
  • CPU 使用率是否发生明显变化。
  • 内存占用是否增加。

只有这样,才能知道究竟是哪项调整发挥了作用。

最后总结一下

这一篇我们主要建立了 Linux 网络性能调优的基础思路。

第一,先测量,再修改。iperf3 可以帮助我们建立吞吐量基线,但网络延迟、丢包和 CPU 瓶颈还需要其他工具配合分析。

第二,网络数据从应用程序到网卡,需要经过套接字缓冲区、协议栈、网络设备队列和驱动等多个环节。发送、接收、中断、软中断以及 CPU 处理能力,都可能成为瓶颈。

第三,网卡 Ring Buffer、CPU 亲和性和网络软中断都值得关注,但不能看到丢包或者 CPU 忙碌,就认定只需要增大队列或调整中断。

第四,TCP 缓冲区参数各有作用。tcp_mem 和 udp_mem 涉及协议栈内存压力管理,而 rmem_max、wmem_max、tcp_rmem 和 tcp_wmem 则涉及套接字缓冲区的相关限制与范围。

第五,BDP 是理解 TCP 窗口和缓冲区需求的重要工具。它告诉我们,在给定带宽和 RTT 的情况下,要充分利用链路,可能需要多大的在途数据量。

最后,缓冲区不是越大越好。缓冲区太小可能限制吞吐量,队列和缓冲区过大则可能增加排队延迟和内存压力。

真正的网络调优,不是把参数改到最大,而是找到瓶颈,用合适的参数解决它,再用测试结果证明调整有效。