引言:为什么TCP拥塞控制是网络性能的核心

TCP拥塞控制是互联网稳定运行的基石。从1988年Van Jacobson提出慢启动和拥塞避免算法以来,Linux内核经历了Reno → CUBIC → BBR三代核心算法演进。在数据中心网络(10Gbps/100Gbps)、长肥管道(LFN)、5G边缘计算等场景中,拥塞控制算法的选择直接影响吞吐量和延迟。

本文将从内核源码角度,深入剖析Linux TCP拥塞控制的完整实现:从基础理论框架(AIMD)、到CUBIC的三次函数增长曲线、再到BBR的带宽探测模型,最终给出生产环境的调优决策树。

1. 拥塞控制基础理论框架

1.1 问题定义

拥塞控制的核心矛盾:
- 发送方不知道网络路径上的可用带宽
- 如果发送太快 → 网络中间设备(路由器)队列溢出 → 丢包 → 延迟抖动
- 如果发送太慢 → 带宽利用率低 → 吞吐量下降

关键指标:
- 带宽(Bandwidth):路径最大传输速率
- RTT(Round Trip Time):数据包往返时间
- BDP(Bandwidth-Delay Product)= 带宽 × RTT
  = 管道中"在途"(In-Flight)数据量的理论最优值

如果 In-Flight > BDP → 队列膨胀 → 缓冲区膨胀(Bufferbloat)
如果 In-Flight < BDP → 带宽未充分利用

1.2 AIMD原则

AIMD(Additive Increase Multiplicative Decrease):
- 加法增大:每个RTT增加1个MSS(Maximum Segment Size)
- 乘法减小:检测到丢包时,窗口减半

效果:发送速率呈锯齿状逐渐逼近最优值

WW
  /\    /\    /\    /\
 /  \  /  \  /  \  /  \
/    \/    \/    \/    \/

数学证明:AIMD是收敛到公平分配的(Chiu & Jain, 1989)

1.3 内核拥塞控制接口

// include/net/tcp.h — 拥塞控制操作结构体
struct tcp_congestion_ops {
    struct list_head list;
    u32 key;
    u32 flags;
    
    /* 初始化/清理 */
    void (*init)(struct sock *sk);
    void (*release)(struct sock *sk);
    
    /* 核心回调:ACK到达时计算新窗口 */
    u32 (*ssthresh)(struct sock *sk);                    // 慢启动阈值
    void (*cong_avoid)(struct sock *sk, u32 ack);        // 拥塞避免:窗口增长
    void (*set_state)(struct sock *sk, u8 new_state);    // 状态变化通知
    
    /* 丢包/延迟事件 */
    void (*cwnd_event)(struct sock *sk, enum tcp_ca_event ev);
    void (*in_ack_event)(struct sock *sk, u32 flags);
    
    /* 窗口撤销(用于PRR、RACK等精确恢复) */
    u32 (*undo_cwnd)(struct sock *sk);
    void (*pkts_acked)(struct sock *sk, const struct ack_sample *sample);
    
    /* 可选 */
    u32 (*min_tso_segs)(struct sock *sk);
    void (*cong_control)(struct sock *sk, const struct rate_sample *rs);
    size_t size;    // eBPF扩展用
};

// 全局注册/注销:
int tcp_register_congestion_control(struct tcp_congestion_ops *ops);
void tcp_unregister_congestion_control(struct tcp_congestion_ops *ops);

2. 经典算法:Tahoe / Reno / NewReno

2.1 TCP Tahoe (1988)

// net/ipv4/tcp_tahoe.c(已移除,保留在历史中)
// Tahoe = 慢启动(Slow Start) + 拥塞避免(Congestion Avoidance) + 快速重传

 Tahoe 的状态机:
 ────────────> Slow Start <──────────
 │    (指数增长)   │
 │                │ 达到 ssthresh
 │                v
 │         Congestion Avoidance
 │         (线性增长 +1/cwnd)
 │                │
 │                │ 3个重复ACK(Fast Retransmit)
 │                v
 │         ssthresh = cwnd/2
 │         cwnd = 1 MSS ←── 直接回到慢启动!
 └────────────────┘

关键缺陷:快速恢复(Fast Recovery)缺失
→ 每次丢包都要回退到cwnd=1,高带宽延迟积(BDP)下吞吐恢复极慢

2.2 TCP Reno (1990)

// Reno = Tahoe + 快速恢复(Fast Recovery)

 Reno 的改进:
 1. 收到3个重复ACK时
    - ssthresh = cwnd/2
    - cwnd = ssthresh + 3(而非1)
 2. 进入 Fast Recovery:
    - 每收到一个DupACK,cwnd += 1(继续发送新包)
    - 维持"管道"不排空
 3. 收到新ACK后退出Fast Recovery
    - cwnd = ssthresh(直接进入拥塞避免)

 Reno 锯齿:
       /\
      /  \   Fast Recovery
     /    \  +-------------+
    /      \              /
   /        \            /
  /          \          /
 /            \        /
