跳转至

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,未调窗口):

[ ID] Interval        Transfer    Bitrate         Retr  Cwnd
[  5] 0.00-60.00 sec  3.20 GBytes  457 Mbits/sec  1241  512 KB

开启后(bbr + 窗口已调 16MB):

[ ID] Interval        Transfer    Bitrate         Retr  Cwnd
[  5] 0.00-60.00 sec  9.80 GBytes  1.40 Gbits/sec   57  3.84 MB

三组指标的对比:

指标 开启前(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 就不会被误触发。

回退方法:

sysctl -w net.ipv4.tcp_max_syn_backlog=2048
sysctl -w net.core.somaxconn=1024

案例 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 涨才是硬件队列问题。

回退方法:

sysctl -w net.core.netdev_max_backlog=4096
# 若回退默认
sysctl -w net.core.netdev_max_backlog=1000

案例 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。

回退方法:

# 两端都执行
sysctl -w net.ipv4.tcp_rmem="4096 262144 16777216"
sysctl -w net.core.rmem_max=16777216

这 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/ 才算持久化。

相关文章