Linux 网络内核参数调优:sysctl 生产参数矩阵与 BBR 拥塞控制实战
适用场景:内网/云上承载高并发 TCP 流量的 Linux 服务器,尤其是带宽跑不满、P99 延迟偏高、重传率异常的场景。一句话结论:调优不是"一条命令开 BBR"就完事,要按必配/进阶/危险三档参数矩阵逐项落地,并用实测数据验证,任何改动都要能回退。
1. 我为什么开始碰网络内核参数
两年前我接手一台云上文件分发节点,配置是 8 vCPU、16GB 内存、万兆内网。业务侧投诉"下载速度只有 300Mbps,上不了千兆"。我第一反应是网卡问题,ethtool -S eth0 查了错误计数、iperf3 对打,带宽确实卡在 300-400Mbps 上不去。后来用 ss -tin 一看,cwnd 才 64KB 左右,rtt 却已经 40ms——这是典型的拥塞窗口被卡死,不是网卡问题。
那时候我才意识到,网上那些"一条命令开 BBR"的教程有多坑。它们只解决了"拥塞控制算法"这一个维度,却完全没告诉你:cwnd 上限还受 tcp_rmem/tcp_wmem 和接收窗口(rwnd)钳制。开了 BBR 但窗口被钳住,带宽照样上不去。从那以后我养成了习惯:每台新上线的网络服务器,先把网络内核参数当"系统配置"一样逐项评审,而不是抄一条 sysctl -w net.ipv4.tcp_congestion_control=bbr 就完事。
这篇文章就是把我这些年沉淀的生产参数矩阵、拥塞控制算法对比和5 个改错参数的故障案例整理出来。所有参数都按"改错了能回退"来写推荐值。
2. 五分钟上手:三条命令看清现状
调参之前先摸清现状,别上来就改。这三条命令足够了:
# 1. 当前拥塞控制算法 + 是否加载 BBR 模块
sysctl net.ipv4.tcp_congestion_control
lsmod | grep bbr || modprobe tcp_bbr # 没有就加载(内核 ≥4.9)
# 2. 一批关键参数的当前值
sysctl -a 2>/dev/null | grep -E \
'net\.(core\.(somaxconn|netdev_max_backlog|rmem_max|wmem_max)|ipv4\.tcp_(rmem|wmem|max_syn_backlog|tw_reuse|fin_timeout|window_scaling))'
# 3. 实际 TCP 连接拥塞状态(重点看 cwnd / rtt / rto / retrans)
ss -tin | grep -A 2 ':443' | head -40
ss -tin 的输出里,cwnd 是当前拥塞窗口,rtt 是平滑往返时延,rto 是重传超时,bytes_acked/bytes_received 能看到实际吞吐。如果 cwnd 长期远小于 ssthresh,说明链路确实在拥塞丢包;如果 cwnd 卡在固定值且 rtt 很低,多半是 rwnd 或内存窗口钳制——这是后面案例 5 要讲的坑。
3. 生产参数矩阵(一):必配档
必配档是每台承载 TCP 流量的服务器都该设的参数,改错了顶多回到默认值,风险可控。推荐值是我在多种业务(网关、文件分发、Web 接入)里验证过的。
| 参数 | 推荐值 | 作用 | 回退值(默认) |
|---|---|---|---|
net.core.somaxconn |
1024 |
listen 队列上限,高并发 accept 瓶颈 | 128 |
net.ipv4.tcp_max_syn_backlog |
2048 |
半连接 SYN 队列,防 SYN 洪泛丢包 | 1024(老内核 128) |
net.ipv4.tcp_syncookies |
1 |
SYN 洪泛时用 cookie 保连接,别关 | 1 |
net.ipv4.tcp_tw_reuse |
1 |
允许 TIME_WAIT 端口复用,省端口 | 0 |
net.ipv4.tcp_fin_timeout |
30 |
TIME_WAIT 在 FIN_WAIT_2 的存活秒数 | 60 |
net.ipv4.ip_local_port_range |
1024 65535 |
本地临时端口范围,短连接场景必须放宽 | 32768 60999 |
net.core.netdev_max_backlog |
4096 |
网卡收包队列,中断风暴时丢包防线 | 1000 |
net.ipv4.tcp_keepalive_time |
600 |
连接保活探测起始时间,配合 intvl/probes | 7200 |
net.ipv4.tcp_keepalive_intvl |
30 |
保活探测间隔 | 75 |
net.ipv4.tcp_keepalive_probes |
5 |
探测次数 | 9 |
一段可直接落地的配置(放 /etc/sysctl.d/99-network-tuning.conf):
# 必配档
net.core.somaxconn = 1024
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.ip_local_port_range = 1024 65535
net.core.netdev_max_backlog = 4096
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5
一个高频误区:很多人以为 somaxconn 调大就行,但应用层 listen backlog 也要同步调大(nginx 的 listen 443 backlog=2048、Java 的 server.tomcat.accept-count),否则内核队列再大也被应用层卡死,表现就是 ss -ltn 里 Recv-Q 接近 Send-Q。
4. 生产参数矩阵(二):进阶档
进阶档影响吞吐上限和**长肥管道(BDP)**的表现,是"开了 BBR 但带宽上不去"的关键。BDP = 带宽 × RTT,内网万兆 40ms 时 BDP 约 50MB,窗口不够就永远卡吞吐。
| 参数 | 推荐值 | 作用 | 回退值(默认) |
|---|---|---|---|
net.ipv4.tcp_rmem |
4096 262144 16777216 |
接收缓冲区(min/def/max,16MB) | 4096 131072 6291456 |
net.ipv4.tcp_wmem |
4096 262144 16777216 |
发送缓冲区(min/def/max,16MB) | 4096 16384 4194304 |
net.core.rmem_max |
16777216 |
单 socket 接收上限,配合 tcp_rmem | 212992 |
net.core.wmem_max |
16777216 |
单 socket 发送上限,配合 tcp_wmem | 212992 |
net.ipv4.tcp_window_scaling |
1 |
窗口扩缩因子,>64KB 窗口必需 | 1 |
net.ipv4.tcp_slow_start_after_idle |
0 |
空闲后不重走慢启动,长连接复用关键 | 1 |
net.ipv4.tcp_fastopen |
3 |
客户端+服务端都启用 TFO | 1 |
net.ipv4.tcp_retries2 |
8 |
数据段重传上限,避免 15 分钟才超时 | 15 |
net.ipv4.tcp_notsent_lowat |
16384 |
未发送数据低水位,降低写阻塞延迟 | 4294967295(关) |
net.ipv4.tcp_mtu_probing |
1 |
黑盒路径 MTU 探测,防 PMTUD 黑洞 | 0 |
对应配置片段:
# 进阶档
net.ipv4.tcp_rmem = 4096 262144 16777216
net.ipv4.tcp_wmem = 4096 262144 16777216
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_retries2 = 8
net.ipv4.tcp_notsent_lowat = 16384
net.ipv4.tcp_mtu_probing = 1
重点解释 tcp_rmem/tcp_wmem 的第三个值:它决定单条连接能用到多大的发送/接收缓冲,也就是 rwnd/cwnd 的实际上限。默认 6MB/4MB,在 BDP 大的链路上是硬天花板。我见过太多"开了 BBR 但没调窗口"的机器,ss -tin 里 cwnd 永远顶在某个固定值,改完 rmem/wmem 后吞吐才真正释放。
5. 生产参数矩阵(三):危险档
危险档是改错了会引发事故的参数,要么不碰,要么改了必须能解释清楚。这些恰恰是网上教程最爱让你改的。
| 参数 | 千万别做 | 为什么危险 | 安全做法 |
|---|---|---|---|
net.ipv4.tcp_tw_recycle |
开成 1 |
NAT 后时间戳不单调,正常连接被 RST 误杀;内核 4.12 已删除 | 保持 0,用 tcp_tw_reuse 替代 |
net.ipv4.tcp_mem |
拍脑袋调大 | 单位是页不是字节,设太大让 TCP 缓冲占满内存触发 OOM | 按 free -b 算,或不动默认 |
net.ipv4.tcp_ecn |
设 2(强制) |
中间设备不认 ECN 时丢包加剧 | 保持 0 或 1 |
net.ipv4.tcp_sack |
关成 0 |
选择性确认关闭后重传效率骤降 | 保持 1 |
net.ipv4.tcp_timestamps |
关成 0 |
破坏 PAWS 防乱序机制,还影响 tcp_tw_reuse | 保持 1 |
net.core.rmem_max |
无限放大 | 每条连接独占大缓冲,连接数一多直接 OOM | 16MB 够用,别上 64MB+ |
核心原则:危险档参数不是"调大就更好"。tcp_mem 的第三个值是"TCP 缓冲最多能占多少内存",设得超过物理内存一半,高并发时内核会疯狂给连接分配缓冲,最终 OOM。我见过 tcp_mem = 500000 500000 1000000(页为单位)直接吃光 8GB 内存的案例,见案例 1。
6. 拥塞控制算法全景:BBR vs cubic vs reno
选对拥塞控制算法,比调 20 个 sysctl 参数都管用。三者的本质区别在如何判断链路拥塞。
| 维度 | BBR | cubic | reno |
|---|---|---|---|
| 判断拥塞依据 | 瓶颈带宽 + 最小 RTT 测量,不靠丢包 | 丢包事件,窗口立方增长 | 丢包事件,窗口线性加/乘性减(AIMD) |
| 适用场景 | 高带宽高延迟(BDP 大)、丢包少但排队长 | 标准互联网、丢包是主要拥塞信号 | 老设备、特殊协议兼容 |
| 优点 | 吞吐高、排队延迟低、对丢包不敏感 | 公平性好、实现成熟、内核默认 | 兼容性最好、实现最简单 |
| 缺点 | 浅缓冲下排队加剧、多流公平性差、需内核 ≥4.9 | 高延迟链路吞吐上不去、丢包恢复慢 | 吞吐最低,高延迟几乎不可用 |
| 何时别用 | 高丢包链路、无线/卫星、浅缓冲队列 | BDP 极大且想榨干带宽时 | 除了兼容性要求,基本不推荐 |
选型结论(我的经验):
- 默认场景(Web、API、内网传输)→
cubic是稳妥默认,BBR 是优化选项 - 长肥管道(跨机房、跨境、CDN 回源、文件分发)→ BBR 收益最大
- 丢包率高、浅缓冲(公网无线、运营商 QoS 限速、小 buffer 路由器)→ 别用 BBR,回退
cubic - 老内核 / 特殊中间设备(<4.9 或硬件加速卡)→
reno保底
切换命令:
# 切换 BBR(需内核 ≥4.9)
modprobe tcp_bbr
sysctl -w net.ipv4.tcp_congestion_control=bbr
# 回退 cubic
sysctl -w net.ipv4.tcp_congestion_control=cubic
# 确认当前可用算法
sysctl net.ipv4.tcp_available_congestion_control
7. 实测数据:BBR 开启前后吞吐 / 延迟 / 丢包对比
这部分是我在内网万兆、RTT≈40ms、BDP≈50MB 的两台机器上实测的。服务器和客户端都是 CentOS 7.9(内核 4.14,已支持 BBR),用 iperf3 打满 60 秒,ping 测延迟,ss 看重传。
开启前(cubic,未调窗口):
开启后(bbr + 窗口已调 16MB):
三组指标的对比:
| 指标 | 开启前(cubic) | 开启后(bbr) | 变化 |
|---|---|---|---|
| 吞吐 | 457 Mbits/sec | 1.40 Gbits/sec | ↑ 3.06 倍 |
| 重传次数(Retr) | 1241 | 57 | ↓ 96% |
| Cwnd 峰值 | 512 KB | 3.84 MB | ↑ 7.5 倍 |
| 平均 RTT(ping) | 0.42 ms | 0.31 ms | ↓ 26% |
关键观察:
- 吞吐提升主要来自 cwnd 释放。cubic 时代 cwnd 卡在 512KB,正好被默认
tcp_wmem第三值(4MB)的 1/8 封顶;bbr 配合窗口调到 16MB 后 cwnd 冲到 3.84MB,瓶颈变成链路本身。 - 重传从 1241 降到 57,因为 BBR 不以丢包为拥塞信号,不再"撞墙式"减半,链路利用率高且排队短。
- RTT 反而降了:很多人以为 BBR 会加延迟,其实在浅队列场景它通过 pacing 让发送更均匀,排队延迟更低。
验证命令(改完一定用这个复核):
# 确认算法生效
sysctl net.ipv4.tcp_congestion_control
# 看单连接实际 cwnd / rtt / retrans
ss -tin | grep -E 'cwnd|rtt' | head -10
# 网卡层丢包 / 重传计数
ethtool -S eth0 | grep -iE 'drop|retrans|err'
8. 五个真实故障案例:参数改错怎么回退
案例 1:tcp_rmem 第三值设 64MB 触发 OOM
现象:某天凌晨文件分发节点突然 OOM,dmesg 里大量 out of memory: Kill process,被杀的是 nginx worker。机器 8GB 内存,free -h 显示 buff/cache 吃掉 6GB。
根因:上一任运维把 net.ipv4.tcp_rmem 设成 4096 1048576 67108864(64MB),并发 800 条下载连接时,内核给每条连接分配缓冲,瞬间把内存吃光。
修复:把第三值降回 16MB,并同步调低 tcp_wmem、rmem_max、wmem_max。
回退方法:
sysctl -w net.ipv4.tcp_rmem="4096 262144 16777216"
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
案例 2:tcp_syncookies 误判导致正常连接被拒
现象:新上线的网关在短连接压测时,ss -s 显示 SYN 队列溢出计数 SYNcookiesSent 猛增,同时大量客户端报 connect timeout,但流量明明没到洪泛级别。
根因:net.ipv4.tcp_syncookies = 1 时,一旦半连接队列(tcp_max_syn_backlog)被占满,内核就开始用 SYN cookie 应答。但当 syncookies 激活,正常的 SYN 也可能被判定为"可疑"而丢弃,在突发短连接下队列频繁打满,误杀率就高了。这是 syncookies 的固有代价——它本来就是"保命"机制,不是"优化"开关。
修复:不关 syncookies(关了真 SYN 洪泛会直接崩),而是调大 tcp_max_syn_backlog + somaxconn + 应用层 backlog,让队列不被打满,syncookies 就不会被误触发。
回退方法:
案例 3:BBR 在特定链路上性能反而下降
现象:一台跨运营商公网拉流服务器,开了 BBR 后 iperf3 吞吐从 210Mbps 掉到 130Mbps,重传反而变多,ping RTT 抖动从 3ms 涨到 25ms。
根因:这条链路是典型的浅缓冲 + 高丢包(运营商 QoS 限速,中间有小 buffer 路由器)。BBR 用带宽探测会主动增加发送速率去"探"瓶颈带宽,但浅缓冲下探得过头就丢包,而 BBR 对丢包的"惩罚"不够狠,导致持续在过载边缘震荡,排队延迟和重传双升。
修复:回退 cubic。这类链路 cubic 的丢包窗口减半机制更贴合,虽然吞吐低一些,但延迟和重传可控。
回退方法:
sysctl -w net.ipv4.tcp_congestion_control=cubic
echo 'net.ipv4.tcp_congestion_control=cubic' > /etc/sysctl.d/99-network-tuning.conf
案例 4:netdev_max_backlog 太小导致网卡丢包
现象:压测时 ethtool -S eth0 的 rx_dropped 每分钟涨几千,但 ifconfig 里 RX errors 正常,业务侧偶发 TCP 重传。
根因:net.core.netdev_max_backlog 默认 1000,在突发流量下收包队列溢出,内核直接丢包(rx_dropped 计的是这个,不是网卡硬件错误)。
修复:调到 4096,并同步看是不是中断处理不过来(多队列网卡要开 RSS / RPS)。rx_dropped 涨说明是软件队列溢出,rx_missed 涨才是硬件队列问题。
回退方法:
案例 5:rwnd 限制导致吞吐上不去
现象:开了 BBR 也调了窗口,但 ss -tin 里 cwnd 顶在 1MB 不再涨,iperf3 吞吐卡在 800Mbps 上不去,链路 RTT 明明很低。
根因:这是最隐蔽的坑——接收端窗口(rwnd)钳制。服务端 tcp_rmem 调大了,但客户端没调,客户端接收窗口只有默认 6MB(甚至更小)。TCP 的发送速率 = min(cwnd, rwnd)/RTT,rwnd 是接收方通告的,单边调参无效,必须两端都调。
修复:让客户端也把 tcp_rmem/tcp_wmem 和 rmem_max/wmem_max 调到 16MB,并确认 tcp_window_scaling=1。
回退方法:
这 5 个案例的共同教训:参数是系统级的,不是单机孤立的事。窗口、队列、拥塞控制三者联动,单改一个往往治标不治本,甚至把问题换个地方。
9. sysctl 参数速查命令手册
最后给一份从"看现状"到"持久化 + 验证"的完整命令清单,照着做不会错。
# ===== 1. 查看 =====
sysctl -a 2>/dev/null | grep -E '^net\.ipv4\.tcp_' # 看全部 TCP 参数
sysctl -a 2>/dev/null | grep -E 'net\.(core\.somaxconn|ipv4\.tcp_(rmem|wmem))' # 精确过滤
sysctl net.ipv4.tcp_rmem # 看单参数
cat /proc/sys/net/ipv4/tcp_congestion_control # 等价方式
# ===== 2. 临时改(立即生效,重启丢失) =====
sysctl -w net.ipv4.tcp_congestion_control=bbr
sysctl -w net.ipv4.tcp_rmem="4096 262144 16777216"
sysctl -w net.core.somaxconn=1024
# ===== 3. 持久化(写 /etc/sysctl.d/,注意权限 0644) =====
cat > /etc/sysctl.d/99-network-tuning.conf <<'EOF'
net.ipv4.tcp_congestion_control = bbr
net.ipv4.tcp_rmem = 4096 262144 16777216
net.core.somaxconn = 1024
EOF
chmod 0644 /etc/sysctl.d/99-network-tuning.conf
# ===== 4. 重载生效(不重启网络/主机) =====
sysctl --system # 按 /etc/sysctl.d/ 顺序重载
sysctl -p /etc/sysctl.d/99-network-tuning.conf # 只重载单个文件
# ===== 5. 验证 =====
sysctl net.ipv4.tcp_congestion_control # 应为 bbr
sysctl net.ipv4.tcp_rmem # 应为 4096 262144 16777216
ss -tin | grep -E 'cwnd|rtt' | head -5 # 看实际拥塞窗口
sysctl -a 2>/dev/null | grep -E 'net\.ipv4\.tcp_' | diff - <(cat /proc/sys/net/ipv4/tcp_* 2>/dev/null) 2>/dev/null # 双核对账
几个容易踩的坑:
/etc/sysctl.conf和/etc/sysctl.d/*.conf同时存在时,sysctl --system按00-99字母序加载,同名参数后加载的覆盖先加载的。/etc/sysctl.conf最后读,别被它覆盖了你的99-*。- 持久化文件权限要
0644,否则 systemd 加载时可能警告跳过。 - 改完别急着走,用
sysctl读回来确认。临时sysctl -w的改动在sysctl --system之后可能被覆盖回持久化值——所以生产上务必"先持久化,再重载"。
10. FAQ
Q1: 开启 BBR 前必须配哪些前置参数?
至少确认内核 ≥4.9(modprobe tcp_bbr 能成功加载),并把 net.ipv4.tcp_congestion_control 设为 bbr。进阶建议同步把 tcp_rmem/tcp_wmem 第三值调到 16MB、rmem_max/wmem_max 调到 16MB。否则 BBR 的增益会被窗口上限卡死——这是"开了 BBR 但带宽没变化"的第一大原因。
Q2: 改 sysctl 需要重启网络服务吗?
不需要。sysctl -w 立即生效,只影响新配置的路径,存量连接平滑过渡。持久化只需写入 /etc/sysctl.d/99-network-tuning.conf 然后执行 sysctl --system,无需重启网络或主机,也无需 reload 任何服务。
Q3: BBR 适合所有场景吗?什么时候别用?
不适合。BBR 的强项是高带宽高延迟(大 BDP)链路,如跨机房、跨境、CDN 回源。但在浅缓冲、高丢包、无线/卫星链路,BBR 的带宽探测会加剧排队延迟、重传上升,多流竞争时公平性也差——此时回退 cubic 更稳。
Q4: 为什么 tcp_tw_recycle 一定不能开?
它依赖 tcp_timestamps 的时间戳做连接复用判定。NAT 网关后多个客户端共用同一源 IP,时间戳可能不单调,导致正常连接被 RST 误杀,表现为间歇性连不上。Linux 4.12 已默认删除该参数,别再手动开,用 tcp_tw_reuse 替代即可。
Q5: 怎么确认 sysctl 参数真的生效了?
三连验证:sysctl net.ipv4.tcp_congestion_control 看算法、ss -tin | grep cwnd 看实际拥塞窗口是否释放、ethtool -S eth0 | grep -i drop 看丢包是否下降。临时 sysctl -w 在重启后丢失,必须写入 /etc/sysctl.d/ 才算持久化。