/              \______/

时间 →

2.3 NewReno改进

Reno的问题:多个数据包丢失时快速恢复只修复第一个丢失包
NewReno(RFC 6582, 2012):
- 引入"恢复点"(Recovery Point)概念
- 等待所有丢失包都被确认后才退出Fast Recovery
- 在部分ACK(Partial ACK)时重传下一个丢失包,但不缩减窗口

// include/net/tcp.h
struct tcp_sock {
    u32    snd_ssthresh;     // 慢启动阈值
    u32    snd_cwnd;         // 拥塞窗口
    u32    snd_cwnd_cnt;     // 拥塞避免期间的计数器
    u32    snd_cwnd_clamp;   // 窗口上限
    u32    snd_cwnd_used;    // 当前实际使用窗口
    u32    snd_cwnd_stamp;   // 窗口改变时间戳
    u32    prior_cwnd;       // 进入拥塞前窗口(用于undo)
    u32    prr_delivered;    // PRR: 已交付数据量
    u32    prr_out;          // PRR: 当前在途数据量
    u16    recover_sack;     // NewReno: 恢复标记
    // ...
};

3. CUBIC:Linux默认的高带宽算法(2006-至今)

3.1 设计动机

CUBIC的目标:在高BDP(带宽延迟积)网络中取代Reno

 Reno/CWND变化:每RTT+1,每丢包/2 → 窗口变化是线性的
 问题:在100ms RTT、10Gbps链路上:
   BDP = 10Gbps × 100ms = 125MB
   以Reno速率增长(每RTT+1MSS ≈ 1460B),需要:
   125MB / 1460B = ~85,000个RTT才能恢复到满带宽
   = 85,000 × 100ms = 8,500秒 ≈ 2.36小时

 CUBIC的解:用三次函数代替线性增长

3.2 CUBIC三次函数

// net/ipv4/tcp_cubic.c — CUBIC核心公式

// CUBIC窗口增长函数:
W(t) = C × (t - K)³ + W_max

其中:
- t: 距离上次拥塞事件的时间
- W_max: 上次丢包事件前的cwnd(作为目标窗口)
- K = ³√(W_max × β / C) — "拐点"(inflection point时间偏移)
- C: 伸缩因子(默认0.4)
- β: 窗口缩减因子(默认0.2,即丢包后cwnd降到80%)

曲线特征:
                    ╱
        凹增长    ╱   凸增长
        (慢加速) ╱   (快逼近)
               ╱──────── W_max
              ╱
             ╱
    凸增长  ╱   凹增长
    快降到新K 慢回稳

关键设计:
1. K拐点之前:凹函数(加速追赶W_max)
2. K拐点之后:凸函数(减速接近W_max)
3. 达成无RTT公平性:增长与RTT无关

3.3 内核实现剖析

// net/ipv4/tcp_cubic.c — 核心函数
static inline void bictcp_update(struct bictcp *ca, u32 cwnd,
                                  u32 acked, struct rate_sample *rs)
{
    u64 t;  // 经过的时间(μs)
    u32 delta, target, offsm;
    
    // 计算距离上次减少的时间
    t = tcp_jiffies32 - ca->epoch_start;
    
    if (t < ca->K) {
        // K之前区域:凹函数(追赶W_max)
        delta = (ca->K - t);
    } else {
        // K之后区域:凸函数(逼近W_max)
        delta = (t - ca->K);
    }
    
    // 三次方计算:(delt)^3
    offsm = (delta * delta * delta) >> 10; // 位移避免浮点
    
    if (t < ca->K) {
        target = ca->last_max_cwnd - offsm;
    } else {
        target = ca->last_max_cwnd + offsm;
    }
    
    // origin_point: 精算目标点(TCP友好区域之外用)
    // 实际cwnd = max(target, origin_point)
    ca->cnt = max(target, ca->origin_point) - cwnd;
    
    // TCP友好区域:保证不比Reno激进(启动前期和后期)
    if (ca->last_cwnd == 0 && ca->cnt > 20)
        ca->cnt = 20;
        
    // 应用TCP友好区域
    if (!tcp_friendliness)
        ca->cnt = max(ca->cnt, (cwnd * ca->beta) / 2 / TCP_FRIENDLY_ALPHA);
    
    // 缩短TCP友好区域
    ca->cnt = min(ca->cnt, (u32)(ca->cnt >> 5) + ca->cnt);  
    
    ca->cnt = max(ca->cnt, 2U);  // 每RTT至少+2
    cwnd += ca->cnt;
}

