190 lines
6.3 KiB
Markdown
190 lines
6.3 KiB
Markdown
|
|
---
|
|||
|
|
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 标志与流量控制的关联
|