TCP拥塞控制原理:慢启动、拥塞避免、快速重传、快速恢复
聊到TCP拥塞控制,我脑子里第一个蹦出来的画面,是十年前在数据中心调优时遇到的一个场景。那会儿业务流量一上来,整个集群的TCP连接就像堵车一样,丢包率飙升,吞吐量直接腰斩。后来我花了整整一周,把拥塞控制的四个核心阶段——慢启动、拥塞避免、快速重传、快速恢复——彻底捋了一遍,才把问题压下去。
说白了,TCP拥塞控制就是一套“交通管制”策略。它不让发送方一股脑把数据全扔出去,而是根据网络的实际承载能力,动态调整发送速率。这四招,就是它的核心手段。
1. 慢启动:从试探开始
慢启动这个名字,其实有点误导人。它并不是真的“慢”,而是“谨慎地加速”。
一开始,发送方并不知道网络能承受多少数据。所以它先设一个很小的拥塞窗口(cwnd),比如1个MSS(最大报文段大小)。然后每收到一个ACK,窗口就翻倍。你想想看,这其实是指数增长。
关键点:慢启动的窗口增长是“每RTT翻倍”,而不是“每包翻倍”。
举个例子:
- 初始cwnd = 1 MSS
- 收到1个ACK后,cwnd = 2 MSS
- 再收到2个ACK后,cwnd = 4 MSS
- 再收到4个ACK后,cwnd = 8 MSS
我在项目中遇到过一种情况:有些工程师把慢启动阈值(ssthresh)设得特别大,结果连接刚建立时,窗口涨得太快,直接把中间路由器的缓存打爆了。嗯,这里要注意——慢启动不是无限加速的,它有一个“刹车点”,就是ssthresh。
当cwnd达到ssthresh时,慢启动结束,进入拥塞避免阶段。
2. 拥塞避免:线性增长,小心试探
进入拥塞避免后,窗口的增长方式从“指数”切换为“线性”。具体来说,每个RTT只增加1个MSS。说白了,就是每收到一个ACK,cwnd增加 1/cwnd 个MSS。
为什么这么设计?因为此时网络已经接近饱和了。再像慢启动那样猛冲,很容易引发拥塞。线性增长是一种“保守试探”——慢慢加,直到出现丢包。
我的经验:在长肥网络(高带宽高延迟)中,拥塞避免阶段的线性增长其实很慢。我曾经调过一个跨国专线,RTT接近200ms,窗口从1000涨到2000,花了整整20秒。这时候可以考虑启用TCP窗口缩放选项,或者改用BBR等新型拥塞控制算法。
3. 快速重传:不等超时,主动重传
传统TCP靠超时重传。但超时时间通常设得比较长(比如200ms以上),效率很低。快速重传的思路是:如果发送方连续收到3个相同的ACK(即重复ACK),就认为某个报文段丢了,不等超时,立即重传。
为什么会是3个?我个人的理解是:1个重复ACK可能是乱序,2个也可能是乱序,但3个重复ACK,丢包的概率就很高了。这是实践中总结出来的经验值。
注意:快速重传只针对单个报文段的丢失。如果发生大量丢包(比如网络严重拥塞),快速重传可能不够用,最终还是得靠超时重传来兜底。
4. 快速恢复:丢包后不归零
在早期的TCP实现中,一旦检测到丢包,cwnd直接降到1,重新慢启动。这太粗暴了——网络只是轻微拥塞,你却把窗口清零,白白浪费带宽。
快速恢复的改进在于:丢包后,cwnd减半(而不是归零),然后进入拥塞避免阶段。这样既响应了拥塞,又保留了部分吞吐能力。
具体流程是这样的:
- 收到3个重复ACK,触发快速重传
- ssthresh = cwnd / 2
- cwnd = ssthresh + 3(因为已经收到的重复ACK表明有3个报文段离开了网络)
- 每收到一个重复ACK,cwnd临时增加1(相当于“偷”一点带宽)
- 当收到新的ACK时,cwnd = ssthresh,进入拥塞避免
我曾经在调试一个视频流服务时,发现快速恢复阶段的“临时窗口膨胀”会导致短暂的流量尖峰。如果网络设备缓存很小,这个尖峰反而会引发新的丢包。所以,快速恢复不是万能的,它更适合缓存充足的网络。
核心逻辑流程图
下面这张图,是我自己画的一个简化版拥塞控制状态机。你可以把它当作“速查手册”。
这张图里,我特意用虚线标出了“超时回退到慢启动”的路径。这是很多新手容易忽略的——快速恢复不是万能的,一旦超时,一切归零。
四个阶段的对比
| 阶段 | 触发条件 | 窗口变化 | 典型场景 |
|---|---|---|---|
| 慢启动 | 连接建立 / 超时重传后 | 指数增长(每RTT翻倍) | 新连接、严重拥塞后恢复 |
| 拥塞避免 | cwnd >= ssthresh | 线性增长(每RTT +1 MSS) | 稳态传输 |
| 快速重传 | 收到3个重复ACK | 立即重传丢失报文 | 单个报文段丢失 |
| 快速恢复 | 快速重传后 | cwnd减半,临时膨胀 | 轻微拥塞,避免归零 |
避坑指南:我曾经在配置Linux内核参数时,把tcp_slow_start_after_idle设成了0,结果空闲连接恢复后直接跳过慢启动,窗口保持原值。这在某些场景下能提升性能,但如果网络状态已经变化,反而会引发拥塞。所以,除非你很清楚自己在做什么,否则别乱改这个参数。
最后说一句:这四个阶段不是孤立的,它们是一个闭环。慢启动试探网络容量,拥塞避免维持稳态,快速重传和快速恢复应对小规模丢包。理解了这个闭环,你就能在遇到TCP性能问题时,快速定位是哪个环节出了岔子。
可可精品资料库