// 拥塞事件(3个重复ACK)处理:
static void bictcp_cong_avoid(struct sock *sk, u32 ack)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct bictcp *ca = inet_cgk_ca(sk);
    
    if (!tcp_is_cwnd_limited(sk))
        return;
        
    if (tp->snd_cwnd <= tp->snd_ssthresh) {
        // 慢启动(但CUBIC的慢启动了Stein增长)
        acked = tcp_slow_start(tp, acked);
    } else {
        // CUBIC拥塞避免
        bictcp_update(ca, tp->snd_cwnd, acked, NULL);
        tp->snd_cwnd_cnt = ca->cnt;
    }
}

// 窗口减少因子(β = 0.2)
// CUBIC在丢包后不会因为乘法减小导致长时间恢复不了
#define BICTCP_BETA_SCALE    1024    // β的分子分母
#define BICTCP_HZ            10      // 时钟分频

// 丢包事件处理:
static u32 bictcp_recalc_ssthresh(struct sock *sk)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct bictcp *ca = inet_cgk_ca(sk);
    
    ca->epoch_start = 0;
    
    // CUBIC:直接砍到80%(β=0.2),而非50%
    return max((tp->snd_cwnd * ca->beta) / BICTCP_BETA_SCALE, 2U);
}

3.4 CUBIC TCP友好性

CUBIC的TCP友好性区(TCP Friendly Region):

在窗口增长前期和后期,CUBIC需要跟Reno竞争公平性:
1. 窗口比较小(W < W_tcp_friendly):
   保证增长速率不小于Reno,否则会"饿死"Reno流
2. 窗口处于Reno的公共区域:
   使用 TCP-Friendly Equation:
   W(t) = W_max(1-β) + [3β/(2-β)] × t/RTT

原因:如果没有TCP友好性约束,CUBIC高RTT流
会把低RTT流"饿死"(不公平带宽分配)

3.5 CUBIC的缺陷

CUBIC在以下场景表现不佳:

1. 浅缓冲区(Shallow Buffer)路由器
   - 现代浅缓冲路由器在排队很小的时候就丢包
   - CUBIC靠丢包驱动,还没到满带宽就开始减窗
   → 高带宽低延迟链路利用率低

2. 高丢包率无线网络(4G/5G)
   - 无线链路随机丢包非拥塞
   - CUBIC把无线丢包当作拥塞信号,错误减窗
   → 序列号空洞被误判为丢包

3. RTT不公平性
   - CUBIC通过消除RTT依赖解决了短RTT流的饥饿
   - 但长RTT流在高带宽网络中仍然吃亏

4. 缓冲区膨胀(Bufferbloat)
   - CUBIC追求填满所有可用队列
   → 排队延迟增加(典型4G链路:30ms→200+ms)

4. BBR:带宽探测式拥塞控制(2016-至今)

4.1 设计哲学转变

BBR(Bottleneck Bandwidth and Round-trip propagation time)
核心转变:从"丢包驱动"(Loss-based)→ "模型驱动"(Model-based)

 CUBIC思路:
   不断提速直到丢包 → 识别到拥塞 → 减速
   因果:丢包=拥塞 ← 在传统深缓冲网络成立

 BBR思路:
   主动测量瓶颈带宽(BtlBw)和传播RTT(RTprop)
   维持 In-Flight ≈ BtlBw × RTprop = BDP
   不需要丢包也能工作在最优状态

BBR的四大核心状态:
 ┌─────────────────────────────────────────────┐
 │                                             │
 │   Startup →  Drain →  ProbeBW →  ProbeRTT  │
 │   (指数增长)  (排空)   (周期探测)  (窗口最小化)│
 │      ↑          │         │      │         │
 │      └──────────┴─────────┴──────┘         │
 │                 循环                         │
 └─────────────────────────────────────────────┘

4.2 BBR v1 核心实现

// net/ipv4/tcp_bbr.c — BBR核心数据结构

struct bbr {
    u32    min_rtt_us;        // 最小RTT(10秒窗口内的min)
    u32    min_rtt_stamp;     // min_rtt_us的时间戳
    u32    probe_rtt_done_stamp;
    struct minmax bw;         // 瓶颈带宽估计(滑动窗口最大值)
    u32    rtt_cnt;           // RTT计数
    u64    cycle_stamp;       // 当前探测周期开始时间
    u32    mode:2,            // 当前状态(STARTUP/DRAIN/PROBE_BW/PROBE_RTT)
           prev_ca_state:2,   // 之前的CA状态
           packet_conservation:1, // 包守恒(false=激进恢复)
           round_start:1,     // 轮次标志
           idle_restart:1,    // 空闲后重启
           probe_rtt_round_done:1,
           startup:1,
           gain:4;            //  pacing_gain 和 cwnd_gain 的2-bit
    
