3. 发送缓冲区调优:SO_SNDBUF 选项与最佳大小计算
发送缓冲区,说白了就是内核帮你暂存待发数据的一块内存。你调用 send() 时,数据先拷进这里,然后内核协议栈慢慢往外发。这块缓冲区设多大,直接影响你的应用能跑多快、会不会卡顿。
我早年调一个金融交易网关,就因为发送缓冲区设太小,导致每秒丢了几百笔订单。嗯,从那以后我再也不敢小看这个参数了。
3.1 SO_SNDBUF 选项:怎么设、什么时候生效
SO_SNDBUF 是套接字级别的选项,用 setsockopt() 来设置。但有个坑——内核实际分配的大小,往往是你要的值的两倍。
int sndbuf = 65536; // 我想要 64KB
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
// 读回来看看实际是多少
int actual = 0;
socklen_t len = sizeof(actual);
getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &actual, &len);
printf("实际发送缓冲区: %d\n", actual); // 很可能输出 131072
为什么会这样?因为 Linux 内核在分配时,会把用户请求值乘以 2,再减去一些协议头预留空间。我记得第一次查这个 bug 时,盯着 tcp_sndbuf 的源码看了半天才明白。
3.2 发送缓冲区大小对性能的影响
缓冲区太小和太大,都会出问题。我列个表,你一看就明白。
| 场景 | 表现 | 根因 |
|---|---|---|
| 缓冲区过小 | send() 频繁阻塞、吞吐量上不去 | 应用层写入速度 > 内核发送速度,缓冲区很快填满 |
| 缓冲区过大 | 内存浪费、延迟变高、丢包时重传代价大 | 大量数据积压在缓冲区,TCP 拥塞控制反应迟钝 |
| 缓冲区刚好 | 吞吐量高、延迟稳定、CPU 占用合理 | 应用层与内核发送速率匹配,流水线饱满 |
我调过一个视频推流服务,默认 16KB 的发送缓冲区,推 1080p 视频时 send() 经常返回 EAGAIN。改成 256KB 后,吞吐量直接翻了三倍。你想想看,缓冲区就是水管的口径——口径太小,水龙头开再大也没用。
3.3 如何计算最佳发送缓冲区大小
最佳大小没有银弹,但有个经验公式可以用。我个人习惯按 带宽-延迟积(BDP) 来算。
核心公式:
最佳发送缓冲区 ≈ 带宽 × RTT × 2
单位:带宽用 bps,RTT 用秒,结果用字节。
举个例子:
- 带宽:100 Mbps(即 12.5 MB/s)
- RTT:20 ms(0.02 秒)
- BDP = 12.5 MB/s × 0.02 s = 0.25 MB = 256 KB
- 最佳缓冲区 ≈ 256 KB × 2 = 512 KB
为什么要乘 2?因为发送缓冲区不仅要装正在发的数据,还要装已经发出但还没收到 ACK 的数据。乘 2 之后,能保证发送窗口不会因为缓冲区不够而被迫缩小。
ping 测一下。如果带宽不确定,可以用 iperf 跑一轮。我一般在压测环境里先设一个较大的值(比如 1MB),然后逐步往下压,找到吞吐量开始下降的那个临界点。
3.4 内核自动调优:tcp_sndbuf 与 tcp_wmem
Linux 内核其实有自动调优机制。你设了 SO_SNDBUF 后,内核会在 tcp_wmem 的范围内动态调整。
# 查看当前 tcp_wmem 值
cat /proc/sys/net/ipv4/tcp_wmem
# 输出示例:4096 16384 4194304
# min default max
三个值的含义:
- min:每个 TCP 连接的最小发送缓冲区,默认 4KB
- default:初始值,默认 16KB
- max:上限,默认 4MB
如果你不设 SO_SNDBUF,内核会从 default 开始,根据拥塞窗口自动往上扩,最多到 max。但如果你手动设了 SO_SNDBUF,内核的自动调优就失效了——它会把你的值当作固定上限。
3.5 知识体系:发送缓冲区调优全景
下面这张图,把发送缓冲区调优的核心逻辑串起来了。你可以把它当作调优时的检查清单。
3.6 调优步骤总结
如果你现在就要调一个线上服务,按这个顺序来:
- 测网络环境:用
ping拿 RTT,用iperf拿带宽,算出 BDP。 - 设初始值:BDP × 2 作为
SO_SNDBUF的候选值。 - 压测验证:用
sar -n TCP或ss -ti观察发送缓冲区是否经常满。 - 微调:如果
send()阻塞频繁,加大;如果内存压力大,减小。 - 考虑自动调优:如果不想手动管理,调大
tcp_wmem的max,不设SO_SNDBUF。
发送缓冲区调优,说白了就是找到那个「不卡顿、不浪费」的平衡点。你多测几次,慢慢就有感觉了。