拥塞控制基础
流量控制 vs 拥塞控制
| 维度 | 流量控制 | 拥塞控制 |
|---|---|---|
| 问题 | 接收方处理不过来 | 网络中间设备(路由器)处理不过来 |
| 范围 | 端到端 | 全局性(影响整个网络) |
| 信号 | 接收窗口 rwnd | 丢包 / 延迟 / ECN |
| 目标 | 保护接收方 | 保护网络 |
生活例子:
- 流量控制 = 餐厅后厨出菜速度匹配前厅服务员的端菜能力
- 拥塞控制 = 整条美食街的餐厅 collectively 控制客流,防止街道堵塞
拥塞窗口 cwnd 与慢启动阈值 ssthresh
TCP 发送方维护两个关键变量:
- cwnd(Congestion Window):拥塞窗口,发送方根据网络状况自我限制的发送量
- ssthresh(Slow Start Threshold):慢启动阈值,cwnd 超过此值后转入拥塞避免
实际发送窗口:
发送窗口 = min(rwnd, cwnd)
经典拥塞控制四阶段(RFC 5681)
1. 慢启动(Slow Start)
连接建立或超时恢复后:
- cwnd 初始值 = 1~2 MSS(RFC 5681 建议 2~4 MSS,现代 Linux 默认 10 MSS)
- 每收到一个 ACK,cwnd += 1 MSS
- 每 RTT 翻倍,呈指数增长
RTT 1: cwnd = 1 MSS, 发送 1 段
RTT 2: cwnd = 2 MSS, 发送 2 段
RTT 3: cwnd = 4 MSS, 发送 4 段
RTT 4: cwnd = 8 MSS, 发送 8 段
...
直到 cwnd ≥ ssthresh
为什么叫"慢"启动? 相对"一次性发满窗口"而言,它从 1 开始慢慢探测网络容量。实际上指数增长很快。
2. 拥塞避免(Congestion Avoidance)
当 cwnd ≥ ssthresh 时:
- 每收到一个 ACK,cwnd += 1/cwnd 个 MSS
- 每 RTT 增加 1 MSS,呈线性增长
RTT N: cwnd = 16 MSS
RTT N+1: cwnd = 17 MSS
RTT N+2: cwnd = 18 MSS
...
这种保守增长探测网络容量边界,避免突然注入大量数据导致拥塞。
3. 快重传(Fast Retransmit)
收到 3 个重复 ACK 时:
- 立即重传丢失段,不等待 RTO 超时
- 这通常意味着网络仅发生轻度拥塞(个别段丢失)
4. 快恢复(Fast Recovery)
快重传后,不将 cwnd 降至 1(避免慢启动的剧烈震荡):
ssthresh = cwnd / 2
cwnd = ssthresh + 3 MSS
然后每收到一个重复 ACK,cwnd += 1 MSS;收到新数据的 ACK 后,cwnd = ssthresh,进入拥塞避免。
为什么 +3 MSS? 因为收到 3 个重复 ACK 说明网络中还有 3 个段在传输,可以多发 3 个段保持管道满载。
拥塞信号与响应总结
| 事件 | 信号含义 | ssthresh | cwnd | 后续阶段 |
|---|---|---|---|---|
| 超时 | 严重拥塞 | cwnd/2 | 1 MSS | 慢启动 |
| 3 个重复 ACK | 轻度拥塞 | cwnd/2 | ssthresh+3 | 快恢复 → 拥塞避免 |
AIMD 的公平性
从控制理论角度,TCP 拥塞控制是**AIMD(Additive Increase Multiplicative Decrease)**算法:
- 加性增加:线性增长探测带宽
- 乘性减少:丢包时窗口减半
Chiu-Jain 公平性证明:多条连接共享瓶颈链路时,AIMD 会收敛到每条连接获得相等的带宽份额。
ECN:显式拥塞通知(RFC 3168)
传统 TCP 通过丢包推断拥塞,但丢包本身浪费带宽。ECN 让路由器在即将拥塞时主动标记而非丢弃:
- IP 首部 DSCP 字段中的两位:ECT(ECN-Capable Transport)和 CE(Congestion Experienced)
- TCP 首部保留两位:ECE(ECN-Echo)和 CWR(Congestion Window Reduced)
- 路由器标记 CE=1 而非丢弃 → 接收方回显 ECE=1 → 发送方像收到 dup ACK 一样减半 cwnd
优势:无损拥塞通知,避免丢包重传开销,对实时应用友好。
本篇小结
- 拥塞控制保护网络,流量控制保护接收方
- 核心变量:cwnd(拥塞窗口)、ssthresh(慢启动阈值)
- 四阶段:慢启动(指数)→ 拥塞避免(线性)→ 快重传 → 快恢复
- 超时 = 严重拥塞:cwnd=1, ssthresh=cwnd/2
- 3 个 dup ACK = 轻度拥塞:cwnd=ssthresh+3, ssthresh=cwnd/2
- AIMD 算法在数学上可证明收敛到公平带宽分配
- ECN:路由器主动标记拥塞,避免丢包
动手实践
查看 Linux 拥塞控制算法:
sysctl net.ipv4.tcp_congestion_control sysctl net.ipv4.tcp_available_congestion_control切换算法并测试:
sudo sysctl net.ipv4.tcp_congestion_control=cubic # 或 reno, bbr查看 ECN 设置:
sysctl net.ipv4.tcp_ecn用 iperf3 测试不同算法:
# 服务端 iperf3 -s # 客户端 iperf3 -c <server_ip> -t 30思考:为什么慢启动是指数增长而不是线性?如果 cwnd 每 RTT 只加 1,连接建立初期需要多久才能达到可用带宽?