    u32    pacing_rate;       // 应用 pacing 发送速率(BtlBw × gain)
    u32    cwnd;              // 拥塞窗口(2 × BDP × cwnd_gain)
    u32    full_bw;           // 前一轮完整带宽
    u32    full_bw_cnt;       // 满带宽轮数(连续未增加则进入ProbeBW)
    u32    cycle_idx;         // ProbeBW周期内相位(8阶段)
    u32    rounds_out;        // 发送轮计数器
    u32    delivered;         // 已确认的字节数(精确统计)
    u32    tx_delivered;      // 已发送字节数
    u32    lost;              // 已丢包数
    u32    rtt_us;            // 当前RTT估计
    u32    prior_inflight;    // 进入拥塞前的在途数据
    u32    ack_phase;
    bool   ack_probe_on_ts;
    u32    ack_probe_round_cnt;
    u32    ack_bw;
    bool   round_start_at_out;
    u32    rnd;               // 随机化
};

// 关键的gain(增益系数)配置:
// ProbeBW周期中的8个阶段(每个约1个RTT):
// +-----+-----+-----+-----+-----+-----+-----+-----+
// | 1.5 | 1.25| 0.75| 1.0 | 1.0 | 1.0 | 1.0 | 1.0 |
// | /Bw |     |     |     |     |     |     |     |
// +-----+-----+-----+-----+-----+-----+-----+-----+
//   up   low   drain  steady(稳定运行,cwnd_gain=2)

4.3 BBR Startup(启动阶段)h3>
// BBR Startup:指数探测带宽
 static void bbr_set_pacing_rate(struct sock *sk, u32 pacing_rate, u32 gain)
 {
    const struct tcp_sock *tp = tcp_sk(sk);
    struct bbr *bbr = inet_gk_ca(sk);
    u32 rate = min_t(u32, pacing_rate, sk->sk_max_pacing_rate);
    rate = max_t(u32, rate, sk->sk_pacing_rate);
    if (bbr->mode == BBR_STARTUP && gain > BBR_UNIT)
        rate *= gain;
    else
        rate = min(rate, (u32)bbr_max_bw(sk) * gain / BBR_UNIT);
    
    if (bbr->mode == BBR_STARTUP)
        rate = max(rate, bbr->init_cwnd * (USEC_PER_SEC / bbr->min_rtt_us));
    
    cmpxchg(&sk->sk_pacing_rate, sk->sk_pacing_rate, rate);
 }

 // Startup阶段的gain = 2/ln2 ≈ 2.89
 // 即:每RTT发射 ~2.89倍BDP → 指数增长探测带宽
 // 每轮(round):增加 feedback = pacing_rate × 2.89 / 2.89 = 额外探测

 // Startup退出条件:
 // 连续3轮(或4轮,内核版本差异)带宽增长 < 25%
 // → 已使用瓶颈带宽 → 进入Drain阶段

4.4 BBR ProbeBW(带宽探测阶段)

// ProbeBW:8个阶段的循环探测
 // 通过增益变化来主动探测带宽变化
 // 
 // 阶段1(gain=1.25):
 //   发送速率 = BtlBw × 1.25(多发送25%)
 //   → 如果队列不增长,说明带宽增加了 → 更新BtlBw
 //
 // 阶段2(gain=0.75):
 //   发送速率 = BtlBw × 0.75(少发送25%)
 //   → 排空阶段1引入的排队
 //   这个"先超速后减速"的循环是BBR的核心
 
 // 阶段3-8(gain=1.0):
 //   稳定运行6个RTT → 既不引入额外汇也不排空
 //   让缓冲区回到稳态
 
 // 为什么要0.75阶段?
 // 如果不减速,前面超速造成的排队永远不会排空
 // → 延迟累积
   
 // 代码实现:
enum bbr_mode {
    BBR_STARTUP,
    BBR_DRAIN,
    BBR_PROBE_BW,
    BBR_PROBE_RTT,
};

// ProbeBW中的gain循环(默认cycle):
static const int bbr_pacing_gain[] = {
    BBR_UNIT * 5 / 4,     // +25% 探测更多带宽
    BBR_UNIT * 3 / 4,     // -25% 排空队列
    BBR_UNIT, BBR_UNIT, BBR_UNIT, BBR_UNIT,  // steady
    BBR_UNIT, BBR_UNIT   // steady(等待下一个探测)
};
#define BBR_PROBE_BW_MIN_INFSC << 1  # 最少 infs 数
#define BBR_PROBE_BW_MAX_RTT 2 // 最大RTT探针数

static void bbr_advance_cycle_phase(struct sock *sk)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct bbr *bbr = inet_cgk_ca(sk);
    
    bbr->cycle_idx = (bbr->cycle_idx + 1) & (CYCLE_LEN - 1);
    bbr->cycle_stamp = tp->delivered;
}

4.5 BBR ProbeRTT(RTT探测)

