CDN系统为了降低回源延迟,往往会在不同运营商或不同物理机房之间部署多个边缘节点,这些节点之间需要通过网络互相拉取缓存内容。传统TCP只能基于单一四元组建立一条端到端连接,一旦这条路径发生拥塞或者链路抖动,整个传输就会受限。Multi-path TCP(MPTCP)作为TCP的一个扩展协议,允许在同一个连接内创建多个子流(subflow),每个子流可以走不同的网络路径,从而把多条链路的带宽聚合起来,也提升了容错能力。

MPTCP协议基础与子流建立机制
MPTCP在TCP选项字段中增加了MP_CAPABLE、MP_JOIN等控制信息。当CDN节点A向节点B发起连接时,首包携带MP_CAPABLE选项声明自身支持多路径能力。如果双方内核都启用了MPTCP,则初始子流建立完成后,任意一端都可以通过发送MP_JOIN报文,基于已验证的令牌(token)在另一条IP路径上建立新的子流。子流在逻辑上属于同一个映射连接,但各自拥有独立的序列号和确认机制。
从实现角度看,Linux内核从4.0版本开始合入了MPTCP基础框架,到5.6之后提供了较为完整的用户态配置接口。在CDN场景中,边缘节点通常具备双网卡或绑定了多个出口IP,利用ip mptcp endpoint add命令可以将这些地址注册为可用子流端点。内核的路径管理器(path manager)会根据策略决定是否主动建链。例如下列命令把节点上的 secondary IP 加入MPTCP端点:
# 查看当前mptcp端点 ip mptcp endpoint show # 添加子流端点,dev为出口网卡,subflow表示允许作为子流 ip mptcp endpoint add 192.168.10.20 dev eth1 subflow # 设置路径管理器为fullmesh,自动在端点间建立全互联子流 sysctl -w net.mptcp.mptcp_path_manager=fullmesh
上述配置完成后,应用层无需修改socket代码,只要使用普通TCP监听与连接,内核就会在底层透明地尝试建立多条子流。不过要注意,中间网络设备如老式防火墙可能会丢弃带有未知TCP选项的包,因此CDN节点间最好走受信任的专线或VXLAN隧道,避免公网透明设备干扰。
CDN节点间部署MPTCP的调度与拥塞控制
建立好多条子流后,下一个核心问题是数据如何在子流之间分配。MPTCP使用数据序列号映射(Data Sequence Number)将应用写入的数据切分到不同子流,接收端再按DSN重组。默认的调度器是冗余最低轮询(minRTT),即优先把数据发给当前拥塞窗口除以RTT得到的发送速率最高的子流。在CDN回源这种大文件传输场景里,这种调度能充分利用低延迟专线,同时把闲时带宽利用起来。
拥塞控制方面,MPTCP要求每个子流运行独立的拥塞算法,但总体窗口受耦合控制器限制,防止多子流合计抢占超过等价单流应得的带宽。Linux默认使用coupled耦合算法,也可切换为backup模式让某条子流仅作容灾。下面是一段用于观察子流状态的命令,运维人员可借此判断某条子流是否长期处于丢包状态:
# 查看某个连接的mptcp子流信息 ss -tinm 'dst 192.168.20.30' # 输出示例中包含子流token、拥塞窗口cwnd、RTT等字段 # 若某子流retrans过高,可考虑将其标记为backup ip mptcp endpoint change 192.168.10.20 backup
在实际CDN调度中,我们还会遇到子流乱序导致的伪重传问题。由于不同路径时延差异大,后发的子流可能先到,接收端若按子流确认可能误判丢包。内核通过DSN映射重组后再确认,能缓解该问题,但应用层若使用TCP_NODELAY强行推送小包,会加剧乱序。因此建议CDN节点间传输开启适当的Nagle延迟,或采用块传输模式。
性能对比与运维避坑实践
我们在两个跨机房CDN节点之间做过对比:单TCP连接在使用1Gbps专线加1Gbps公网VPN时,因为公网抖动,平均吞吐只有600Mbps;启用MPTCP并配置fullmesh双子流后,稳定吞吐达到1.7Gbps,接近两条链路理论之和。重传率从2.3%降到0.4%,说明多路径确实分散了单点拥塞风险。下面的Python片段展示了如何用socket选项探测对端是否支持MPTCP,便于在调度系统中做能力判断:
import socket
def supports_mptcp(host, port):
try:
s = socket.create_connection((host, port), timeout=3)
# 在Linux上可通过getsockopt读取MPTCP信息
# 此处仅为示例,实际需调用MPTCP-specific接口
opt = s.getsockopt(socket.SOL_TCP, 0x100)
s.close()
return bool(opt & 0x1)
except Exception as e:
print("probe failed")
return False
print(supports_mptcp("192.168.20.30", 8080))
运维落地时常见误区是以为开了内核参数就万事大吉。实际上如果CDN节点的容器网络使用了NAT或者iptables做MASQUERADE,MPTCP的MP_JOIN报文里的地址会被改写,导致对端无法回建子流。此时需要在网关节点保留原始源IP,或使用ip mptcp endpoint显式宣告内网地址。另外,部分负载均衡器以五元组做会话保持,会把子流误判为新连接,需在LB上关闭严格五元组哈希,改为基于MPTCP token的识别。
另一个容易忽略的点是监控。传统监控只看tcp重传,对MPTCP应细分到子流级别。可通过/proc/net/mptcp或者eBPF程序采集每子流cwnd、RTT、retrans,再汇总到时序数据库。只有把每条子流的健康度看清楚,才能在专线劣化时自动将其置为backup,保证CDN节点间传输始终跑在最优路径组合上。
MPTCPCDNTCP_subflow修改时间:2026-08-17 23:20:38