第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是全双工的,双方都要独立关闭自己的通道。

挥手流程:

  1. 主动方发FIN,说「我不发了」
  2. 被动方回ACK,说「知道了,但我还有数据要发」
  3. 被动方发FIN,说「我也发完了」
  4. 主动方回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 知识体系总览

下面这张图总结了本章的核心逻辑。我习惯用这种图来梳理思路,一目了然。

TCP/IP 协议栈性能开销核心逻辑 三次握手 SYN → SYN+ACK → ACK 开销:1个RTT + 内存 优化:连接复用 四次挥手 FIN → ACK → FIN → ACK 开销:TIME_WAIT 2MSL 优化:tcp_tw_reuse 滑动窗口 流量控制 影响:吞吐量 优化:调大窗口 拥塞控制 慢启动 指数增长 cwnd 直到丢包或阈值 拥塞避免 线性增长 cwnd 每个RTT +1 MSS 快速重传 3个重复ACK触发 不等超时直接重传 核心:握手/挥手决定连接建立/关闭开销,滑动窗口控制流量,拥塞控制防止网络崩溃 三者共同决定TCP吞吐量,调优需综合考虑

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协议栈的调优是个系统工程,咱们后面几章会深入每个细节。记住,没有银弹,只有不断测试和调整。

可可精品资料库