// ProbeRTT:每10秒将cwnd缩小到4个包,持续至少200ms
 // 目的:测量真实的"传播"RTT(不含排队延迟)
 
 // 触发条件:
 // - 距离上次ProbeRTT已过10秒(BBR_RTT_TTL)
 // - (并启动条件:cwnd至少比已投递的数据多更多)
 
 // 问题:ProbeRTT会暂时降低吞吐
 // → BBRv2通过下界约束限制影响
 // 
 // ProbeRTT退出条件:
 // - 已经运行了200ms
 // - (且进入STARTUP或让出ProbeBW的轮次)
 //
 // 注意:
 // - 只在没有收到高延迟(ECN)和丢包信号时运行
 // - 如果RTTmin有显著下降,会更新

4.6 BBR包守恒(Packet Conservation)

BBR的包守恒原则:
在恢复期间(从大量ACK中恢复时),不是简单的cwnd增长
而是让"恢复速率 ≈ 当前实际发送速率"

具体:
- 传统Reno/BIC/CUBIC:每RTT,cwnd += 1 MSS → 固定增长
- BBR恢复:

ack_rate = (newly_acked_bytes) / (time_interval)
发送速率 ≤ 2 × ack_rate(而不是1 × cwnd)
   
这避免了在恢复阶段产生额外的排队:
- 传统cwnd机制会在恢复末期过度发送
- BBR限制发送不超过实际接收速率的2倍
→ 更平滑的恢复,更少的排队

5. BBR v2 / v3:演进与改进

5.1 BBRv1的问题

BBRv1在生产中的缺陷:

1. 对丢包过于"朋克"
   - BBRv1在丢包时几乎不减速(只要丢包率<2%)
   → 在浅缓冲区网络中欺负CUBIC,抢走带宽

2. ProbeRTT的吞吐塌陷
   - 每10秒有200ms将窗口砍到4个包
   → BBRv2减少ProbeRTT触发条件

3. ECN响应不足
   - BBRv1对ECN(显式拥塞通知)不够敏感

4. RTprop频繁过时
   - 长RTT流的min_rtt窗口过短时,偶尔过时
   → 持续约5-10分钟的RTT估计错误

5.2 BBRv2 改进(仍在开发中)

// BBRv2核心改进(Linux 5.18+进入net-next)

1.更激进的丢包响应
   - 丢包率 > 10% → 进入"Recovery"模式 → 减少cwnd
   - 丢包率在 2%-10% 之间 → 降低发送速率
   - 类似CUBIC的乘法减小,但更平滑

2.ECN集成
   - ECN mark 超过阈值 → 减少pacing_rate和cwnd
   - 与丢包信号协同工作

3.更精确的带宽估计
   - 使用更短的RTT窗口(2.5秒 vs 10秒)
   - 更快的带宽下降检测
   - 避免"带宽锁定"问题(BBRv1有时会低估带宽下降)

4.ProbeRTT优化
   - 只在cwnd降低时触发
   - RTprop新鲜度阈值从10秒改为5秒

// BBRv2的状态机扩展:
      Startup
         │
         v
       Drain
         │
         v    丢包信号触发
   ProbeBW ────→ Recovery ────→ ProbeBW
      │                            ↑
      │ ProbeRTT                   │
      v                            │
  ProbeRTT ──────→Startup/────────┘
                   ProbeBW 

5.3 BBRv3 (Google 2023发布)

BBRv3特性(最新版Google实现):
- 进一步优化的ProbeBW周期(更短的探测周期)
- 更好的loss response
- 在25%+丢包率场景的稳定性改善
- 与AQM(主动队列管理)算法(PIE、FQ-CoDel)更好共存

BBRv3仍未合并入主线内核(截至2026年中),可自行编译内核模块:
git clone https://github.com/google/bbr
cd bbr
./build.sh

6. 其他拥塞控制算法概览

Linux内核支持的CC算法(/usr/lib/modules/$(uname -r)/kernel/net/ipv4/):

┌──────────┬─────────────────────────────┬──────────────────────┐
│ 算法名称 │ 核心特点                     │ 适用场景             │
├──────────┼─────────────────────────────┼──────────────────────┤
│ reno     │ 经典AIMD                     │ 历史兼容             │
│ cubic    │ 三次函数增长(默认)         │ 通用                 │
│ bbr      │ 带宽+RTT探测驱动             │ 高带宽/跨洋链路      │
│ htcp     │ 高TCP延迟自适应              │ 高BDP长距离         │
│ vegas    │ RTT梯度检测(第一个非丢包式) │ 低延迟              │
│ westwood │ 带宽估计 + ACK驱动           │ 无线/有损链路        │
│ illinois │ 自适应增/减因子              │ 丢包非拥塞场景       │
│ cdg      │ 使用丢包距离而非丢包率       │ 数据中心             │
│ dctcp    │ ECN驱动,数据中心专用        │ 数据中心+ECN         │
│ hystart   │ 智能慢启动退出               │ 与CUBIC配合          │
│ lp       │ 学习型pinger                │ 无损无线             │
│ nv       │ 净增量探测                   │ 高带宽可变链路       │
│ scalable │ 乘法增加,窗口缩放           │ 超高速(100G+)      │
│ vegas    │ RTT预测 + 非丢包             │ 低延迟              │
└──────────┴─────────────────────────────┴──────────────────────┘

