第 6 章:多路径 QUIC——同时走两条路
从外卖配送说起
你点了外卖,配送平台发现有两条路可以到你家:
- 路线 A(主路):距离近但经常堵车,平时 10 分钟但堵车时要 30 分钟
- 路线 B(备路):距离远但基本不堵,稳定 15 分钟
单路径模式:骑手只走一条路。走 A 赌运气,走 B 浪费 A 畅通时的优势。
多路径模式:两个骑手同时出发!
- A 畅通时:两条路的外卖都很快到,你能吃到”更热”的饭
- A 堵车时:B 的骑手还在正常送,你不用等
- A 完全不通时(路被封了):无缝切到 B,你根本感觉不到变化
这就是多路径 QUIC 的核心价值:同时使用多条网络路径,提升带宽、降低延迟、增强可靠性。
为什么多路径在移动时代至关重要?
今天的手机有多少条”路”?
你的手机此刻可用的网络接口:
┌─────────────────┐
│ 你的手机 │
├─────────────────┤
│ WiFi (路径1) │ → 路由器 → 宽带ISP → 互联网
│ 4G/5G (路径2) │ → 基站 → 运营商核心网 → 互联网
│ (未来:WiFi6E) │ → 另一个频段的WiFi
└─────────────────┘
这两条路径的特征完全不同:
WiFi: 低延迟(5-20ms),高带宽(100Mbps+),但信号范围小、易断
4G: 高延迟(30-80ms),中带宽(10-50Mbps),但覆盖广、稳定
5G: 低延迟(5-15ms),超高带宽,但覆盖不连续
单路径的痛点
场景1:视频通话时走出 WiFi 范围
单路径(WiFi):画面卡住 → 断线重连(TCP) 或 切换重连(QUIC迁移)
→ 即使QUIC连接迁移也有短暂中断
多路径:WiFi 断了但 4G 一直在传 → 用户无感知切换
场景2:你想用满两条路径的带宽
单路径:只能用WiFi(100Mbps)或4G(50Mbps)之一
多路径:两条路径同时用 → 理论最大 150Mbps(带宽聚合)
场景3:弱网环境
单路径:WiFi 信号弱,丢包 10%,体验很差
多路径:WiFi 不稳时把重要数据冗余发到 4G → 冗余提高可靠性
Multipath QUIC vs 普通 QUIC 连接迁移
等等,第 3 章说过 QUIC 已经支持连接迁移了。那多路径和连接迁移有什么区别?
连接迁移(Connection Migration):
一次只用一条路径,切换时从旧路径"搬"到新路径
时间轴:
─────[WiFi]────────│切换│────────[4G]─────────
↑ 短暂中断
多路径(Multipath):
同时使用多条路径,数据可以分散在多条路径上
时间轴:
─────[WiFi]────────────────[WiFi衰减]────────
─────[4G]──────────────────[4G继续]──────────
↑ 同时在用! ↑ 无缝!
关键区别:
连接迁移 = 换路(旧的不用了)
多路径 = 开两条路同时用
| 特性 | 连接迁移 | 多路径 |
|---|---|---|
| 同时活跃路径数 | 1 | 2+ |
| 带宽聚合 | 不支持 | 支持 |
| 切换时中断 | 有短暂中断 | 无中断 |
| 冗余传输 | 不支持 | 支持 |
| 复杂度 | 低 | 高 |
| 标准化状态 | RFC 9000 (已完成) | draft-ietf-quic-multipath (进行中) |
多路径 QUIC 的技术架构
核心概念
┌───────────────────────────────────────────────────────┐
│ QUIC Connection │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Stream 0│ │ Stream 4│ │ Stream 8│ ← 应用层流 │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └───────────┼───────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ Scheduler │ ← 调度器:决定数据走哪条路 │
│ └──────┬──────┘ │
│ ┌───────┴───────┐ │
│ ┌─────┴─────┐ ┌─────┴─────┐ │
│ │ Path 0 │ │ Path 1 │ ← 网络路径 │
│ │ (WiFi) │ │ (4G) │ │
│ │ RTT:20ms │ │ RTT:80ms │ │
│ │ CC:BBR3 │ │ CC:BBR3 │ ← 每路径独立CC │
│ │ Recovery │ │ Recovery │ ← 每路径独立恢复 │
│ └───────────┘ └───────────┘ │
│ │
└───────────────────────────────────────────────────────┘
每个组件的职责
Path(路径):
- 一对本地地址和远端地址
- 有自己的 Connection ID
- 有自己的拥塞控制器(独立的 CWND)
- 有自己的 RTT 估算
- 有自己的丢包恢复机制
Scheduler(调度器):
- 接收上层要发送的数据
- 决定这个数据包走哪条路径
- 策略可以是:选最快的路径、两条都发、轮流发
Recovery(恢复):
- 每条路径独立检测丢包
- 路径 A 丢包不影响路径 B 的拥塞窗口
- 重传可以走原路径也可以走另一条路径
三种调度策略:TQUIC 的核心设计
TQUIC 实现了三种调度策略,每种适合不同的场景:
策略一:MinRtt(最小延迟优先)
逻辑:每次发包时,选择 RTT 最小的路径
┌─────────────────────────────────────┐
│ MinRtt Scheduler │
│ │
│ 输入:要发送的数据帧 │
│ │
│ 决策过程: │
│ 1. 查看所有活跃路径的当前 RTT │
│ Path 0 (WiFi): RTT = 15ms │
│ Path 1 (4G): RTT = 60ms │
│ 2. 选择 RTT 最小的路径 │
│ → 选择 Path 0 │
│ 3. 检查该路径的拥塞窗口是否有空间 │
│ 有空间 → 在 Path 0 上发送 │
│ 没空间 → 选择次优路径(Path 1) │
└─────────────────────────────────────┘
适用场景:对延迟敏感的应用(视频通话、游戏)
优点:数据总是走最快的路,端到端延迟最低
缺点:慢路径可能很少被使用 → 带宽聚合效果差
// TQUIC 中 MinRtt 调度器的核心逻辑(简化)
impl MultipathScheduler for MinRttScheduler {
fn select_path(&self, paths: &PathMap) -> Option<PathId> {
paths.iter()
.filter(|p| p.is_active() && p.can_send()) // ← 过滤可用路径
.min_by_key(|p| p.smoothed_rtt()) // ← 选RTT最小的
.map(|p| p.id())
}
}
策略二:Redundant(冗余发送)
逻辑:每个数据帧同时在所有活跃路径上发送一份
┌─────────────────────────────────────┐
│ Redundant Scheduler │
│ │
│ 输入:要发送的数据帧 F │
│ │
│ 决策过程: │
│ 1. 在 Path 0 上发送 F 的副本1 │
│ 2. 在 Path 1 上发送 F 的副本2 │
│ │
│ 接收端: │
│ 先到的副本被接受,后到的丢弃 │
│ │
│ 结果:延迟 = min(Path0延迟, Path1延迟)│
└─────────────────────────────────────┘
适用场景:对可靠性和延迟都极度敏感的场景
- 金融交易(毫秒级延迟差异=钱)
- 远程手术(不能有任何中断)
- 关键控制信号
优点:
- 最低延迟(等于最快路径的延迟)
- 最高可靠性(一条路径完全断了也不影响)
缺点:
- 带宽浪费(所有数据发两份)
- 总带宽 = min(路径带宽),不能聚合
- 两条路径的流量都在增加
// TQUIC 中 Redundant 调度器(简化)
impl MultipathScheduler for RedundantScheduler {
fn select_paths(&self, paths: &PathMap) -> Vec<PathId> {
// 选择所有可用路径——数据会在每条路径上各发一份
paths.iter()
.filter(|p| p.is_active() && p.can_send())
.map(|p| p.id())
.collect()
}
}
策略三:RoundRobin(轮询)
逻辑:按顺序轮流在各路径上发送
┌─────────────────────────────────────┐
│ RoundRobin Scheduler │
│ │
│ 输入:连续的数据帧 F1, F2, F3, F4 │
│ │
│ 决策过程: │
│ F1 → Path 0 │
│ F2 → Path 1 │
│ F3 → Path 0 │
│ F4 → Path 1 │
│ ... │
└─────────────────────────────────────┘
适用场景:两条路径质量相近时,追求带宽聚合
优点:两条路径的带宽都被利用
缺点:
- 如果路径质量差异大,会引入严重的重排序
(快路径的包先到,慢路径的后到 → 应用层乱序)
- 需要在接收端做重排序缓冲
三种策略对比
延迟 带宽利用 可靠性 带宽浪费
MinRtt 最优 中等 中等 无
Redundant 最优 最差(不聚合) 最优 2倍
RoundRobin 中等 最优(聚合) 中等 无
选择指南:
┌─────────────────────────────────────────┐
│ 你最在意什么? │
│ │
│ 延迟最低 + 省带宽 ──→ MinRtt │
│ 绝对不能断 ─────────→ Redundant │
│ 下载速度最快 ───────→ RoundRobin │
│ 不确定 ─────────────→ MinRtt (默认推荐) │
└─────────────────────────────────────────┘
多路径 QUIC 的核心挑战
多路径听起来很美好,但实现起来有很多难题:
挑战一:接收端重排序
发送顺序:帧1, 帧2, 帧3, 帧4, 帧5
调度结果:
Path 0 (RTT=20ms): 帧1, 帧3, 帧5
Path 1 (RTT=80ms): 帧2, 帧4
接收端收到顺序:
t=20ms: 帧1 到达 (Path 0)
t=20ms: 帧3 到达 (Path 0)
t=20ms: 帧5 到达 (Path 0)
t=80ms: 帧2 到达 (Path 1) ← 比帧3晚到!
t=80ms: 帧4 到达 (Path 1)
问题:
t=20ms 时,帧1和帧3到了但帧2没到
应用层能看到帧1的数据,但帧2的数据要等到 t=80ms
如果是同一个 Stream 内的数据 → 流内队头阻塞!
解决方案:
1. 接收端维护重排序缓冲区
2. MinRtt 策略尽量避免这个问题(数据主要走快路径)
3. 调度器可以把同一个 Stream 的数据尽量放在同一条路径
挑战二:何时声明丢包?
单路径丢包检测:
包 100 之后的 3 个包(101,102,103)被确认 → 包100丢了
简单明确
多路径丢包检测:
路径 A 上发了包 100(包号是全局的还是每路径的?)
路径 B 上发了包 101-103 并被确认
问题:路径 B 的确认能用来判定路径 A 的包丢失吗?
如果路径 B 的 RTT 是路径 A 的 4 倍:
路径 B 确认包 101-103 时,路径 A 的包 100 可能还在路上
→ 不能用跨路径的确认来判定丢包!
TQUIC 的做法:
每条路径独立做丢包检测
路径 A 只看路径 A 上发出的包的确认状态
路径 A 丢包 → 只影响路径 A 的拥塞窗口
挑战三:路径质量突变
场景:你走进电梯
WiFi 路径:信号突然从满格变成零
4G 路径:还有微弱信号
需要快速做出决策:
1. WiFi 完全不可用了 → 停止在 WiFi 上发送
2. 把所有流量转移到 4G
3. 出电梯后 WiFi 恢复 → 重新激活 WiFi 路径
关键指标:切换的速度
太慢:WiFi 上积压大量数据,超时重传,用户感觉卡
太快(误判):WiFi 只是短暂波动就放弃,浪费了好路径
TQUIC 通过 PathMap 管理路径状态:
每条路径有 Active / Degraded / Unavailable 状态
基于连续丢包次数和 RTT 飙升来判断路径质量
挑战四:队头阻塞的”回归”
多路径虽然是为了解决问题,但也可能引入新的队头阻塞:
Stream 级别的阻塞(多路径特有):
Stream A 的帧 1-3 被调度到:
帧1 → Path 0 (快, 到达)
帧2 → Path 1 (慢, 未到达) ← 卡在这里
帧3 → Path 0 (快, 到达)
即使帧1和帧3都到了,Stream A 还是要等帧2
因为 Stream 内部必须有序交付
这比单路径更严重:
单路径:帧2丢了需要重传(同路径RTT时间)
多路径:帧2可能只是在慢路径上"还在路上"(慢路径RTT时间)
缓解策略:
1. 同一 Stream 的帧尽量调度到同一路径
2. MinRtt 策略本身就减少了这个问题
3. 接收端设置合理的重排序超时
IETF 多路径 QUIC 标准化进展
draft-ietf-quic-multipath
多路径 QUIC 的标准化工作在 IETF 的 QUIC 工作组进行中:
标准化时间线:
2021: 提出初始草案
2022: 合并多个提案为统一草案
2023: 第 10+ 版草案,核心设计趋于稳定
2024-2025: 持续修订,争取进入 RFC 发布流程
当前状态(2025):仍是 Internet-Draft,尚未成为 RFC
标准草案的核心设计决策
决策1:包号空间
选项A:全局包号空间(所有路径共享)
优点:简单
缺点:跨路径确认复杂
选项B:每路径独立包号空间 ← 草案选择了这个
优点:路径间完全隔离,丢包检测简单
缺点:需要额外机制关联同一连接的不同路径
决策2:路径标识
使用 Path ID(小整数)标识每条路径
不同路径使用不同的 Connection ID
决策3:新帧类型
PATH_ABANDON:放弃一条路径
PATH_STATUS:通告路径偏好(主用/备用/只接收)
ACK_MP:多路径 ACK(指定是哪条路径的确认)
TQUIC 的多路径实现深入
TQUIC 是目前唯一原生实现了多路径 QUIC 的开源项目。让我们看看它的关键数据结构:
PathMap:路径管理
// TQUIC 中的路径管理(简化)
pub struct PathMap {
paths: Vec<Path>,
active_path_id: PathId,
// 每条路径的信息
}
pub struct Path {
id: PathId,
local_addr: SocketAddr,
peer_addr: SocketAddr,
// 连接 ID
dcid: ConnectionId,
scid: ConnectionId,
// 路径状态
state: PathState,
// 独立的拥塞控制
congestion_controller: Box<dyn CongestionController>,
// 独立的 RTT 估算
rtt: RttEstimator,
// 独立的丢包恢复
recovery: Recovery,
// 路径验证状态
validated: bool,
challenge_data: Option<[u8; 8]>,
}
pub enum PathState {
Active, // 正常使用
Degraded, // 质量下降(高丢包/高RTT)
Standby, // 备用(不主动发数据)
Closed, // 已关闭
}
MultipathScheduler trait:调度器接口
// TQUIC 的调度器 trait(简化)
pub trait MultipathScheduler: Send + Sync {
/// 选择要发送数据的路径
fn on_select_path(
&mut self,
paths: &PathMap,
frame: &Frame,
) -> Option<PathId>;
/// 路径状态变化时的回调
fn on_path_updated(
&mut self,
path_id: PathId,
event: PathEvent,
);
/// 获取调度器名称(用于日志)
fn name(&self) -> &str;
}
// 使用示例:注册自定义调度器
let mut config = Config::new()?;
config.set_multipath_scheduler(Box::new(MyCustomScheduler::new()));
这个 trait 设计是 TQUIC 的核心扩展点——犀牛鸟参赛者可以实现自定义的调度策略。
每路径 Recovery 设计
// TQUIC 中每路径独立的丢包恢复(简化)
impl Path {
pub fn on_ack_received(&mut self, ack: &AckFrame) {
// 1. 只处理本路径上发出的包的确认
let newly_acked = self.recovery.on_ack(ack);
// 2. 更新本路径的 RTT
if let Some(rtt_sample) = newly_acked.rtt_sample {
self.rtt.update(rtt_sample);
}
// 3. 通知本路径的拥塞控制器
self.congestion_controller.on_ack(
newly_acked.bytes,
self.rtt.smoothed(),
);
// 4. 检测本路径的丢包
let lost_packets = self.recovery.detect_loss(self.rtt.smoothed());
if !lost_packets.is_empty() {
self.congestion_controller.on_loss();
}
}
}
多路径的实际效果:性能数据
带宽聚合效果
测试环境:
Path 0: 50Mbps, 20ms RTT, 0% 丢包
Path 1: 30Mbps, 60ms RTT, 0% 丢包
理论最大聚合带宽:80Mbps
实际测试结果(大文件下载):
RoundRobin: ~72Mbps (90% 效率)
MinRtt: ~55Mbps (69% 效率,大部分走 Path 0)
Redundant: ~45Mbps (56%,受限于去重)
单路径: ~50Mbps (只用 Path 0)
为什么 RoundRobin 达不到 100%?
→ Path 1 的高 RTT 导致重排序,接收端等待时间增加
→ 两路径 RTT 差异越大,聚合效率越低
切换可靠性效果
测试场景:Path 0 在 t=5s 时突然断开
单路径 QUIC(连接迁移):
t=5.0s: 检测到路径不可用
t=5.0s-5.3s: 路径验证(PATH_CHALLENGE/RESPONSE)
t=5.3s: 迁移完成,恢复传输
中断时间: ~300ms
多路径 QUIC (MinRtt):
t=5.0s: Path 0 不可用
t=5.0s: Path 1 已经在工作,立即承担所有流量
中断时间: ~0ms(但带宽下降)
优势:对用户完全无感知的切换
多路径 QUIC 与 MPTCP 的对比
在 QUIC 之前,TCP 层面也有多路径方案:Multipath TCP (MPTCP, RFC 8684)。
┌──────────────────┬────────────────────┬───────────────────────┐
│ │ MPTCP │ Multipath QUIC │
├──────────────────┼────────────────────┼───────────────────────┤
│ 标准化 │ RFC 8684 (2020) │ Draft (进行中) │
│ 部署情况 │ iOS/macOS 默认启用 │ TQUIC 等少数实现 │
│ 中间设备兼容性 │ 差(TCP头被篡改) │ 好(QUIC加密保护) │
│ 加密保护 │ 无(TCP明文头部) │ 有(QUIC全程加密) │
│ 每路径独立CC │ 是 │ 是 │
│ 实现位置 │ 内核态 │ 用户态 │
│ 升级部署 │ 需改OS内核 │ 改应用即可 │
│ 已知问题 │ 中间设备干扰 │ 标准化未完成 │
└──────────────────┴────────────────────┴───────────────────────┘
MPTCP 的最大问题是中间设备兼容性:
MPTCP 部署障碍:
1. 很多防火墙看到不认识的 TCP Option 就丢包
2. NAT 设备可能修改 TCP 头部,破坏 MPTCP 签名
3. 某些运营商的透明代理会重写 TCP 头部
结果:即使两端都支持 MPTCP,中间路径不一定允许
苹果的实践:iOS 用 MPTCP 时,如果检测到不兼容就回退到普通 TCP
Multipath QUIC 没有这个问题:
QUIC 头部加密 → 中间设备无法读取或篡改
对中间设备来说,QUIC 就是普通的 UDP 包
只要 UDP 能通,多路径就能工作
犀牛鸟可以贡献什么?
多路径 QUIC 是 TQUIC 的核心方向,也是犀牛鸟参赛的主要切入点:
贡献方向一:新的调度策略
现有策略的局限:
MinRtt: 不利用慢路径的带宽
RoundRobin: 不考虑路径质量差异
Redundant: 浪费带宽
可以研究的新策略:
1. 加权调度(Weighted Scheduler):
按路径带宽比例分配数据
WiFi 50Mbps, 4G 30Mbps → WiFi分配 62.5%, 4G 分配 37.5%
2. 自适应调度(Adaptive Scheduler):
实时监测路径质量,动态调整策略
正常时用 MinRtt → 检测到快路径拥塞 → 自动切到加权
3. 流感知调度(Stream-aware Scheduler):
实时流(视频) → MinRtt(低延迟路径)
大文件流(下载) → RoundRobin(聚合带宽)
控制流(信令) → Redundant(保证可靠)
多路径 QUIC 的技术挑战
挑战一:乱序重组
问题:两条路径延迟不同,数据到达顺序可能乱
发送顺序:Packet 1(WiFi), Packet 2(4G), Packet 3(WiFi), Packet 4(4G)
到达顺序(WiFi快,4G慢):
Packet 1 (WiFi, 20ms后到达)
Packet 3 (WiFi, 25ms后到达)
Packet 2 (4G, 80ms后到达) ← 比 Packet 3 还晚!
Packet 4 (4G, 85ms后到达)
接收端需要:
1. 足够大的重排序缓冲区
2. 不要把"延迟到达"误判为"丢包"
3. 正确地向应用层按序交付
TQUIC 的解决方案:
- 每路径独立的包号空间(避免全局乱序)
- 路径感知的丢包检测阈值
- 自适应的重排序窗口
挑战二:路径之间的耦合
问题:一条路径的拥塞不应该影响另一条
场景:
WiFi: 正常,CWND = 100KB
4G: 突然丢包严重
错误做法:整个连接的 CWND 减半
→ WiFi 也被降速了!
正确做法(TQUIC):
每条路径独立的拥塞控制器
4G 路径减速,WiFi 路径不受影响
但也不能完全独立——调度器需要知道各路径状态
→ 调度器和拥塞控制器之间需要信息共享
挑战三:缓冲区膨胀(Buffer Bloat)
问题:快路径的数据先到,慢路径的数据还在路上
接收端需要缓存快路径的数据等慢路径
示意:
WiFi数据: [1][3][5][7][9] ← 很快到达
4G数据: [2][4][6][8][10] ← 慢慢到达
接收端缓冲区:
时刻1: [1] 可交付, [3] 缓存等待 [2]
时刻2: [1] 可交付, [3][5] 缓存等待 [2]
时刻3: [1] 可交付, [3][5][7] 缓存等待 [2]
...缓冲区越来越大!
解决思路:
1. 调度器尽量让两条路径的数据"同时到达"
→ MinRtt 策略考虑路径延迟差异
2. 接收端流控限制缓冲区大小
3. 极端情况下放弃慢路径
挑战四:队头阻塞的回归
讽刺的问题:多路径可能重新引入队头阻塞
单路径 QUIC:
Stream A 和 Stream B 互不影响 ← QUIC 的核心优势
多路径 QUIC(如果调度不当):
Stream A 的数据分散在两条路径上
快路径的帧先到,但要等慢路径的帧
→ 流内队头阻塞又回来了!
解决思路:
1. 尽量让同一个流的数据走同一条路径
2. 或者接受一定的乱序,用更大的缓冲区
3. 对延迟敏感的流只走最快路径(流感知调度)
IETF 多路径 QUIC 标准化进展
draft-ietf-quic-multipath 核心设计
IETF 多路径 QUIC 草案的关键帧类型:
PATH_NEW Frame:
- 通知对端"我要使用一条新路径了"
- 包含路径标识符
PATH_ABANDON Frame:
- 通知对端"我不再使用这条路径了"
- 触发路径资源释放
ACK_MP Frame:
- 多路径 ACK,指明确认的是哪条路径的包
- 每条路径有独立的包号空间
关键设计决策:
- 每条路径独立的包号空间(vs 共享包号空间)
- 路径标识用 Connection ID 映射
- 拥塞控制 per-path
- 调度策略不在标准中规定(留给实现)
标准化时间线
2017: 最早的多路径 QUIC 研究论文
2021: draft-ietf-quic-multipath-00 发布
2022-2024: 活跃讨论和修改(已到 draft-10+)
2025: 接近完成,但仍有争议点
预期: 2025-2026 可能成为 RFC
争议点:
1. 是否允许单包跨路径(当前:不允许)
2. 路径 ID 的分配方式
3. 与现有单路径实现的向后兼容性
多路径在实际产品中的应用
腾讯的实际案例
微信视频通话:
- WiFi + 4G 同时使用
- WiFi 正常时优先走 WiFi(低延迟)
- WiFi 卡顿时无缝切换到 4G
- 用户完全无感知
QQ 视频:
- 类似策略
- 额外的"冗余模式":重要帧在两条路径上都发一份
- 牺牲带宽换取可靠性
腾讯云 CDN:
- 服务端多路径加速
- 聚合多条路径的带宽
Apple 的 MPTCP 经验(作为参考)
iOS 从 iOS 7 开始支持 Multipath TCP:
- Siri 使用 MPTCP(WiFi + Cellular)
- Apple Music 流媒体
- Apple Maps 导航
经验教训(QUIC 多路径可以借鉴):
1. 电量消耗是关键问题(两个无线接口同时开启)
2. 用户可能不愿意同时用WiFi和流量(流量费用)
3. 需要智能的路径选择:不是总是两条都用
4. 策略应该是"按需使用第二条路径"
代码视角:实现一个简单的调度器
如果你要为 TQUIC 贡献一个新的调度策略,框架大概是这样:
// 定义你的调度器
pub struct WeightedScheduler {
weights: HashMap<PathId, f64>, // 每条路径的权重
}
impl MultipathScheduler for WeightedScheduler {
fn on_path_updated(&mut self, path_id: PathId, stats: &PathStats) {
// 根据路径带宽更新权重
let total_bw: f64 = self.paths.values()
.map(|p| p.bandwidth)
.sum();
self.weights.insert(
path_id,
stats.bandwidth / total_bw, // ← 按带宽比例分配
);
}
fn select_path(&self, paths: &[PathId]) -> Option<PathId> {
// 选择"欠债最多"的路径(保证长期比例正确)
paths.iter()
.max_by(|a, b| {
let debt_a = self.weights[a] - self.actual_ratio[a];
let debt_b = self.weights[b] - self.actual_ratio[b];
debt_a.partial_cmp(&debt_b).unwrap()
})
.copied()
}
}
这个 Weighted Scheduler 的思路:
- 根据每条路径的带宽计算”应该分配的比例”
- 跟踪”实际分配的比例”
- 每次选择”最欠债”的路径(实际比例低于目标比例的)
本章小结
| 概念 | 关键点 |
|---|---|
| 多路径动机 | 带宽聚合 + 无缝切换 + 冗余可靠 |
| TQUIC PathMap | 每条路径独立管理,含独立拥塞控制 |
| MinRtt 策略 | 选延迟最小的路径,适合实时应用 |
| Redundant 策略 | 所有路径都发一份,适合关键数据 |
| RoundRobin 策略 | 轮流发送,适合带宽聚合 |
| 核心挑战 | 乱序重组、路径耦合、缓冲区膨胀、HOL回归 |
| IETF 进展 | draft-ietf-quic-multipath,接近完成 |
| 唯一实现 | TQUIC 是六大项目中唯一原生支持多路径的 |
一句话总结:多路径 QUIC 让一个连接同时使用多条网络路径,核心难点在于”如何聪明地调度数据到不同路径”。TQUIC 的 MultipathScheduler trait 提供了可插拔的调度框架,是犀牛鸟贡献的核心方向。
读完本章你能做什么
- 解释多路径 QUIC 的三个核心价值(带宽聚合、无缝切换、冗余)
- 说出 TQUIC 的三种调度策略及各自适用场景
- 描述 MultipathScheduler trait 的核心接口
- 列出多路径 QUIC 的四个技术挑战
- 设计一个简单的加权调度策略的伪代码
- 解释为什么 TQUIC 需要每路径独立的拥塞控制
常见误区
误区一:多路径就是”同时用两条路,速度翻倍”
正确理解:带宽聚合的实际效果通常远低于理论值。原因包括:1. 两条路径延迟不同导致乱序和重传;2. 调度算法的开销;3. 缓冲区限制。实际中 WiFi(50Mbps) + 4G(30Mbps) 可能只能达到 60-70Mbps 的聚合效果,而不是 80Mbps。
误区二:所有 QUIC 实现都支持多路径
正确理解:在我们研究的六大实现中,只有 TQUIC 原生支持多路径 QUIC。其他实现目前都只支持单路径(虽然支持连接迁移——但那是”从一条路径切换到另一条”,不是”同时使用多条”)。
误区三:多路径 QUIC 和连接迁移是一回事
正确理解:连接迁移是”放弃旧路径,切换到新路径”——任何时刻只用一条路径。多路径是”同时使用多条路径”——所有路径并行传输数据。连接迁移是 QUIC v1(RFC 9000)的标准功能,多路径是尚在标准化中的扩展。
导读目录:README.md 上一章:第 5 章 · 拥塞控制——在黑暗中找到最佳车速 下一章:第 7 章 · 社区标杆:quinn 与 quiche——极简 vs 全能