利用 BDP 计算公式科学配置 TCP 缓冲区参数
在 Linux 网络性能调优中,net.ipv4.tcp_rmem、tcp_wmem、tcp_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_wmem、wmem_max约束 - 接收窗口 → 由对端接收缓冲区决定,对端受
tcp_rmem、rmem_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:缓冲区下限,避免被压到 0default:初始默认值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_wmem 的 max 受这两个值制约——如果你的 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_wmem 的 max 必须 ≥ 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
六、几点需要注意的"坑"
-
rmem_max/wmem_max是tcp_rmem/tcp_wmem的天花板
不先把rmem_max抬上去,单独调大tcp_rmem max没效果——max会被rmem_max截断。 -
tcp_mem单位是页,不是字节
网上很多教程把tcp_mem写成4096 87380 6291456等字节量级,那是错的。tcp_mem一般使用几十万到上百万量级的数字。 -
自动调优开启时,
default只是种子,max才是关键
tcp_moderate_rcvbuf = 1时内核会基于 RTT 和丢包动态调整接收缓冲区,但不会超过tcp_rmem的max。所以"算好 BDP → 设好 max"才是正解,无脑放大default没意义。 -
不要单看链路 BDP,要看单连接期望带宽的 BDP
高带宽被多连接分摊时,单连接不需要链路全 BDP 那么大。 -
总内存要核算
max× 并发连接数 不能爆掉内存。比如max=48MB× 一万连接 = 480GB,对 64GB 机器是不现实的。多并发时反而应限制单连接max,让 BDP 由并发连接数"合伙"承担。 -
tcp_mem high别设成接近物理内存
high是页数,过大会让 TCP 把整机内存吃完,引发 OOM 或让其他进程陷入内存压力。 -
窗口缩放
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 算进物理内存预算,比任何抄来的"通用最优配置"都靠谱。