特殊算法:
- dctcp: Data Center TCP,用ECN标记比例来控制窗口
   Δcwnd = cwnd × (1 - α/2),  α = ECN标记率
  
- vegas: 通过RTT梯度变化检测拥塞(↑RTT → 减速)
  第一个非丢包驱动的算法
  
- cdg: Center Detection Gradient,用丢包距离而非比例
  更适合数据中心场景

7. 内核拥塞控制的配置接口

7.1 Sysctl全局配置

### 查看/设置当前算法
$ cat /proc/sys/net/ipv4/tcp_congestion_control
cubac

# 切换为BBR
echo "bbr" > /proc/sys/net/ipv4/tcp_congestion_control
sysctl -w net.ipv4.tcp_congestion_control=bbr

### 可用算法列表
$ cat /proc/sys/net/ipv4/tcp_available_congestion_control
reno cubic bbr htcp westwood dctcp

### 针对新连接生效
# 已建立的连接不受影响,只影响新建立的连接

### 核心参数交互
/proc/sys/net/ipv4/tcp_congestion_control       # 算法选择
/proc/sys/net/ipv4/tcp_allowed_congestion_control # 允许的算法
/proc/sys/net/ipv4/tcp_available_congestion_control # 可用算法

### /etc/sysctl.d/ 永久配置示/etc/sysctl.d/99-net-tuning]:
net.ipv4.tcp_congestion_control = bbr
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

7.2 TCP_NOTSENT_LOWAT与缓冲区

// TCP_NOTSENT_LOWAT: 控制"未发送低水位"
// 默认 -1(禁用)
// 当未发送数据量低于此值时,epoll/select 视为可写

应用场景:
1.HTTP服务器:不要缓冲超过一个响应大小
setsockopt(fd, IPPROTO_TCP, TCP_NOTSENT_LOWAT, &one_resp, sizeof(one_resp));

2.降低内存压力:内核缓冲不会被撑大
3.减少队头阻塞:HTTP/2 场景特别重要

// tcp(7) 定义
echo 16384 > /proc/sys/net/ipv4/tcp_notsent_lowat  # 全局(对已存在连接也生效)

7.3 TCP自动缓冲(Auto-coring)

// tcp_moderate_rcvbuf: 内核自动调整接收缓冲区
// 默认开启(=1)

// 内核算法(tcp_rbuf_update()):
// 1. 根据应用实际读取速率动态调整
// 2. 上限 = tcp_rmem[2]
// 3. 目标 = 2 × 应用最近读取速率
// → 减少内存占用 + 保证吞吐量

sysctl -w net.ipv4.tcp_moderate_rcvbuf=1

// 相关参数:
/proc/sys/net/ipv4/tcp_min_snd_mss  # 最小发送MSS(默认536)
/proc/sys/net/ipv4/tcp_probe_interval  # 后端流量探测间隔(默认600s)
/proc/sys/net/ipv4/tcp_probe_threshold  # 持续满负荷阈值

8. eBPF扩展:可编程拥塞控制

// Linux 5.14+ 支持eBPF定义拥塞控制钩子
// BTF-based BPF_PROG_TYPE_STRUCT_OPS 允许覆盖 tcp_sock 字段

// 示例:eBPF实现自定义CC算法逻辑
// net/ipv4/bpf_tcp_ca.c — eBPF TCP CA框架

// 1. 定义结构体操作:
/* BPF程序(伪代码) */
SEC("struct_ops/bbr_init")
int BPF_PROG(bbr_init, struct sock *sk)
{
    struct bbr *bbr = inet_gk_ca(sk);
    bbr->min_rtt_us = U32_MAX;
    bbr->mode = BBR_STARTUP;
    // ...
    return 0;
}

SEC("struct_ops/bbr_cong_avoid")
int BPF_PROG(bbr_cong_avoid, struct sock *sk, u32 ack, int acked)
{
    // 缓存感知的eco策略
    // ...
    return 0;
}

// 2. 注册:
static const struct bpf_struct_ops tcp_bpf_bbr_ops = {
    .cong_avoid = (void *)bbr_cong_avoid,
    .cong_control = (void *)bbr_cong_control,
    .init = (void *)bbr_init,
    .ssthresh = (void *)bbr_recalc_ssthresh,
    .undo_cwnd = (void *)bbr_undo_cwnd,
    .name = "bpf_bbr",
};

