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 -----------------------------------------------------------
然后在 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
再查看软中断统计:
重点关注 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,中文通常称为带宽延迟乘积。
它描述的是:在给定带宽和往返时延的情况下,链路上大约可以容纳多少尚未完成确认的在途数据。
计算公式为:
注意,带宽与数据量必须使用一致的单位。如果带宽使用 bit/s,那么计算得到的 BDP 就是 bit;如果需要字节数,还要除以 8。
用高速公路理解 BDP 我们可以把网络想象成一条高速公路。
带宽:相当于高速公路的车道通行能力。 RTT:相当于一辆车从出发到收到返回确认所需的时间。 在途数据:相当于已经上路、但发送方还没有收到确认的数据。 如果高速公路通行能力很强,但车辆往返时间很长,那么想要充分利用这条高速公路,就必须让足够多的车辆同时在路上。
网络也是一样。
假设链路带宽是 1 Gbps,RTT 是 100 ms。
如果发送方每次只能发送很少的数据,然后就必须等待接收方确认,那么发送方就会频繁等待,链路的利用率自然上不去。
而当允许更多数据在途时,发送方就可以在等待确认的同时继续发送数据,更充分地利用链路带宽。
这就是 BDP 的核心意义。
先测量 RTT 我们可以先使用 ping 观察两台主机之间的往返时延:
假设结果中的统计信息如下:
1 rtt min/avg/max/mdev = 0.335/0.341/0.347/0.015 ms
这里:
min:最小往返时延。avg:平均往返时延。max:最大往返时延。mdev:往返时延的离散程度指标。本例平均 RTT 为 0.341 ms。
换算成秒:
需要注意,ping 测量的是 ICMP 往返时延,不一定与 TCP 业务的实际 RTT 完全相同。对于高精度调优,最好结合真实 TCP 连接的 RTT 统计来分析。
计算 1 Gbps 链路的 BDP 现在假设:
计算过程如下:
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 位。
因此,未使用窗口缩放选项时,窗口字段能够表示的最大值是:
也就是接近 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,那么仅按这个简化假设计算:
也就是约 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 -R
如果双向表现差异明显,就需要进一步检查两个方向的链路、队列和处理能力。
第三步:观察延迟和 TCP 重传 测试 RTT:
查看 TCP 统计:
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。
例如:
如果带宽以 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 的情况下,要充分利用链路,可能需要多大的在途数据量。
最后,缓冲区不是越大越好。缓冲区太小可能限制吞吐量,队列和缓冲区过大则可能增加排队延迟和内存压力。
真正的网络调优,不是把参数改到最大,而是找到瓶颈,用合适的参数解决它,再用测试结果证明调整有效。