第1章:TCP/IP 协议栈深度解析:三次握手与四次挥手的性能开销
大家好,我是你们的老朋友。今天咱们聊聊TCP/IP协议栈里最核心的几个机制。说实话,我做了十几年通信优化,见过太多系统因为TCP参数没调好而翻车。嗯,咱们先从最基础的握手和挥手说起。
1.1 三次握手:不只是打个招呼那么简单
三次握手,说白了就是客户端和服务端互相确认「我准备好了」的过程。但你别小看这三次交互,每一次都有它的门道。
核心流程:
- 第一次:客户端发SYN,告诉服务端「我想连你」
- 第二次:服务端回SYN+ACK,说「收到,我也准备好了」
- 第三次:客户端发ACK,确认「好,咱们开始干活」
我个人习惯把三次握手看作「电话确认」——你拨号(SYN),对方接起来说「喂?」(SYN+ACK),你回一句「是我」(ACK)。少一次都不行。
性能开销在哪?
我遇到过不少团队,觉得三次握手就多了一个RTT(往返时间),没啥大不了的。但你想想看,在高并发场景下,每个连接都多一个RTT,累积起来就是灾难。
举个例子:假设你的服务部署在美西,客户端在亚洲,RTT大约150ms。每次握手多花150ms,100万个连接就是150秒的额外延迟。这还没算上服务端维护半连接队列的内存开销。
避坑指南:
我曾经在优化一个电商秒杀系统时,发现SYN Flood攻击导致服务端半连接队列爆满。后来我调整了tcp_max_syn_backlog和tcp_syncookies参数,才把问题压下去。记住,生产环境一定要开启syncookies。
1.2 四次挥手:优雅离场还是被迫分手?
四次挥手比握手更复杂。为什么?因为TCP是全双工的,双方都要独立关闭自己的通道。
挥手流程:
- 主动方发FIN,说「我不发了」
- 被动方回ACK,说「知道了,但我还有数据要发」
- 被动方发FIN,说「我也发完了」
- 主动方回ACK,说「收到,拜拜」
这里有个坑——TIME_WAIT状态。主动方在发送最后一个ACK后,会进入TIME_WAIT状态,持续2MSL(最大报文段生存时间)。为什么?因为要确保被动方收到了ACK,万一没收到还能重传。
注意:
TIME_WAIT过多会耗尽端口资源。我见过一个案例,某网关服务器因为TIME_WAIT堆积,导致新连接无法建立。解决方案是开启tcp_tw_reuse和tcp_tw_recycle(注意:内核4.12以后移除了recycle)。
1.3 滑动窗口:流量控制的艺术
滑动窗口,说白了就是接收方告诉发送方「你一次能发多少,别撑死我」。这个机制直接影响吞吐量。
窗口大小的影响:
| 窗口大小 | 吞吐量表现 | 适用场景 |
|---|---|---|
| 过小(< 64KB) | 低,发送方频繁等待ACK | 低延迟、小数据量 |
| 适中(64KB - 1MB) | 良好,平衡延迟和吞吐 | 大多数Web应用 |
| 过大(> 1MB) | 可能触发拥塞控制,反而降低 | 高带宽、低丢包网络 |
我建议你在调优时,先看net.ipv4.tcp_rmem和net.ipv4.tcp_wmem的默认值。很多Linux发行版默认窗口太小,不适合现代网络。
1.4 拥塞控制:慢启动、拥塞避免、快速重传
拥塞控制是TCP的「刹车系统」。它防止你发得太快,把网络堵死。咱们一个一个说。
慢启动
慢启动不是真的慢,而是「试探性加速」。刚开始时,拥塞窗口(cwnd)很小,每收到一个ACK就翻倍。指数增长,直到遇到丢包或达到慢启动阈值(ssthresh)。
我记得有一次优化文件传输服务,发现慢启动阶段太保守。后来我把初始cwnd从10提升到20,小文件传输速度提升了30%。但要注意,别调太大,否则容易造成网络拥塞。
拥塞避免
当cwnd达到ssthresh后,TCP进入拥塞避免阶段。这时候增长变慢——每个RTT只增加1个MSS(最大报文段大小)。说白了就是「小心试探,别惹事」。
为什么这样设计?因为网络状况随时在变,线性增长比指数增长更安全。你想想看,如果一直指数增长,网络早就炸了。
快速重传
当接收方收到乱序报文时,会立即发送重复ACK。发送方收到3个重复ACK后,不等超时就直接重传。这就是快速重传。
关键点:
快速重传比超时重传快得多。超时重传至少等一个RTO(重传超时时间),而快速重传几乎立即触发。我建议你监控tcpi_retransmits指标,如果快速重传比例过高,说明网络丢包严重。
1.5 知识体系总览
下面这张图总结了本章的核心逻辑。我习惯用这种图来梳理思路,一目了然。
1.6 实战调优建议
说了这么多理论,咱们来点实际的。我总结了几条调优经验:
- 连接复用:用HTTP Keep-Alive或连接池,减少三次握手次数。我在项目中见过,开启Keep-Alive后,QPS提升了40%。
- 调整初始窗口:把
initcwnd从10调到20-30,适合高带宽网络。但别超过40,否则容易造成burst。 - 监控丢包:用
ss -ti查看TCP重传率。如果超过1%,说明网络有问题,先别急着调参数。 - 开启BBR:如果内核支持,用BBR拥塞控制算法代替CUBIC。BBR在丢包环境下表现更好。
小技巧:
我习惯在压测时同时抓包(tcpdump)和看内核统计(/proc/net/tcp)。抓包能看到握手细节,内核统计能看到重传和窗口变化。两者结合,问题定位快得多。
好了,这一章就到这里。TCP/IP协议栈的调优是个系统工程,咱们后面几章会深入每个细节。记住,没有银弹,只有不断测试和调整。