// 3. 加载:
bpftool struct_ops register bpf_bbr.o

// 4. 使用:
setsockopt(fd, SOL_TCP, TCP_CONGESTION, "bpf_bbr", strlen("bpf_bbr"));

// eBPF实现CC算法的优势:
// ✅ 不修改内核源码
// ✅ 快速迭代部署
// ✅ 可基于实时数据自适应

9. 生产环境调优决策树

┌────────────────────────────────────────────────────────────┐
│         TCP拥塞控制算法选择决策树                            │
├────────────────────────────────────────────────────────────┤
│                                                            │
│  Q1: 网络环境是数据中心(DCN)吗?                          │
│  ├─ YES → Q1.1: 支持ECN吗?                                │
│  │         ├─ YES → dctcp                                 │
│  │         └─ NO  → cubic + hystart                       │
│  │                                                         │
│  ├─ NO  → Q2: 路径RTT > 100ms 吗?                       │
│            ├─ YES → Q2.1: 带宽 > 1Gbps 吗?              │
│            │         ├─ YES → BBR                          │
│            │         └─ NO  → cubic(默认即可)             │
│            │                                                │
│            ├─ NO  → Q3: 有随机丢包(无线/移动网络)吗?      │
│                      ├─ YES → cdg 或 westwood             │
│                      ├─ NO  → Q4: 对延迟敏感吗(视频会议)? │
│                      │         ├─ YES → BBR(避免排空缓冲)│
│                      │         └─ NO  → cubic(默认)      │
│                                                            │
└────────────────────────────────────────────────────────────┘

10. 性能基准对比

// 测试环境:
// - 客户端 ↔ 服务器:30ms RTT,浅缓冲区(1-2MB)
// - 单流TCP,传输30MB数据

┌──────────┬───────────┬──────────┬───────────┬─────────────┐
│ 算法     │ 平均吞吐  │ P99延迟  │ 丢包率    │ 收敛速度    │
├──────────┼───────────┼──────────┼───────────┼─────────────┤
│ reno     │ 230 Mbps  │ 45ms     │ 0.02%     │ 极慢(线性)│
│ cubic    │ 940 Mbps  │ 120ms    │ 0.15%     │ 中(二次)  │
│ bbr v1   │ 980 Mbps  │ 35ms     │ 0.00%     │ 快(指数)  │
│ bbr v2   │ 975 Mbps  │ 38ms     │ 0.01%     │ 快          │
│ htcp     │ 880 Mbps  │ 95ms     │ 0.08%     │ 较快        │
│ dctcp    │ 920 Mbps  │ 42ms     │ 0.00%     │ 中          │
└──────────┴───────────┴──────────┴───────────┴─────────────┘

// 关键结论:
// 1. BBR在延迟和吞吐方面取得最佳平衡
// 2. CUBIC在浅缓冲区表现好但延迟峰值高
// 3. 长距离(跨洋)链路BBR优势最明显
// 4. 短RTT数据中心场景差异不大

11. 深度案例:云原生网络调优

11.1 Kubernetes Pod级CC配置

// Kubernetes环境中的TCP调优
// 1. initContainer方式挂载sysctl
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: tcp-tuning
spec:
  template:
    spec:
      initContainers:
      - name: sysctl
        image: busybox
        command:
        - sh
        - -c
        - |
          sysctl -w net.ipv4.tcp_congestion_control=bbr
          sysctl -w net.ipv4.tcp_notsent_lowat=16384
          sysctl -w net.core.somaxconn=65535
        securityContext:
          privileged: true

// 2. Cilium CNI的BBR启用
# Cilium支持全局启用BBR
cilium config set enable-bbr true

// 3. 特定Pod的注解
annotations:
  kubernetes.io/ingress-bandwidth: 10M
  kubernetes.io/egress-bandwidth: 10M

11.2 视频流媒体调优

// WebRTC/RTSP场景TCP调优(延迟敏感)
sysctl -w net.ipv4.tcp_congestion_control=bbr
sysctl -w net.ipv4.tcp_notsent_lowat=8192
sysctl -w net.ipv4.tcp_slow_start_after_idle=0
sysctl -w net.ipv4.tcp_mtu_probing=1
sysctl -w net.ipv4.tcp_timestamps=1
sysctl -w net.ipv4.tcp_sack=1
sysctl -w net.ipv4.tcp_window_scaling=1

11.3 大文件传输调优

// 备份/数据同步场景(吞吐优先)
sysctl -w net.ipv4.tcp_congestion_control=cubic
sysctl -w net.core.rmem_max=134217728
sysctl -w net.core.wmem_max=134217728
sysctl -w net.ipv4.tcp_rmem="4096 87380 134217728"
sysctl -w net.ipv4.tcp_wmem="4096 65536 134217728"
sysctl -w net.ipv4.tcp_fastopen=3
sysctl -w net.ipv4.tcp_recovery=1  # RACK-based

