在 Linux 网络性能调优中,net.ipv4.tcp_rmemtcp_wmemtcp_mem 等参数是绕不开的关键点。很多人要么保持默认值不动,要么照着网上抄一份"一劳永逸"的配置。问题在于:默认值不一定适配你的链路,抄来的配置更不一定对你合适——它和你的带宽时延强相关。

本文给出一个用 BDP(Bandwidth-Delay Product,带宽时延积) 来反推 TCP 缓冲区参数的方法,让你可以根据自己的链路情况"算"出合理配置,而不是"蒙"。

一、什么是 BDP

BDP 描述的是:在一条链路上,从发送端发出数据到收到对端 ACK 期间,处于"在途(in flight)"状态的数据量。它由两个因素决定:

  • 带宽 B:链路的最大吞吐能力(bit/s 或 bytes/s)
  • 往返时延 RTT:一个数据包从发出到收到 ACK 的时间(秒)

公式:

BDP = 带宽 B × RTT

换算到字节单位:

BDP(bytes) = (带宽 bit/s × RTT s) / 8

物理含义很简单:要"填满"这条管道,让管道里始终有足够的数据流动,发送端至少需要把 BDP 这么多字节的数据推到网络上,等待 ACK 回来。如果发送端能同时持有 BDP 字节的未确认数据,链路就能跑满。

二、BDP 与 TCP 缓冲区的关系

TCP 是可靠传输协议,发送端"能同时持有多少未确认数据"受发送窗口接收窗口中较小者限制:

  • 发送窗口 → 由本端发送缓冲区决定,受 tcp_wmemwmem_max 约束
  • 接收窗口 → 由对端接收缓冲区决定,对端受 tcp_rmemrmem_max 约束

要让链路跑满,两端的缓冲区都要至少容纳一个 BDP 大小的数据

缓冲区设置 后果
缓冲区 < BDP 窗口受限,发送端被迫等待 ACK,链路无法跑满,吞吐量下降
缓冲区 = BDP 刚好填满管道,理论最优
缓冲区 ≫ BDP 浪费内存;多连接叠加时整体内存压力大;极端情况还可能加重网络拥塞

所以配置的核心思路是:先算出 BDP,再让缓冲区的 max 至少覆盖 BDP,但不要极端放大。

三、计算 BDP:几个典型场景

下面举三个例子,覆盖局域、数据中心、跨地域链路。

场景 A:千兆局域,1Gbps,RTT ≈ 0.5ms

BDP = 1,000,000,000 bit/s × 0.0005 s / 8 = 62,500 bytes ≈ 61 KB

缓冲区只要几十 KB 就够,内核默认值绰绰有余。

场景 B:万兆数据中心,10Gbps,RTT ≈ 0.1ms

BDP = 10,000,000,000 × 0.0001 / 8 = 125,000 bytes ≈ 122 KB

同样不大。这种场景瓶颈通常不在缓冲区而在中继设备、中断处理、丢包恢复策略。

场景 C:跨地域高带宽链路,10Gbps,RTT ≈ 100ms

BDP = 10,000,000,000 × 0.1 / 8 = 125,000,000 bytes ≈ 119 MB

这下很大了。默认 tcp_rmem 的 max(很多发行版是 6 MB 左右)远不够,需要调大,否则单连接在跨地域链路上根本跑不满。

经验法则:跨地域 + 高带宽 = 长肥管道(LFN,Long Fat Network)。这是 BDP 最值得调的场景。

四、相关内核参数详解

先厘清几个易混参数。

net.ipv4.tcp_rmem

TCP 接收缓冲区每个连接的三档值(单位:字节):

min   default   max
  • min:缓冲区下限,避免被压到 0
  • default:初始默认值
  • max:自动调优时缓冲区能达到的上限——这就是我们要根据 BDP 调的值

net.ipv4.tcp_wmem

TCP 发送缓冲区每个连接的三档值(单位:字节),格式与 tcp_rmem 相同。max 同样依据 BDP 调整。

net.ipv4.tcp_mem

整个 TCP 协议栈的全局内存使用阈值,注意单位是(一般为 4096 字节,可用 getconf PAGE_SIZE 查看):

low   pressure   high

它表示系统范围内所有 TCP 连接占用的总内存水平线。pressure、high 触发后会压缓冲区。多连接服务器上要算:

建议 high ≈ (可用内存 / PAGE_SIZE) × 你愿意分给 TCP 的比例

net.core.rmem_max / net.core.wmem_max

SO_SNDBUF/SO_RCVBUF 系统调用设置缓冲区时的硬上限(非 TCP 自动调优路径)。tcp_rmem/tcp_wmemmax 受这两个值制约——如果你的 tcp_rmem max 大于 rmem_max,会被截断。所以调整 TCP 参数前,要先放开通用的限制:

rmem_max ≥ tcp_rmem 的 max
wmem_max ≥ tcp_wmem 的 max

net.core.rmem_default / net.core.wmem_default

非 TCP 协议或未走 TCP 自动调优路径下的默认缓冲区大小。一般跟着 TCP 的 default 档对齐即可。

五、基于 BDP 的配置方法

假设我们眼前的链路是 10Gbps,跨地域 RTT ≈ 100ms(场景 C),BDP ≈ 119 MB。下面是从 BDP 倒推参数的过程。

1. 确定单连接缓冲区上限

理论上 tcp_rmem/tcp_wmemmax 必须 ≥ BDP。建议留一定余量(×1.5 ~ ×2 倍),用于缓冲"已收到但未读"、"已发送未确认"的数据,以及应对 RTT 抖动:

单连接 max ≈ BDP × 2 ≈ 240 MB

