Files
cs-note/hhs/NETWORK/04-传输层/04-TCP流量控制与拥塞控制.md
T
2026-05-24 11:42:38 +08:00

190 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [计算机网络, TCP流量控制, 拥塞控制, BBR, Cubic]
create time: 2026-05-18 02:00
---
# 流量控制与拥塞控制
## 概述
TCP 有两个"刹车"机制:流量控制(防止接收方被淹没)和拥塞控制(防止网络被撑爆)。前者是**端到端**的,后者是**全网协同**的。
## 流量控制(Flow Control)
### 滑动窗口原理
```mermaid
flowchart LR
Sender["发送方"] -->|"seq 100..299"| Recv["接收方<br/>rwnd = 300"]
Recv -->|"ACK=100, window=300"| Sender
Note over Sender:"可用窗口 = min(cwnd, rwnd)"
Note over Recv:"rwnd = RecvBufSize - UnackedData"
```
| 概念 | 含义 |
|------|------|
| **rwnd** (Receive Window) | 接收方的剩余缓冲区大小,告诉发送方"我还能收多少" |
| **cwnd** (Congestion Window) | 拥塞窗口,由发送方根据网络状况自行估算 |
| **实际可用窗口** | `min(rwnd, cwnd)` — 取两者较小值 |
### 零窗口问题
当接收方缓冲区满时,会通告窗口大小为 0:
```
发送方收到 ACK with window=0 → 停止发送数据 → 进入 persist timer 探测状态
```
### Zero Window Probe (ZWP)
Linux 内核的 ZWP 定时发送一个字节的数据来探测窗口是否恢复:
```bash
$ sysctl net.ipv4.tcp_no_metrics_save
net.ipv4.tcp_no_metrics_save = 1 # 避免缓存错误的 RTT 值
```
> [!warning] 死锁场景
> 如果 ZWP 丢失且对端没有响应,连接会永久僵死。解决方案:应用层实现超时重连。
## 拥塞控制(Congestion Control)
### 四种核心算法
```mermaid
flowchart TD
subgraph "慢启动阶段 Slow Start"
A["cwnd = 1 MSS"] -->|"每个RTT翻倍(指数增长)"| B[cwnd ≥ ssthresh?]
end
B -->|"是"| C["进入拥塞避免<br/>(线性增长)"]
subgraph "拥塞避免阶段 Congestion Avoidance"
C -->|"每个RTT+1 MSS<br/>(线性增长)"| D[检测到丢包? ]
end
D -->|"是"| E["ssthresh = cwnd / 2<br/>cwnd = 1 (或 2 MSS)"]
E --> F["回到慢启动<br/>(快速恢复)"]
F --> G["快速恢复后<br/>进入拥塞避免"]
style A fill:#DDA0DD,color:#000
style C fill:#FFD700,color:#000
style E fill:#FF6B6B,color:#fff
style G fill:#98FB98,color:#000
```
### 详细对照表
| 阶段 | cwnd 变化 | 增长速度 | 适用场景 |
|------|----------|---------|---------|
| **慢启动** | 每 RTT × 2 | 指数 | 初始探测、快速恢复后 |
| **拥塞避免** | 每 RTT + 1 MSS | 线性 | 接近容量时的精细调节 |
| **快重传** | 收到 3 个 Dup ACK | 立即重传 | 轻微丢包 |
| **快恢复** | cwnd = max(cwnd/2, 2MSS) | 从减半值开始慢启动 | 伴随快重传 |
### 三种触发事件
| 事件 | 操作 | ssthresh | cwnd |
|------|------|---------|------|
| 3个重复 ACK | 快重传 + 快恢复 | cwnd / 2 | max(cwnd/2, 2×MSS) |
| RTO 超时 | 慢开始 | cwnd / 2 | 1 MSS (Reno/Cubic) / 3 segments (NewReno) |
| SACK 确认部分缺失 | SACK-based fast recovery | 同上 | 同上 |
## Linux 拥塞控制算法对比
### 可选算法列表
```bash
$ cat /proc/sys/net/ipv4/congestion_control
bbr
$ ls /lib/modules/$(uname -r)/kernel/net/ipv4/*_cc.ko*
tcp_cubic.ko tcp_dctcp.ko tcp_htcp.ko tcp_highspeed.ko tcp_hybla.ko tcp_illinois.ko tcp_lp.ko tcp_reno.ko tcp_scalable.ko tcp_vegas.ko tcp_westwood.ko tcp_yeah.ko tcp_bbr.ko
```
### 主流算法对比
```mermaid
flowchart LR
Reno["TCP Reno<br/>• cwnd/2 on drop<br/>• Classic cubic curve"]
Cubic["TCP Cubic (Linux default)<br/>• Non-linear recovery<br/>• Better for high-BDP links"]
BBR["Google BBR v2<br/>• Model bottleneck<br/>• Max throughput, low latency<br/>• No loss-based triggering"]
DCTCP["DCTCP (Datacenter)<br/>• ECN-based<br/>• Near-zero congestion in DC"]
style Reno fill:#DDA0DD,color:#000
style Cubic fill:#FFD700,color:#000
style BBR fill:#98FB98,color:#000
style DCTCP fill:#B0C4DE,color:#000
```
| 特性 | Reno | Cubic (默认) | BBR v2 | DCTCP |
|------|------|-------------|--------|-------|
| 触发方式 | 丢包/重复ACK | 丢包/重复ACK | **延迟模型** | ECN标记 |
| 高带宽利用 | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 低延迟 | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 数据中心 | ❌ | ✅ | ✅ (推荐) | ✅ (需ECN支持) |
| WAN/广域网 | ✅ | ✅ | ✅✅ | ❌ |
| Google 生产环境 | — | — | ✅ 全部使用 | — |
| 配置复杂度 | 零 | 调参选项多 | 最少 (只需设置目标 bw/rtt) | 需交换机支持 ECN |
### BBR v2 核心思想
BBR 不依赖丢包作为拥堵信号——它直接建立网络管道模型:
```
BBR 维护三个核心测量值:
┌─────────────┬──────────────┬─────────────┐
│ Bottleneck │ Propagation │ In-flight │
│ Bandwidth │ Delay (BDP) │ Data Limit │
│ (最高吞吐) │ (最低延迟) │ (当前水量) │
└─────────────┴──────────────┴─────────────┘
然后动态调整: send_rate ≤ bw AND in_flight ≤ bdp + buffer
```
```bash
# 切换为 BBR
$ sudo sysctl net.ipv4.tcp_congestion_control=bbr
$ echo 'net.ipv4.tcp_congestion_control=bbr' >> /etc/sysctl.conf
# 验证
$ ss --info
... cubic bbr ...
```
## Go 中的实践
### 设置 TCP 选项
```go
import "golang.org/x/net/ipv4"
// 设置 socket 级别的拥塞控制相关参数
conn, _ := net.Dial("tcp", "example.com:80")
c := ipv4.NewConn(conn.RawConn())
c.SetTrafficClass(0) // DSCP/ECN
c.SetNoDelay(false) // Nagle 算法关闭 → 小包立刻发
```
```go
// http.Transport 的连接管理
transport := &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
}
// 自定义 DialContext 以启用 keepalive
dialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second, // 替代内核默认的 7200s
}
```
## 关联笔记
- [[hhs/NETWORK/TCP段结构与状态机]] — cwnd/rwnd 在 TCP 首部中的 Window 字段
- [[hhs/NETWORK/TCP三次握手与四次挥手]] — 握手中协商的 Window Scale 选项
- [[hhs/NETWORK/TCP粘包与拆包]] — PSH 标志与流量控制的关联