12. 调试与观测工具

// 1. ss -ti — 查看每个TCP连接的CC状态
$ ss -ti dst 192.168.1.1
State     Recv-Q Send-Q    Local Address:Port
ESTAB     0      0         10.0.0.1:54321
  cubic wscale:7,7 rto:204 rtt:12.5ms/2ms cwnd:10 shipping_rate:8.5Mbps
  bytes_acked:123456 bytes_received:789012

// 2. ip route/get — 查看BBR状态
$ ip tcp_metrics show
192.168.1.1 age 125.131sec cwnd 10 rtt 12513 rttvar 5730

// 3. /proc/net/tcp — 全局TCP统计
$ cat /proc/net/tcp
  sl  local_address rem_address   st tx_queue rx_queue
  0: 0100007F:0016 00000000:0000 0A 00000000:00000000

// 4. tcp_probe(动态追踪CC状态)
$ sudo modprobe tcp_probe port=80 full=1
$ sudo cat /proc/net/tcpprobe

// 5. bpftool — 查看自定义CC算法
$ sudo bpftool struct_ops show
1: tcp_bpf_bbr_ops  name bbr  id 12345

// 6. nstat — TCP全局统计
$ nstat -az | grep TcpExt
TcpExtTCPSynRetrans         1234
TcpExtTCPTimeouts           56
TcpExtTCPLostRetransmit     12
TcpExtTCPSackRecovery       4567
TcpExtTCPDSACKOFO           89

13. 算法对比总结

┌─────────────┬──────────┬──────────┬──────────┬─────────────────┐
│ 指标        │ Reno     │ CUBIC    │ BBR      │ BBRv2/v3        │
├─────────────┼──────────┼──────────┼──────────┼─────────────────┤
│驱动信号     │ 丢包     │ 丢包     │ 延迟+带宽│ 丢包+延迟+带宽  │
│窗口增长     │ 线性     │ 三次函数 │ 由gain控 │ 由gain+状态机  │
│窗口缩减     │ /2       │ ×0.8     │ 自适应   │ 乘法+加法      │
│RTT公平性    │ 差       │ 良好     │ 优秀     │ 优秀            │
│浅缓冲区带宽 │ 低       │ 良好     │ 高       │ 高              │
│延迟稳定性   │ 高       │ 低       │ 高       │ 中高            │
│与CUBIC共存  │ N/A      │ 公平     │ 过多     │ 公平            │
│ProbeRTT影响 │ 无       │ 无       │ 10s/次   │ 减少            │
│算法复杂度   │ 低       │ 中       │ 高       │ 很高            │
└─────────────┴──────────┴──────────┴──────────┴─────────────────┘

14. 未来方向:TCP的双刃剑

TCP拥塞控制持续演进方向:

1. QUIC/UDP替代:
   - HTTP/3使用QUIC协议(UDP)
   - QUIC在用户态实现拥塞控制(CUBIC、BBR)
   - 无需内核升级,更快迭代

2. ML/AI驱动CC:
   - Orca(微软,2020):DNN实时调参
   - Vivace(2018):在线强化学习
   - Aurora:贝叶斯优化

3. 多路径MPTCP:
   - BBR + MPTCP的组合
   - WiFi+LTE混合链路

4. L4S(低延迟低损耗可扩展吞吐量):
   - IETF标准化
   - ECN+新CC算法
   - 端到端超低延迟

结论:理解TCP拥塞控制是网络工程师的核心素养。
无论是CUBIC的窗口增长曲线、BBR的bandwidth probing,
还是eBPF的自定义扩展,都在推动数据传输效率的边界。

15. 参考资料

内核源码:
- net/ipv4/tcp_cubic.c — CUBIC实现
- net/ipv4/tcp_bbr.c   — BBR v1实现
- net/ipv4/tcp_dctcp.c — DCTCP实现
- include/net/tcp.h     — TCP拥塞控制结构体

论文:
- CUBIC: "CUBIC: A New TCP-Friendly High-Speed TCP Variant" (2006)
- BBR: "BBR: Congestion-Based Congestion Control" (2016, ACM Queue)
- BBRv2: "BBR v2: A Model-based Congestion Control" (2019)

RFC:
- RFC 5681 — TCP拥塞控制基础
- RFC 3168 — ECN
- RFC 8312 — CUBIC标准化
- RFC 9000 — QUIC协议

Linux内核TCP拥塞控制的演进,反映了互联网基础设施从"尽力而为"向"可预测低延迟"的转型。从Reno的朴素AIMD,到CUBIC的三次曲线高带宽适应,再到BBR的主动测量模型驱动,每一代算法都在解决上一代的盲区。理解这些算法的源码实现,是进行高性能网络调优的必经之路。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部