4. TCP/IP协议栈瓶颈:从三次握手到参数调优
TCP/IP协议栈,说白了就是网络通信的交通规则。我做了十几年通信优化,见过太多应用卡在网络层。今天咱们就聊聊,怎么找到这些瓶颈,怎么突破它们。
4.1 TCP三次握手:你被延迟坑过吗?
三次握手,每个网络工程师都背过。但实际项目中,我见过最离谱的案例——一个游戏服务器,玩家登录要等3秒。查了半天,发现是三次握手在作祟。
为什么会这样?
正常流程大家都懂:
- 客户端发SYN
- 服务端回SYN+ACK
- 客户端再发ACK
但问题出在哪儿?我告诉你,两个地方最容易踩坑:
第一个坑:SYN Flood攻击防护
Linux默认开启tcp_syncookies。当半连接队列满了,系统会用SYN Cookie机制。这本身没问题,但如果你服务器并发高,半连接队列太小,就会频繁触发Cookie计算。每次计算都要消耗CPU,延迟就上去了。
我个人习惯,高并发服务器会把net.ipv4.tcp_max_syn_backlog调到1024以上。曾经有个电商项目,双十一前夜调了这个参数,登录成功率从92%直接飙到99.5%。
第二个坑:TCP Fast Open(TFO)
你想想看,每次请求都要三次握手,是不是很浪费?TFO允许在SYN里携带数据。我建议,如果你的应用是短连接频繁请求,比如API网关,一定要开启TFO。
# 开启TFO
net.ipv4.tcp_fastopen = 3
注意:TFO需要客户端和服务端都支持。我曾经在一个老系统上开了TFO,结果某些旧版Android客户端直接连不上。嗯,这里要注意兼容性测试。
4.2 滑动窗口:别让你的带宽白费
滑动窗口机制,说白了就是控制发送速度。接收方告诉发送方:「我还能收多少,你看着发」。
但实际中,我见过最蠢的问题——窗口缩成0了,双方还在那干等。
为什么会窗口为0?
- 接收方应用层读得太慢
- 缓冲区设置太小
- 内存压力导致内核收缩窗口
我曾经排查过一个视频流卡顿问题。服务器带宽100Mbps,客户端却只能跑5Mbps。抓包一看,窗口长期在64KB徘徊。调大接收缓冲区后,直接跑到80Mbps。
关键参数:
| 参数 | 默认值 | 推荐值 | 说明 |
|---|---|---|---|
| tcp_rmem | 4096 87380 6291456 | 8192 131072 16777216 | 读缓冲区(min default max) |
| tcp_wmem | 4096 16384 4194304 | 8192 65536 8388608 | 写缓冲区(min default max) |
| tcp_window_scaling | 1 | 1 | 开启窗口缩放,支持>64KB窗口 |
我建议,对于大带宽长肥网络(比如跨洲专线),一定要确认tcp_window_scaling是开启的。否则窗口最大64KB,带宽再高也跑不起来。
4.3 拥塞控制算法:Cubic vs BBR
拥塞控制,就是网络堵车时的应对策略。传统Cubic算法,我用了十年。直到Google推出BBR,我才发现原来可以这么优雅。
Cubic算法:
- 基于丢包检测
- 窗口呈三次函数增长
- 适合有线网络,丢包率低的环境
BBR算法:
- 基于带宽和RTT测量
- 不依赖丢包
- 适合高延迟、有丢包的无线网络
我记得有个项目,客户在海外用4G网络访问国内服务器。Cubic下平均延迟300ms,经常超时。换成BBR后,延迟降到150ms,吞吐量翻了一倍。
什么时候用BBR?
- 无线网络(4G/5G/WiFi)
- 卫星链路
- 跨洲长肥网络
- 有随机丢包的环境
什么时候用Cubic?
- 数据中心内部
- 有线网络,丢包率<0.1%
- 需要公平竞争的场景
注意:BBR在公平性上不如Cubic。如果多个流共享同一瓶颈链路,BBR可能会抢占更多带宽。我曾经在IDC内部测试,BBR流会挤占Cubic流的带宽,导致其他服务不稳定。
# 查看当前拥塞控制算法
sysctl net.ipv4.tcp_congestion_control
# 切换到BBR
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
4.4 TCP参数调优:实战经验总结
参数调优,说白了就是找到最适合你场景的配置。没有银弹,但有套路。
我总结了一套「三板斧」:
- 先看瓶颈在哪:CPU?内存?带宽?延迟?
- 再调核心参数:缓冲区、超时、重传
- 最后验证效果:A/B测试,对比指标
常用调优参数一览:
| 参数 | 作用 | 我的推荐 |
|---|---|---|
| tcp_keepalive_time | 空闲多久开始探测 | 600秒(默认7200) |
| tcp_keepalive_intvl | 探测间隔 | 30秒(默认75) |
| tcp_keepalive_probes | 探测次数 | 3次(默认9) |
| tcp_fin_timeout | FIN_WAIT2超时 | 15秒(默认60) |
| tcp_tw_reuse | 复用TIME_WAIT连接 | 1(开启) |
我曾经遇到一个奇葩问题:服务器连接数飙到10万+,全是TIME_WAIT。调了tcp_tw_reuse和tcp_fin_timeout后,连接数降到2万,内存占用直接减半。
嗯,这里要注意一个误区。很多人以为参数越大越好。其实不是。比如tcp_rmem设得太大,反而会浪费内存,甚至导致OOM。我建议根据实际带宽和延迟来算:
# 估算最佳缓冲区大小
# 带宽延迟积(BDP) = 带宽(bps) × RTT(秒) / 8
# 例如:100Mbps带宽,50ms延迟
# BDP = 100 * 10^6 * 0.05 / 8 = 625000 bytes ≈ 610KB
所以缓冲区设到1MB左右就够用了。设成16MB纯属浪费。
最后提醒一句:调参前一定要备份原配置。我曾经在线上直接改参数,结果导致所有连接断开。从那以后,我都是先在测试环境验证,再灰度上线。
以上就是TCP/IP协议栈瓶颈的核心内容。记住,没有万能配置,只有最适合你场景的方案。多抓包、多分析、多验证,你也能成为TCP调优高手。