多路径传输实战:MPTCP与CMT-SCTP机制、实测与调优对比
多路径传输这件事,最早让我真正重视起来的是一个挺尴尬的场景:一台现场设备同时插着有线和无线两张网卡,有线链路带宽高但抖动大,无线链路稳定但速率低,跑单条 TCP 的时候,应用层看到的总吞吐还不如只开无线那一条来得稳。问题不在链路本身,而在于传输层只认一条路。多路径传输协议要解决的就是这个:让一个连接同时骑在多条路径上跑,既能聚合带宽,又能在某条路径劣化时平滑迁移。这篇文章聚焦两条技术路线——MPTCP(MultiPath TCP)与CMT-SCTP(Concurrent Multipath Transfer for SCTP),从机制拆解到环境搭建、从参数计算到实测数据,把我自己踩过的坑和调优心得摊开讲。适合已经懂 TCP 三次握手、看过 SCTP 基本概念,但还没真正跑过一次多路径压测的读者;如果你只是想搞清楚"这两个东西到底选哪个",直接跳到第 1.3 节和最后的对比表也能拿到答案。
1. 为什么要在协议层做多路径:整体设计与选型考量
1.1 单路径的天花板到底卡在哪
绝大多数人第一次遇到多路径需求,都是从带宽聚合开始的,但真跑起来会发现瓶颈往往不在带宽。单条 TCP 连接的实际吞吐受三个东西同时制约:带宽、往返时延、丢包率。带宽时延积决定了管道里最多能塞多少未被确认的数据,一旦接收窗口或拥塞窗口填不满这条管道,链路带宽就是浪费;而丢包一旦发生,拥塞控制立刻把窗口砍半,恢复速度取决于 RTT,RTT 越长恢复越慢。在移动场景下这三者还会互相耦合——信号切换时 RTT 突然从 20ms 跳到 200ms,窗口跟不上,应用层就是一次明显的卡顿。
应用层的应对办法通常是"多开几个连接"或者"多网卡负载均衡"。前者治标不治本,因为多条 TCP 各自独立做拥塞控制,在网络已经拥塞时反而加剧排队;后者更粗糙,负载均衡是流级别的,一个长连接永远只走一条路,链路利用率完全看运气。真正能同时拿到聚合收益和故障冗余的,只有在传输层把多条路径绑成一条逻辑连接,也就是多路径传输。这也是我坚持在协议层做这件事的唯一理由:路径的探测、调度、故障迁移必须由掌握拥塞状态的实体来做,应用层拿不到这些信息。
1.2 两条路线各自的基因:一个长在传输层,一个长在信令层
MPTCP 和 CMT-SCTP 看着都是"多路径 + 多归属",血缘其实完全不同。MPTCP 是给 TCP 打的补丁,思路非常克制:上层仍然是一个标准的 socket 接口,应用几乎无感;底层拆成一条主连接(master subflow)加若干子流(subflow),每条子流是货真价实的 TCP 连接,有独立的序列号空间,靠一个连接级序列号(Data Sequence Signal,DSS)把数据重新拼起来。它把多路径能力做成了一个 TCP 选项的扩展集,所以能穿过大部分中间设备,部署阻力小。
CMT-SCTP 的底子是 SCTP。SCTP 天生就是多宿主的——一个偶联(association)可以绑定多个 IP 地址,对端在这些地址之间自动做路径管理和故障切换,这是 SCTP 当年为信令传输设计的核心能力。CMT 做的事情是在这个基础上把"多路径仅做冗余"改成"多路径并发使用"。也就是说,SCTP 的多路径原本是主备模式,CMT 把它改造成并行发送。这个基因决定了 CMT-SCTP 在语义上更"正统",它天然具备多流(multi-streaming)能力,能避免跨流队头阻塞,但也决定了它对中间设备的依赖更重——很多网络设备压根不认识 SCTP 协议号 132。
我的判断是:这两者的差异不是"谁更快",而是"你要在哪一层付出代价"。MPTCP 的代价在协议栈实现复杂度和内核版本依赖上,收益是无感部署;CMT-SCTP 的代价在网络可达性和内核改造上,收益是协议层面的完整性和可预测性。
1.3 选型时真正该问的四个问题
我见过太多团队上来就比吞吐数字,结果选完发现根本跑不起来。选型前先把这四个问题回答清楚,比任何 benchmark 都有用。
| 关键问题 | MPTCP 的答案 | CMT-SCTP 的答案 |
|---|---|---|
| 中间设备能不能放过这个协议 | 基于 TCP 选项,绝大多数 NAT、防火墙、代理可透传 | 独立协议号 132,大量设备直接丢弃或误判 |
| 现网内核支不支持 | Linux 5.6 之后进主线,开关即用 | 主线内核对 CMT 支持不完整,通常需定制补丁 |
| 是否需要多流隔离语义 | 无原生多流概念,需应用层自己拆 | 原生多流,流间独立送达,互不阻塞 |
| 故障切换的粒度 | 子流级别,可配优先级与备份模式 | 路径级别,内置主备与重传策略 |
注意:把"中间设备能否透传"放在第一位,是因为这一条一旦不成立,后面所有优化都白做。我在一个企业内网环境里试过 SCTP 直连,两端机器完全正常,中间过了一台老防火墙就直接建连失败,抓包只能看到 INIT 发出去没有任何回应。
2. 核心机制拆解:MPTCP 与 CMT-SCTP 到底怎么跑起来
2.1 MPTCP 的子流建立全过程
MPTCP 的连接建立分两段,理解这两段是排障的基础。第一段是主连接建立:客户端发 SYN 时带上 MP_CAPABLE 选项,里面包含版本号、标志位和一个 64 位的密钥;服务端在 SYN/ACK 里回自己的 MP_CAPABLE 和密钥。双方各自用"自己的密钥 + 对端密钥"做哈希得到连接级 token,这个 token 是后续加子流的凭证。这一段如果被中间设备剥掉了选项,连接会静默降级成普通 TCP——注意是静默降级,不报错,这是最常见的"以为跑起来了其实没跑"的原因。
第二段是加子流。客户端用本地另一个地址发起一条全新的 TCP 连接,握手包里带 MP_JOIN,里面含对端 token、自己的随机数和一个基于密钥计算的 HMAC。服务端校验 HMAC 通过后,这条新连接被挂到已有连接上,成为一条子流。地址的增删靠 ADD_ADDR / REMOVE_ADDR 选项通告,子流优先级靠 MP_PRIO 调整,出问题时靠 MP_FAIL 回退,连接快速拆除靠 MP_FASTCLOSE。数据在子流上传输时,每一段数据带一个连接级的 DSS 编号,接收端按 DSS 重排后交给应用。这个"两层序列号"的设计是 MPTCP 全部复杂度的根源——它必须同时处理子流级丢包和连接级乱序。
内核里这套逻辑由路径管理器(path manager)和调度器(scheduler)驱动。Linux 上常见的路径管理器策略有两种:fullmesh 是两端所有地址两两建子流,ndiffports 是在同一对地址上开 N 条子流(多用于单网卡但想借多端口绕开某些限制的场景)。调度器在 5.19 之后有可配置项,默认调度器倾向于把新数据放到"最快可用"的子流上,redundant 调度器则是全路径重复发送,用带宽换可靠性。这两个策略的取舍非常极端,后面在实测数据里能看到差别。
2.2 CMT-SCTP 的偶联与多宿主绑定
CMT-SCTP 的建立过程是 SCTP 那套四步握手(INIT → INIT-ACK → COOKIE-ECHO → COOKIE-ACK),区别在于 INIT 和 INIT-ACK 里会携带地址列表。客户端在 INIT 里列出自己所有的 IP,服务端在 INIT-ACK 里回自己的地址列表,双方据此知道对方有哪些可用的地址对。协议规定其中一组地址对被选为"主路径",其余为备用路径;CMT 改造的点就在于让这些"备用路径"也能承载数据,而不是干等着主路径失效。
CMT 真正麻烦的地方是它把 SCTP 原本为"主备切换"设计的机制硬生生改成"并发",于是引出了几个必须在实现里补掉的问题。首先是接收端缓冲阻塞:SCTP 的接收窗口是按偶联统一算的,如果两条路径速率差异大,快路径的数据不断到达但慢路径有个空洞没补上,接收缓冲会被快路径的数据塞满而无法向上交付,整个偶联吞吐被拖到慢路径的水平。这个问题在多路径文献里被反复讨论,缓解手段包括按路径独立告警、调整重排阈值、动态限制快路径的发送量。其次是拥塞控制耦合:如果每条路径各自独立做拥塞控制(uncoupled CMT),总发送速率会远超网络公平份额,在共享瓶颈链路上极具攻击性;于是有了耦合方案,典型的是 CMT/RPW(基于资源池的耦合)和 CMT/PF(基于势函数的耦合)。耦合的本质是让所有路径共享一个"虚拟拥塞窗口",按各路径的 RTT 和丢包情况分配速率。
第三个是乱序与重排。SCTP 本身靠 TSN(Transmission Sequence Number)做可靠有序交付,多路径必然产生大范围乱序,接收端必须维护一个重排队列。重排队列的大小、等待时间直接决定了吞吐上限,配小了就频繁触发快速重传(把正常乱序误判成丢包),配大了就内存暴涨且延迟升高。这个取舍和 MPTCP 的接收缓冲问题本质相同,只是 SCTP 的实现里参数更"硬",调起来没那么灵活。
2.3 调度与拥塞控制:性能差距的真正来源
把协议握手都抛开,多路径方案的性能差异 90% 集中在两个模块:调度器和拥塞控制。调度器决定"这段数据走哪条路",拥塞控制决定"每条路能发多快",两者的交互方式决定了聚合效率。
MPTCP 的调度器在实现上必须处理一个现实问题:子流之间 RTT 不同,如果无脑往快路径堆数据,慢路径的子流会长期空闲但拥塞窗口还在被探测,形成"假活跃"。默认调度器一般用"最小 RTT 优先 + 一定比例的轮转"来平衡,但在 RTT 差异超过 3 倍的场景下,慢路径的贡献会急剧下降。我在 20ms 对 200ms 的两条链路上实测,慢路径的吞吐贡献不足其理论值的 40%,主要原因不是它发不出去,而是发出去的数据到达太晚,DSS 重排窗口长期等待它,反过来压制了快路径的交付节奏。
CMT-SCTP 的调度策略通常更"均匀",因为它默认所有路径都是可信的,倾向于按各路径的 cwnd 比例分配。这在同构路径下表现很好,聚合效率能跑到理论值的 85% 以上;但在异构路径下问题反而更大,因为均匀分配意味着慢路径会拿到与其能力不匹配的数据量,队头阻塞更严重。文献里对 CMT 的主要批评也集中在这里:缺少像 MPTCP 那样成熟的、对 RTT 差异敏感的调度启发式。
提示:评估任何多路径方案,别只看总吞吐。至少同时看三个数:聚合效率(实际吞吐 / 各路径单独吞吐之和)、第 99 百分位完成时间、以及慢路径的利用率。前两个看用户体验,第三个看方案是否真的在"用"多路径,而不是名义上开了两条。
2.4 接收端缓冲与乱序:最容易被忽略的成本
这一节我要单独拎出来讲,因为它是多路径部署中最容易被低估的部分。多路径天然产生乱序,乱序数据必须在接收端缓存,缓存大小直接决定了能不能填满管道。粗略的算法是:每条路径各自需要一个 BDP 大小的缓冲(带宽 × RTT),再加上路径间 RTT 差造成的最大偏差量。如果有两条路径,RTT 差是 60ms,快路径带宽 50Mbps,那么仅为了等慢路径数据,就要额外留 50Mbps × 60ms ≈ 375KB。
这个数字听起来不大,但要注意:内核默认的接收缓冲上限往往只有几百 KB,而且是所有连接共享内存配额。一旦多路径连接数上去,缓冲区不足导致的窗口收缩会非常隐蔽——应用层看到的是"吞吐上不去但没报错",抓包看到的是窗口一直在 200KB 左右打转。我在一次压测里排查了两个小时,最后发现就是net.ipv4.tcp_rmem的最大值没调,接收窗口被硬卡住。
SCTP 侧同理,net.sctp.rmem_max和偶联级的接收窗口共同决定上限。而且 SCTP 的接收缓冲是按偶联分配的,一个偶联绑了四条路径就得按四条路径的 BDP 之和来估,估小了直接触发接收窗口阻塞,表现为发送端 cwnd 明明很大但就是发不动。
3. 实操:把两套环境搭起来并跑出可比数据
3.1 测试拓扑与参数设定
为了让两套方案的数据可比,我用同一套拓扑:两台机器各带两个逻辑接口,中间用网络模拟工具注入延迟和丢包,两条路径的带宽、时延、丢包率完全一致,只切换传输协议。路径 A 配 50Mbps / 20ms / 0.1% 丢包,路径 B 配 10Mbps / 80ms / 1% 丢包。这个配置是刻意做成异构的,因为同构路径下两种协议都能跑到理论值,比不出东西;现实中的多路径场景几乎全是异构的。
测试负载用大文件单向传输,跑 120 秒取稳态区间,每次实验重复五轮取中位数。应用层单连接单流,避免多线程掩盖协议层差异。测吞吐的同时记录 CPU 占用,因为多路径的重排和调度是实打实的 CPU 开销,某些场景下这才是真正的瓶颈。
| 参数 | 路径 A | 路径 B |
|---|---|---|
| 带宽 | 50 Mbps | 10 Mbps |
| 单向时延 | 20 ms | 80 ms |
| 丢包率 | 0.1% | 1.0% |
| BDP(单向) | 125 KB | 100 KB |
| MTU | 1500 | 1500 |
3.2 MPTCP 环境落地步骤
内核优先选 5.15 以上的稳定版,5.6 虽然已经进主线但早期版本的路径管理器行为差异较大,容易得出不可复现的结论。检查是否编进内核:
# 确认内核支持 uname -r sysctl net.mptcp.enabled # 若不存在该条目,说明内核未编入 MPTCP打开开关并配置路径管理器与调度器:
sysctl -w net.mptcp.enabled=1 # 路径管理器,fullmesh 适合两端多地址,ndiffports 适合单地址多端口 sysctl -w net.mptcp.pm_type=0 # 部分内核版本提供调度器选择,先看有哪些可用 cat /proc/sys/net/mptcp/scheduler 2>/dev/null || echo "该内核未暴露调度器开关"声明本地可用端点,让协议栈知道哪些地址可以拿去建子流:
# 添加一个子流端点,subflow 表示该地址可用于发起/接受子流 ip mptcp endpoint add 192.0.2.10 dev eth0 subflow ip mptcp endpoint add 198.51.100.10 dev eth1 subflow # 查看当前端点与子流状态 ip mptcp endpoint show ip mptcp monitorip mptcp monitor这个命令强烈建议在压测时一直开着,它会实时打印子流建立、关闭、地址通告事件。我排障时 80% 的信息来自这个输出,比抓包快得多。服务端侧如果希望主动接受子流,端点要加signal标志,否则只会在客户端侧发起。
应用侧不需要改代码,但要确认 socket 没被强制绑定到单一地址。有些框架默认 bind 到 0.0.0.0,这没问题;但如果代码里显式 bind 到某个具体 IP,MPTCP 就只能用那一个地址,子流加不进来。
3.3 CMT-SCTP 环境落地步骤
这一段的现实情况要先说清楚:主流发行版的内核里,SCTP 支持是有的,但CMT 这个改造通常不在主线里,需要自己打补丁重编内核,或者用带 CMT 补丁的内核分支。这是 CMT-SCTP 落地最大的门槛,也是我在选型时把它排在后位的主要原因。
先验证基础 SCTP 能力:
# 加载模块并查看协议支持 modprobe sctp sysctl net.sctp # 用工具验证一次普通偶联能否建立 sctp_test -H 192.0.2.10 -P 5001 -l sctp_test -H 198.51.100.20 -P 5001 -h 192.0.2.10 -p 5001 -s如果基础偶联能通,再考虑 CMT。打完补丁的内核通常会暴露额外的 sysctl 开关,用来控制是否启用并发多路径、耦合模式、重排阈值:
# 具体名称依补丁实现而定,先用 grep 找出来 sysctl -a | grep -i sctp | grep -i -E "cmt|multipath|reorder"缓冲区需要提前放大,避免接收窗口阻塞污染测试结果:
sysctl -w net.sctp.rmem_max=16777216 sysctl -w net.sctp.wmem_max=16777216绑定多地址需要在应用侧显式调用sctp_bindx(),这是 SCTP 和 TCP 最大的编程差异。用现成工具测试时,多数工具支持传多个地址参数,但顺序很关键——第一个绑定的地址会成为主路径。如果测试时发现流量全走一条路,先检查绑定顺序和补丁的并发开关有没有真的打开。
注意:CMT 补丁的行为在不同内核版本、不同实现之间差异极大,网上抄来的 sysctl 名字很可能在你的内核上不存在。我的做法是先
sysctl -a | grep sctp把全部条目列出来,对照补丁文档逐个确认,而不是凭记忆敲命令。
3.4 关键参数计算与缓冲区配置
参数不能拍脑袋,算一遍比调十次有用。先算单路径 BDP:
- 路径 A:50 Mbps × 20 ms = 1,000,000 bit = 125 KB
- 路径 B:10 Mbps × 80 ms = 800,000 bit = 100 KB
- 两路径合计:225 KB
再算乱序容忍量。路径间单向时延差是 60ms,快路径在这 60ms 内能发出 50Mbps × 60ms = 375KB。也就是说,为了让慢路径的数据不至于把快路径卡死,接收端至少要多留 375KB 的重排空间。加上两条路径各自的 BDP,接收缓冲的合理下界是:
225 KB(BDP 之和)+ 375 KB(乱序容忍)≈ 600 KB
工程上我会再乘 1.5 到 2 倍留余量,取 900KB 到 1.2MB。落到配置上就是:
# 单连接可申请的最大接收缓冲,注意单位是字节 sysctl -w net.ipv4.tcp_rmem="4096 262144 2097152" sysctl -w net.ipv4.tcp_wmem="4096 262144 2097152" # 连接数多的时候还要看总内存配额,别把机器吃光 sysctl -w net.core.rmem_max=2097152这里有个反直觉的点:缓冲区不是越大越好。开得太大,慢路径的空洞迟迟不被判定为丢包,重排队列越积越长,应用层看到的是吞吐上去了但延迟也上去了,交互式业务反而体验更差。我一般会同时配一个重排超时,超过这个时间就认为数据丢了直接重传,宁可浪费一点带宽也不让延迟失控。MPTCP 侧对应的是net.mptcp.rto_min之类的参数(不同内核版本命名不同),SCTP 侧是重排阈值相关的 sysctl。
3.5 三组对比数据与解读
跑完五轮取中位数,结果如下。单独跑每条路径的基线也列出来,方便算聚合效率。
| 场景 | 路径 A 单独 | 路径 B 单独 | 理论聚合 | MPTCP 实测 | CMT-SCTP 实测 |
|---|---|---|---|---|---|
| 异构路径(50/10 Mbps) | 42.1 Mbps | 8.4 Mbps | 50.5 Mbps | 41.8 Mbps | 38.6 Mbps |
| 同构路径(30/30 Mbps) | 27.3 Mbps | 27.3 Mbps | 54.6 Mbps | 48.2 Mbps | 50.1 Mbps |
| 路径 B 中途中断 10 秒 | 42.1 Mbps | 中断 | 无 | 未中断,降至 40.9 Mbps | 未中断,降至 35.2 Mbps |
第一组数据说明的问题很直接:异构路径下两者的聚合效率都不高。MPTCP 实测 41.8Mbps,接近路径 A 单独跑的成绩,说明慢路径基本没贡献;CMT-SCTP 更差,因为它的均匀分配策略把不少数据塞给了慢路径,队头阻塞反过来压制了快路径。这与前面机制分析的预测一致。
第二组同构路径是 CMT-SCTP 的主场,50.1Mbps 对 48.2Mbps,聚合效率分别约 91.8% 和 88.3%。差距不大但有稳定的倾向性:MITM 场景下 SCTP 按 cwnd 比例分配的调度更均匀,而 MPTCP 默认调度器会把一部分数据压在最小 RTT 的子流上,长期看形成轻微倾斜。
第三组是故障场景,这部分我认为比吞吐数字更有价值。路径 B 中断后,MPTCP 的连接没有断,吞吐下降到 40.9Mbps,比路径 A 单独跑还略低一点,因为子流被移除、连接级序列号有短暂等待。CMT-SCTP 降到 35.2Mbps,恢复慢了几秒,原因是它的路径失效检测依赖心跳和重传超时,参数保守的话判定就慢。
还有一组不在表里的观测:CPU 占用。MPTCP 在 50Mbps 量级的吞吐下,单核占用约 18%(主要是重排和 DSS 处理);CMT-SCTP 约 12%,因为它没有两层序列号的开销。但 CMT-SCTP 的接收端内存占用明显更高,重排队列峰值比 MPTCP 高出约 40%,这就是"用内存换 CPU"的典型取舍。
4. 常见问题与排查技巧实录
4.1 故障速查表
下面这张表是我这几年积累下来的,按"现象 → 最可能原因 → 验证手段"组织,可以直接拿去当排查清单用。
| 现象 | 最可能原因 | 验证手段 |
|---|---|---|
| MPTCP 建连成功但只有一条子流 | 中间设备剥掉了 MP_CAPABLE 选项,静默降级 | 两端同时抓包,看握手包里是否带 MP_CAPABLE |
| 子流加了但流量不走 | 端点未标 subflow,或对端未开 signal | ip mptcp endpoint show确认标志位 |
| 吞吐等于单路径,聚合无效果 | 调度器倾斜,或接收窗口被卡 | 抓包看窗口值;查 rmem 配置 |
| 延迟忽高忽低 | 重排队列过大,等待慢路径超时 | 降低重排超时,观察 P99 延迟变化 |
| SCTP 偶联建立失败但 TCP 正常 | 中间设备丢弃协议号 132 | 在链路上分段抓包定位丢在哪一跳 |
| SCTP 多路径只走主路径 | CMT 补丁未生效,回退到标准多宿主主备模式 | 查补丁暴露的 sysctl 开关是否开启 |
| 慢路径吞吐远低于理论值 | RTT 差过大导致队头阻塞 | 单独测各路径 RTT,把 RTT 差压到 3 倍以内 |
| 高并发下多路径连接吞吐集体下滑 | 全局内存配额耗尽,缓冲区被挤掉 | 查net.core.rmem_max与连接数乘积 |
| 偶联频繁触发快速重传 | 重排阈值过小,把乱序误判为丢包 | 抓包看重传是否集中在乱序窗口之后 |
4.2 几个踩过的坑
第一个坑是以为打开了就生效。MPTCP 的静默降级非常坑,sysctl net.mptcp.enabled是 1,端点也加了,但中间过了一台设备之后选项被吃掉,两边握手干干净净地退化成了普通 TCP,应用层毫无感知。后来我养成习惯:任何多路径环境上线前,先在两端跑ip mptcp monitor,确认真的看到子流建立事件,没看到就说明有问题,哪怕业务跑得挺好。
第二个坑是盲目加大接收缓冲。有一次为了让长肥管道跑满,把接收缓冲设到了 16MB,结果吞吐没涨多少,P99 延迟直接翻倍。原因就是重排队列变成黑洞,慢路径丢了包也不触发重传,所有数据都在等一个永远不会到的东西。正确做法是缓冲和重排超时一起调,缓冲负责"能装多少",超时负责"等多久放弃",只调一个必然出问题。
第三个坑是在虚拟化环境里测出假数据。一开始我在两台虚机之间跑,结果聚合效率奇高,长期 95% 以上。后来发现虚拟网卡的队列和宿主机的中断合并把这些差异全抹平了,到达时间被"批量投递",乱序几乎不存在,测出来的不是协议能力而是虚拟化环境的特性。换成物理网卡加外部网络模拟之后数据才真实。这个教训很值钱:测传输层性能,先把虚拟化这层干扰排掉。
第四个坑是忽略了 SCTP 的编程接口差异。SCTP 是一对多(one-to-many)风格,一个 socket 可以管多个偶联,这和 TCP 的一对一完全不同。移植代码时如果把sctp_bindx()的地址顺序搞错,主路径就选到了低带宽的那条,后面怎么调调度参数都没用。地址顺序这件事文档里写过,但真正吃到亏才会记住。
4.3 一份可以直接抄的调优清单
最后整理一份我自己的调优顺序,按"投入产出比"从高到低排:
- 先确认多路径真的在用。抓包或者看 monitor 输出,确认子流数量。这一步不做,后面全是玄学。
- 再算 BDP 和乱序容忍量,把接收缓冲设到合理区间,不要拍脑袋。算不出来就先按 2 倍 BDP 之和起步。
- 压 RTT 差异。如果两条路径 RTT 差超过 3 倍,优先考虑把慢路径设成备份模式(MPTCP 用 MP_PRIO 降优先级),而不是硬要它参与聚合。强行聚合的结果往往是快路径被拖慢。
- 调重排超时,让乱序判定和业务延迟要求匹配。交互类业务宁可早重传,大文件传输可以放宽。
- 最后才碰调度器。默认调度器在大多数场景够用,换调度器前先确认前四步都做到位了,否则换完你也说不清是调度器的功劳还是前面配置的功劳。
我个人的体会是,多路径传输的收益从来不是线性的。两条同构路径能拿到接近 100% 的聚合,两条异构路径能拿到 60% 就算不错,三条以上异构路径的边际收益会快速衰减,而接收端内存和 CPU 开销是实打实线性增长的。真要做方案设计,先把"我需要的是带宽聚合还是故障冗余"这个问题回答清楚——如果只是后者,MPTCP 的备份子流模式或者 SCTP 原生多宿主就够了,根本不用把 CMT 那套耦合拥塞控制搬出来,省下的是内核改造和长期维护的成本。至于我自己的项目,最后选的是 MPTCP,理由很简单:现网内核直接支持,排障工具有现成的,异构路径下把慢链路降为备份后表现稳定,这就够了。