香港服务器上的 CentOS 7:我如何调优内核网络参数(rmem/wmem)把高带宽下的延迟打下来

夜里 1:40,机房空调的风像海风一样直灌进来。香港葵涌的机架区只剩交换机的风扇声。业务群里“延迟飙到 60ms+”的报警在频率上已经像节拍器。运营喊我去看看 10G 链路为什么一上量就抖。
我把笔电插上带外,SSH 进那台 CentOS 7 的边缘节点。灯光打在银色机框上,像在提醒我:今晚不调好,明早全线就得背锅。
现场与目标
硬件/系统环境(真实可复现)
机房:香港(HK)Tseung Kwan O,双上联 BGP(NTT + PCCW),单台直连 10G SFP+
服务器:Dell R430(1U)
- CPU:Intel Xeon E5-2680 v4 × 2(28 逻辑核)
- 内存:128 GB
- 网卡:Intel X520 SFP+(驱动 ixgbe)
- 磁盘:2 × NVMe(系统与业务分卷)
系统:CentOS Linux release 7.9(kernel 3.10.0-1160.el7.x86_64)
业务侧:TLS 终端 + 边缘 API 网关,长连接占比高,跨境访问较多(20–60ms RTT 常态)
目标:在不改架构的前提下,把高带宽下的尾延迟和抖动降下来,并尽量把 10G 链路吃满。
现象与初判
压测(未调优前)
| 指标 | 单流 iperf3 | 8 流 iperf3 | Ping(空载→满载) | 丢包(sar -n DEV 1) |
|---|---|---|---|---|
| 吞吐 | 2.6 Gbps | 5.8–6.3 Gbps | 12ms → 58–65ms | RX dropped 持续 > 0 |
| CPU | ksoftirqd 高(单核顶满),用户态不高 |
软中断抖 |
快速检查
- ss -tin:大量处于 ESTAB 的长流,rcv_ssthresh 偏小,窗口增长慢
- /proc/net/softnet_stat:第 2 列(flow_limit)与第 1 列(dropped)偶有增加
- /proc/interrupts:ixgbe 多队列中断分布不均,有几个队列集中在同一 NUMA 节点的同几颗核上
- ethtool -S eth0:rx_no_buffer_count 偶发跳动,环形队列深度不够用
初判:典型的高带宽 × 中等 RTT场景,窗口/缓冲不足 + 队列/中断倾斜导致拥塞与丢包,进而放大了尾延迟。
关键知识:BDP 与 rmem/wmem
带宽时延积(BDP)决定了单条长流要吃满链路需要多大的收/发窗口与缓冲。
BDP = 带宽(Byte/s) × RTT(s)
举几个我们现场常见组合(已换算为 MB,十进制):
| 链路 × RTT | 需要窗口(约) |
|---|---|
| 10 Gbps × 10 ms | 12.50 MB |
| 10 Gbps × 20 ms | 25.00 MB |
| 10 Gbps × 40 ms | 50.00 MB |
| 10 Gbps × 80 ms | 100.00 MB |
| 1 Gbps × 40 ms | 5.00 MB |
结论:香港到内地常见 30–50ms RTT,要把 10G 吃满,每条连接的收/发窗口上限至少要允许到 50–100 MB。这就落到 Linux 的 rmem/wmem 与 tcp_rmem/tcp_wmem 上。
动手调:从内核缓冲到 NIC 再到软中断
下面是我那晚在机架边一步步做的。你可以整体照抄,也可以按模块挑。
1) sysctl:把“可能的上限”先放开
创建 /etc/sysctl.d/99-hk-net-tuning.conf:
# ====== 基础能力上限,先放开 ======
net.core.rmem_max = 268435456 # 256MB
net.core.wmem_max = 268435456 # 256MB
net.core.rmem_default = 262144 # 256KB(默认值不要太大,防并发爆内存)
net.core.wmem_default = 262144
# TCP 自适应接收缓冲(min / default / max)
# 模板 A:大流优先(跨境大窗口)
net.ipv4.tcp_rmem = 4096 16777216 134217728 # 4KB / 16MB / 128MB
net.ipv4.tcp_wmem = 4096 16777216 134217728
# 如果你是“海量并发小流”,用模板 B(更保守的默认值):
# net.ipv4.tcp_rmem = 4096 262144 134217728 # 256KB default
# net.ipv4.tcp_wmem = 4096 262144 134217728
# 队列与握手能力
net.core.netdev_max_backlog = 250000
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 262144
# 现代 TCP 选项(CentOS7 默认都开,但显式写上)
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_sack = 1
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_mtu_probing = 1 # 1=遇到问题再探测;跨网段更稳妥
# 不要开 tcp_tw_recycle(新内核已移除),tcp_tw_reuse 仅客户端适用
# TIME-WAIT 与端口
net.ipv4.ip_local_port_range = 10000 65535
net.ipv4.tcp_fin_timeout = 15
生效:
sysctl --system
为何“默认值”不要太大?
tcp_rmem/tcp_wmem 的第二个值是“自适应目标值”,太大时大量并发会吃爆内存。长流多、并发少用模板 A;并发多、长流少用模板 B。
粗略估算:如果默认 16MB,1 万并发 ≈ 160GB 潜在占用(并不会同时达到上限,但要有风险意识)。我现场这台并发在几千量级,选模板 A。
2) 网卡与队列:把“进出水道”挖宽
查看队列与可调范围
ethtool -l eth0 # 查看当前/最大 TX/RX 队列
ethtool -g eth0 # 查看环形队列深度
扩大环形队列与 qdisc
# 环形队列更深,降低 rx_no_buffer(结合内存余量)
ethtool -G eth0 rx 4096 tx 4096
# 提高接口队列
ip link set dev eth0 txqueuelen 10000
# 关闭 LRO(服务器侧/转发侧建议关),保留 GRO/TSO/GSO
ethtool -K eth0 lro off gro on gso on tso on
# 适度降低中断合并时延(换低尾延,代价是更高 CPU)
ethtool -C eth0 rx-usecs 8
把中断/RSS/RPS 摊开到多个核
# 确认硬件 RSS 生效(ixgbe 默认支持)
ethtool -n eth0 rx-flow-hash tcp4
# 如果 irqbalance 没把中断分散,手动看一下并设置亲和性
grep -i eth0 /proc/interrupts
# 针对每个中断号 echo 掩码到 /proc/irq/<IRQ>/smp_affinity 可手动分散(生产建议先让 irqbalance 自己干)
# 配 RPS/RFS(软分发,硬件 RSS 不够用时再上)
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
for q in /sys/class/net/eth0/queues/rx-*; do
echo ffffffff > $q/rps_cpus # 32 核掩码示例,按实际核数写
echo 4096 > $q/rps_flow_cnt
done
经验:先靠硬件 RSS(多队列 + irqbalance),再用 RPS/RFS 补位。不要一上来全开满,观察 ksoftirqd 与 softnet_stat 再调。
3) 拥塞控制:CUBIC 为主,BBR 作“可选项”
CentOS 7 的 3.10 内核默认 CUBIC,没有原生 BBR。
想用 BBR,需要升级内核(ELRepo 的 kernel-ml 4.14/5.x 及以上),然后:
# 可选:若已升级到新内核
sysctl -w net.ipv4.tcp_congestion_control=bbr
# 验证
sysctl net.ipv4.tcp_congestion_control
我现场没有改内核(风险窗口有限),沿用 CUBIC。只靠窗口/队列/中断就把尾延打下去了。
如果你是跨洲长 RTT + 大流,且允许维护窗口,BBR 值得一试。
4) 应用侧与 ulimit 的衔接
确保 ulimit -n 足够(如 1048576),否则连接数大时会卡在 FD 上。
Nginx/HAProxy 不要把 send/recv 应用层缓冲写得比 net.core.{r,w}mem_max 还大。
大量长连接服务建议开 reuseport(Nginx SO_REUSEPORT),结合多 worker 分流。
5) 验证方法(可直接复用)
基线(调优前)记录一次:
# 吞吐与并发流
iperf3 -c <对端IP或回环对端> -t 60 -P 8 --get-server-output
# 尾延迟
ping -i 0.2 <对端> & # 另窗跑压测
# 丢包与软中断
sar -n DEV 1
watch -n1 'cat /proc/net/softnet_stat'
# 网卡统计
watch -n1 'ethtool -S eth0 | egrep "rx_no_buffer|rx_errors|tx|rx"'
调优后再次跑,对比表格:
| 指标 | 调优前 | 调优后 |
|---|---|---|
| 8 流 iperf3 吞吐 | 5.8–6.3 Gbps | 9.1–9.5 Gbps |
| Ping 尾延(p99) | 60–65 ms | 18–22 ms |
| RX dropped | 持续 > 0 | 基本 0 |
ksoftirqd 占核 |
经常 100% | 稳定 < 40% |
实际表现与你的 RTT/对端栈/交换机不同会略有差异,但方向应该一致:吞吐靠近链路上限、尾延显著收敛、丢包消失或极少。
完整配置清单(可粘贴)
/etc/sysctl.d/99-hk-net-tuning.conf
# ====== 上限与窗口 ======
net.core.rmem_max = 268435456
net.core.wmem_max = 268435456
net.core.rmem_default = 262144
net.core.wmem_default = 262144
net.ipv4.tcp_rmem = 4096 16777216 134217728
net.ipv4.tcp_wmem = 4096 16777216 134217728
# ====== 队列与握手 ======
net.core.netdev_max_backlog = 250000
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 262144
# ====== 现代 TCP 特性 ======
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_sack = 1
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_mtu_probing = 1
# ====== 端口/存活 ======
net.ipv4.ip_local_port_range = 10000 65535
net.ipv4.tcp_fin_timeout = 15
加载:sysctl --system
/etc/rc.d/rc.local(或做成 systemd 服务)
#!/bin/bash
# NIC 队列与 offload
ethtool -G eth0 rx 4096 tx 4096
ip link set dev eth0 txqueuelen 10000
ethtool -K eth0 lro off gro on gso on tso on
ethtool -C eth0 rx-usecs 8
# RPS/RFS(按需)
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
for q in /sys/class/net/eth0/queues/rx-*; do
echo ffffffff > $q/rps_cpus
echo 4096 > $q/rps_flow_cnt
done
chmod +x /etc/rc.d/rc.local
如何“算”你的合适窗口(举例)
想法:把 tcp_*mem 的 max 定在 1–2 × BDP,net.core.*max ≥ 该值;把 default 依业务形态在 256KB–16MB 之间取值。
示例(10 Gbps,RTT 40 ms):
BDP ≈ 50 MB → tcp_rmem/tcp_wmem max 建议 ≥ 64MB,甚至 128MB 更从容。
对“并发多”的业务,把 default 从 16MB 降到 256KB/1MB;对“长流多”的业务,default 用 4–16MB 让窗口快点长起来。
我踩过的坑(请避开)
为了“追求低延迟”把 GRO/TSO 全关:CPU 被打爆,实时是低了,吞吐崩了,尾延更差。服务器侧建议 GRO/TSO/GSO 打开,仅 LRO 关。
把 tcp_tw_recycle 拿来就用:新内核没了且会搞坏 NAT/负载均衡后的连接,别碰。
盲目开 Jumbo(MTU 9000)走公网/跨运营商:黑洞/碎片乱飞。除非你确定端到端支持,否则公网维持 1500。
默认值设太大导致内存压力:tcp_rmem/tcp_wmem 的第二个值别飙太高,“max 放开,default 保守”。
只改 sysctl 不看队列/中断:netdev_max_backlog、环形队列、RSS/RPS、irqbalance 一样重要。
忽略交换机/上联限速:某些运营商策略或防护清洗会在阈值处 PPS 打顶,看 ethtool -S 与上联统计别只盯 Mbps。
FAQ:几条我常被问到的取舍
CUBIC vs BBR?
CentOS 7 原生 3.10 用 CUBIC 足够打下 90% 的问题;全球长 RTT场景(>100ms)空窗可尝试 BBR,但需要升级内核,评估回滚路径。
net.core.rmem_default/wmem_default 设多少合适?
并发多 → 256KB–1MB;长流多 → 4–16MB。default 只是目标值,Linux 会按压力自适应,但太大时“平均”开销也跟着大。
RPS 一定要开吗?
不一定。优先让硬件 RSS + irqbalance 分散。只有当 ksoftirqd 单核顶满或流特征单一时,再用 RPS/RFS 补。
结尾:那一晚 2:30 的风
配置落地后,我让对端再次拉 8 路 iperf3,Ping 窗口里 20ms 一直稳着,sar 上的 dropped 清得干干净净。
我站在 25U 机架前,听着风扇在耳边像海浪一样起伏,想起刚来的那会儿,对“rmem/wmem”只停留在“把数值调大”的刻板印象。
其实所谓调优,不过是把窗口、队列、中断和业务形态四件事揉顺了。
3 点整,报警频道安静下来。我把扳手塞回工具包,顺手在机柜门上贴了张纸条:
“max 放开,default 审慎;先硬后软,度量先行。”
第二天早班的同事看到,笑说这像咒语。我说,咒语也好,流程也罢,能把延迟打下来,就是对的路。
附:一键回滚与观测清单
回滚:
- mv /etc/sysctl.d/99-hk-net-tuning.conf{,.bak} && sysctl --system
- ethtool -G eth0 rx 512 tx 512 && ethtool -K eth0 gro on gso on tso on lro off && ethtool -C eth0 rx-usecs 20
- 重启 irqbalance,去掉手改的 smp_affinity
观测:
- iperf3 -c <peer> -t 60 -P 8
- ping -i 0.2 <peer>(压测同时看波动)
- sar -n DEV 1、ethtool -S eth0、/proc/net/softnet_stat
- ss -tin 看窗口增长与重传
如果你也在香港边缘节点折腾这些,把你的 RTT、并发量级、业务形态发我一眼,我可以按你的场景把上面两套模板再“打磨到位”。