但 240 MB / 连接对很多场景过于奢侈——是否真要这么大,还要看你单连接期望带宽。如果你一个连接也就传 1Gbps,那用 1Gbps 去算 BDP(≈ 12 MB),不用按 10Gbps 算。

先把这一点说清楚:

用"你期望单连接跑到的带宽"去算 BDP,而不是物理带宽。
物理带宽被多个连接并发分摊时,单连接 BDP 远小于链路 BDP。

举例:10Gbps 链路上,你期望单连接跑 2Gbps,RTT 100ms:

单连接 BDP = 2,000,000,000 × 0.1 / 8 = 25,000,000 bytes ≈ 24 MB

max 取 2 倍余量 → ~48 MB。这就是单连接缓冲区上限。

2. 确定 min / default

  • min:通常几千到一万多字节即可,不需要改太狠
  • default:起始缓冲区,略低于单连接 BDP 即可,让初始连接就有一个不错的起点。例如设为 ~ 2 MB

3. 确定 tcp_mem(全局)

假设机器 64GB 内存,PAGE_SIZE = 4096,给 TCP 分配 1/4:

可用 TCP 内存 = 64 GB × 0.25 = 16 GB
对应页数      = 16 GB / 4 KB = 4,194,304 页

三档可以设为:

low      = 1,048,576      (4 GB)   开始注意
pressure = 2,097,152      (8 GB)   开始压缓冲区
high     = 4,194,304      (16 GB)  上限

4. 放开 core 层硬限制

rmem_max ≥ tcp_rmem 的 max = 48 MB
wmem_max ≥ tcp_wmem 的 max = 48 MB

实际可设为 ~64 MB,再加一点余量。

5. 综合 sysctl 配置示例

把上面的结论落到 /etc/sysctl.d/99-tcp-bdp.conf

# 单连接缓冲区(值已根据 BDP 计算):
#   单连接期望 2Gbps × 100ms × 2 倍余量 ≈ 48 MB
net.ipv4.tcp_rmem = 4096 2097152 50331648
net.ipv4.tcp_wmem = 4096 2097152 50331648

# 全局 TCP 内存(64GB 机器,给 TCP 1/4 ≈ 16GB)
net.ipv4.tcp_mem = 1048576 2097152 4194304

# 通用缓冲区硬上限,确保不被截断
net.core.rmem_max = 67108864
net.core.wmem_max = 67108864
net.core.rmem_default = 2097152
net.core.wmem_default = 2097152

# 启用 TCP 接收缓冲区自动调优(默认即为 1,关键是 max 设好后再启用才有意义)
net.ipv4.tcp_moderate_rcvbuf = 1
# 窗口缩放必须开,否则单窗口上限只有 64KB
net.ipv4.tcp_window_scaling = 1

应用:

sysctl --system

六、几点需要注意的"坑"

  1. rmem_max/wmem_maxtcp_rmem/tcp_wmem 的天花板
    不先把 rmem_max 抬上去,单独调大 tcp_rmem max 没效果——max 会被 rmem_max 截断。

  2. tcp_mem 单位是页,不是字节
    网上很多教程把 tcp_mem 写成 4096 87380 6291456 等字节量级,那是错的。tcp_mem 一般使用几十万到上百万量级的数字。

  3. 自动调优开启时,default 只是种子,max 才是关键
    tcp_moderate_rcvbuf = 1 时内核会基于 RTT 和丢包动态调整接收缓冲区,但不会超过 tcp_rmemmax。所以"算好 BDP → 设好 max"才是正解,无脑放大 default 没意义。

  4. 不要单看链路 BDP,要看单连接期望带宽的 BDP
    高带宽被多连接分摊时,单连接不需要链路全 BDP 那么大。

  5. 总内存要核算
    max × 并发连接数 不能爆掉内存。比如 max=48MB × 一万连接 = 480GB,对 64GB 机器是不现实的。多并发时反而应限制单连接 max,让 BDP 由并发连接数"合伙"承担。

  6. tcp_mem high 别设成接近物理内存
    high 是页数,过大会让 TCP 把整机内存吃完,引发 OOM 或让其他进程陷入内存压力。

  7. 窗口缩放 tcp_window_scaling 必须开启
    否则单报文接收窗口上限是 65535 字节(64KB),不管 tcp_rmem 设多大都没用——这是协议层(而非缓冲区层)的限制。

七、验证与监控

配置完不要只是说"应该好了",用以下命令观察实际窗口:

# 观察某连接的 cwnd、rwnd、RTT
ss -tin | less

# 关注输出里的:
#   rtt:0.1ms  mss:1448  cwnd:...
ss -tin 'state established' | grep -E 'cwnd|rtt'

# 查看 TCP 全局内存使用
cat /proc/net/sockstat

判断要点:

  • 若在传输中 cwnd 持续被某个 wscale × 窗口值限制住,并且达不到 BDP,说明缓冲区还不够大。
  • cat /proc/net/sockstat 显示 mem 用量接近 tcp_mem pressure,说明全局内存压力来得太早,应调大 pressure/high 或降低单连接 max

结语

BDP 是网络调优中少数几个"立体"的概念——它把抽象的带宽与时延,准确翻译成具体的字节数,让缓冲区配置有了一根尺。下次再有人让你"给一份 tcp 优化配置",先问他:

你的带宽 × RTT = 多少?

然后照着 BDP 推算缓冲区 max,先放开 rmem_max/wmem_max 上限、把 tcp_mem 算进物理内存预算,比任何抄来的"通用最优配置"都靠谱。

文章作者: emporer
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Emporer-Linux
喜欢就支持一下吧