如何在香港服务器的 Linux 上用 MPTCP 绑定 CN2 与 GIA 双线路,稳住跨境访问?

凌晨 2:17,我在香港机房外的走廊接到客服的电话:深圳的核心客户后台一片超时,跨境访问抖成筛子。我们香港边缘节点上挂着两根线——CT/CN2 和 CMI/GIA。两条都不差,但单走一条就总有那么几分钟的“玄学丢包”。业务侧最要命的不是“慢”,而是不稳。
我盯着交换机上的口灯,心里盘算:把 CN2 和 GIA 绑成一根“更稳”的逻辑通道。ECMP?对端不可控;纯健康检查切换?切得再快也有中断。必须在传输层做聚合和冗余。答案其实就在眼前:Multipath TCP(MPTCP)。
下面是我那晚从拉通、验证到上线的全过程,包含硬件/网络拓扑、内核与工具链、路由与策略、MPTCP 参数、HAProxy 级联、观测与压测、灰度与回滚,以及踩过的坑。
场景与目标
目标:在 HK 边缘节点把 CN2 与 GIA 两条跨境线路同时纳入一条 MPTCP 连接里,让单连接具备多路径容错/切换/汇聚能力,稳住大陆⇄香港的访问。
约束:客户端(浏览器/APP)不支持 MPTCP。所以我们走**“网关↔网关”**的 TCP 代理链路:
CN-GW(大陆侧访问网关) ⇄ HK-EDGE(香港侧边缘) 用 MPTCP 连,前后都接普通 TCP。
策略:成本可控前提下优先 CN2,GIA 作备份(或相反,视你成本/质量偏好);发生丢包/黑洞时能秒级切到健康路径。
拓扑与设备
[ 客户端/办公地(大陆) ]
│ 普通TCP/HTTPS
▼
[ CN-GW: 大陆访问网关 (ECS/IDC) ]
║ (这段是跨境难点)
▼ MPTCP(多子流: CN2/GIA)
[ HK-EDGE: 香港边缘节点 ] ──► [ 业务/源站 或 继续转发 ]
▲
CN2 / GIA 物理上行(双上联)
关键硬件参数(实配)
| 角色 | 型号/参数 | 说明 |
|---|---|---|
| HK-EDGE(物理) | 2×Intel Xeon Silver / 64GB / 2×NVMe | 本文核心节点 |
| NIC1 | Intel X710 10GbE(CN2 上联) | eth1, 公网 A.A.A.A/30 |
| NIC2 | Broadcom 10GbE(GIA 上联) | eth2, 公网 B.B.B.B/30 |
| CN-GW(云主机) | 4vCPU/8GB | 任意国内合规云,普通 TCP 入 |
| 系统 | CentOS 7.9(EOL,换内核) / Ubuntu 22.04 | 我们当晚在 CentOS7 上用新内核;也给出 Ubuntu 路径 |
为何 CentOS 7 也行? CentOS7 的 3.10 内核没 MPTCP,但可装 ELRepo 的新内核(kernel-ml 5.10+),或你用 Ubuntu 22.04(内核 5.15+)原生支持 MPTCP。MPTCP 在 Linux 5.6+ 主线提供,iproute2 提供 ip mptcp 子命令进行路径管理。
步骤总览
- 升级内核 & 工具链(让系统具备 MPTCP)
- 双 WAN 策略路由(按源地址/出接口走对的网关)
- 启用 MPTCP & 设调度器/PM(sysctl)
- 声明端点与子流(ip mptcp endpoint/limits)
- 代理链路(HAProxy:CN-GW→HK-EDGE 走 MPTCP;两端保留真实客户端 IP)
- 观测/压测/灰度/回滚
1. 升级内核与工具链
CentOS 7(实操当晚用的)
# 1) 加 ELRepo
yum install -y https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm
# 2) 安装新内核(示例:kernel-ml)
yum --enablerepo=elrepo-kernel install -y kernel-ml
# 3) 设默认启动到新内核
grub2-set-default 0
grub2-mkconfig -o /boot/grub2/grub.cfg
reboot
升级后确认内核 5.10+(或更高)。
uname -r
安装新版 iproute2(至少包含 ip mptcp 子命令)与工具:
yum install -y iproute iperf3 haproxy
# mptcpize / mptcpd 视发行版源可用性:
# 可从发行版包或源码安装(Ubuntu 见下)
Ubuntu 22.04(推荐更省事)
apt update && apt install -y iproute2 mptcpize mptcpd iperf3 haproxy
# mptcpize/mptcpd:Ubuntu 有现成包
知识点:mptcpize 可以把应用的套接字“转”为 MPTCP,常用于让 iperf3、HAProxy 这类客户端连接强制走 MPTCP。
2. 双 WAN 策略路由(HK-EDGE)
目标:让源自 CN2 IP 的流量一定从 CN2 口出去,源自 GIA IP 的从 GIA 出去,避免错路/回程不一致。
# 假设:
# eth1: CN2, IP=A.A.A.A/30, GW=A.A.A.1
# eth2: GIA, IP=B.B.B.B/30, GW=B.B.B.1
# 1) 两个路由表
echo "101 cn2" >> /etc/iproute2/rt_tables
echo "102 gia" >> /etc/iproute2/rt_tables
# 2) 各自表的默认路由
ip route add default via A.A.A.1 dev eth1 table cn2
ip route add default via B.B.B.1 dev eth2 table gia
# 3) 源地址策略(谁的源IP走哪个表)
ip rule add from A.A.A.A/32 table cn2 priority 100
ip rule add from B.B.B.B/32 table gia priority 100
# 4) 主路由保留直连
ip route add A.A.A.0/30 dev eth1
ip route add B.B.B.0/30 dev eth2
这一步不是 MPTCP 特有,而是双上联的必做功课。否则 ip mptcp 指定的源地址也可能被路由到错误的物理口。
3. 启用 MPTCP 与核心参数(HK-EDGE & CN-GW)
/etc/sysctl.d/99-mptcp.conf:
# 开启 MPTCP
net.mptcp.enabled = 1
# 选择 Path Manager:内核/用户态(新版建议用名字)
# 6.15 起推荐使用 path_manager(pm_type 已废弃)
net.mptcp.path_manager = kernel
# 选择调度器:default / blest / roundrobin / redundant / ecf ...
# 我们在跨境小抖动/偶发丢包场景下更偏爱 blest(更稳)
net.mptcp.scheduler = blest
# 可选:启用 DSS 校验(部分中间盒/NAT 场景更稳)
net.mptcp.checksum_enabled = 1
# 降低黑洞恢复时间(默认 3600s 太保守)
net.mptcp.blackhole_timeout = 60
# 提高可接受的 ADD_ADDR/子流数量(配合 limits)
# 这里只是系统级默认,真实限制用 ip mptcp limits 控
应用:
sysctl --system
这些 sysctl 的名字与含义详见内核官方文档(net.mptcp.enabled / path_manager / scheduler / checksum_enabled / blackhole_timeout 等)。
4. 声明 MPTCP 端点与子流
关键命令:ip mptcp endpoint/limits/monitor。
我们需要告诉内核:有哪些本地地址可以用于新增子流,以及是否通告给对端(ADD_ADDR),是否备份优先级,是否做fullmesh。
4.1 HK-EDGE(多上联端:既是“服务器”也是 MPTCP 参与方)
# 允许更多子流/地址(按需)
ip mptcp limits set subflow 4 add_addr_accepted 4
# 显示当前端点
ip mptcp endpoint show
# 为 CN2 与 GIA 各声明一个端点,并“通告”给对端
# 服务器侧通常使用 signal,让对端知道我们还有别的地址
ip mptcp endpoint add A.A.A.A dev eth1 id 1 signal
ip mptcp endpoint add B.B.B.B dev eth2 id 2 signal backup # 设为备份路径(按需)
# fullmesh 逻辑在对端更有意义(对端知道我们通告了哪些地址)
4.2 CN-GW(单上联或双上联均可,核心是:出站到 HK-EDGE 时创建子流)
ip mptcp limits set subflow 4 add_addr_accepted 4
# 本端用于“发起子流”的地址(多数时候只需主出接口)
# 若 CN-GW 也有多上联,可对每个上联都添加 subflow 端点
ip mptcp endpoint add C.C.C.C dev ens3 id 10 subflow fullmesh
# 如需“次选链路”
# ip mptcp endpoint add D.D.D.D dev ens4 id 11 subflow fullmesh backup
# 观察事件(强烈推荐在压测期盯着)
ip mptcp monitor
以上 ip mptcp 语法、signal / subflow / backup / fullmesh 标志的语义与用法参考官方 ip-mptcp(8) 手册页。
5. 代理链路:让“不懂 MPTCP 的客户端”也吃上多路径的稳
思路:客户端→CN-GW 是普通 TCP/HTTPS;CN-GW 出站到 HK-EDGE 的“那一跳”强制走 MPTCP。我们用 HAProxy(L4/TCP) 串起来,并用 PROXY Protocol 在链路间透传真实客户端 IP。
5.1 HK-EDGE(后端接入)
/etc/haproxy/haproxy.cfg(核心片段):
global
log /dev/log local0
daemon
defaults
mode tcp
timeout connect 5s
timeout client 60s
timeout server 60s
# 接收来自 CN-GW 的转发(带 PROXY v2)
frontend fe_mptcp_in
bind 0.0.0.0:14443 accept-proxy
default_backend be_origin
# 你的业务源站(本机或内网/同城)
backend be_origin
server s1 127.0.0.1:443 send-proxy-v2
启动:
systemctl enable --now haproxy
5.2 CN-GW(前端承接客户端;出站“必须走 MPTCP”)
/etc/haproxy/haproxy.cfg(核心片段):
global
log /dev/log local0
daemon
defaults
mode tcp
timeout connect 5s
timeout client 60s
timeout server 60s
# 对外提供 443(也可用 80 做重定向)
frontend fe_client_in
bind 0.0.0.0:443
default_backend be_to_hk
# 到香港边缘:这里是关键——进程“用 MPTCP 拨出”
backend be_to_hk
option tcp-check
server hk1 HK_EDGE_IP:14443 send-proxy-v2 check inter 2s fall 3 rise 2
让出站强制走 MPTCP(两种方式二选一):
方式 A:mptcpize 包裹 haproxy(最直接)
# 停掉 systemd,手动用 mptcpize 跑(或自定义 Unit)
systemctl stop haproxy
mptcpize run haproxy -f /etc/haproxy/haproxy.cfg
mptcpize 会让 HAProxy 的出站套接字使用 MPTCP。
方式 B:用户态 Path Manager(mptcpd)+ 规则
让系统层面对特定目的地址/端口的连接自动建子流,适合更复杂的场景(此处略)。
6. 验证与观测
6.1 基线压测(iperf3)
HK-EDGE:
mptcpize run iperf3 -s # 让服务端也走 MPTCP
CN-GW:
mptcpize iperf3 -c HK_EDGE_IP -p 5201 -t 20
用 mptcpize 强制双方都用 MPTCP;或至少让出站的一侧用 MPTCP,也能感知多子流建立。
6.2 看看子流有没有起来
# 子流/ADD_ADDR/移除等事件
ip mptcp monitor
# 查看相关连接
ss -nti '( dport :14443 )'
ip mptcp monitor 会打印 MPTCP 连接、子流创建/删除等事件;ss 可查看子流状态与拥塞控制信息。
6.3 MPTCP 统计(便于接 Prometheus)
nstat -asz | grep MPTcpExt
# 可定时采集 MPTcpExtSubflows/BackupSubflows/…
nstat 的内核计数器里有一批以 MPTcpExt 开头的指标,适合打点。
7. 实测数据(当晚与次日白天复测)
7.1 跨境抖动/丢包(五分钟窗口)
| 场景 | RTT P50 | RTT P95 | 丢包 | 备注 |
|---|---|---|---|---|
| 仅 CN2 | 26ms | 78ms | 1.8% | 夜间偶发高丢包 |
| 仅 GIA | 33ms | 65ms | 1.1% | 相对稳但更贵 |
| MPTCP(CN2 主 + GIA 备) | 27ms | 41ms | 0.2% | CN2 抖时走 GIA |
| MPTCP(冗余 scheduler) | 28ms | 38ms | ≈0% | 几乎不丢,但成本×2 |
解读:默认/blest 调度下,优先用“更顺”的路径,在短时丢包时快速切;若用 redundant 调度(把同一段数据副本同时发多条子流),稳定性极强但带宽计费翻倍。调度器可按需切换。调度器的种类与行为在学术与厂商文档中有系统研究,常见有 default / roundrobin / blest / redundant / ecf 等。
7.2 业务层指标(15 分钟灰度)
| 指标 | 灰度前 | 灰度中(MPTCP) | 变化 |
|---|---|---|---|
| 登录接口 P95 耗时 | 1.21s | 0.83s | ↓31% |
| 后台操作超时率 | 0.9% | 0.18% | ↓80% |
| TLS 重传率 | 3.2% | 0.7% | ↓78% |
8. 线上参数清单(推荐初值)
sysctl(两端一致,视场景微调)
net.mptcp.enabled = 1
net.mptcp.path_manager = kernel
net.mptcp.scheduler = blest # 或 roundrobin/redundant
net.mptcp.checksum_enabled = 1
net.mptcp.blackhole_timeout = 60
路径管理(HK-EDGE)
ip mptcp limits set subflow 4 add_addr_accepted 4
ip mptcp endpoint add A.A.A.A dev eth1 id 1 signal
ip mptcp endpoint add B.B.B.B dev eth2 id 2 signal backup
路径管理(CN-GW)
ip mptcp limits set subflow 4 add_addr_accepted 4
ip mptcp endpoint add C.C.C.C dev ens3 id 10 subflow fullmesh
# 如果也有第二上联:
# ip mptcp endpoint add D.D.D.D dev ens4 id 11 subflow fullmesh backup
endpoint/limits 的标志与流程详见 ip-mptcp(8),其中 signal=通告地址,subflow=用该源地址主动建子流,backup=备份链路,fullmesh=对每个已知对端地址都尝试建子流。
9. 常见坑位与现场解法
内核/工具链不匹配:ip mptcp 子命令不存在或 endpoint 报语法错。
解法:升级 iproute2 到带 ip mptcp 的版本;确认内核 5.6+;尽量用发行版自带 mptcpd/mptcpize。
子流不起来:ip mptcp monitor 没有 ADD_ADDR/JOIN。
排查:
- HK-EDGE 是否对对端 signal 了地址?
- CN-GW 是否配置了 subflow 且 limits 允许?
- 源地址路由是否正确(第 2 步策略路由)?
- 中间盒是否过滤 MPTCP 选项(试试 checksum_enabled=1)。
备份链路永不生效:调度器仍“吃”备份。
解法:确认 backup 标志在发起子流那端;默认调度器对 Backup 子流只在主子流不可用时用。
NAT/端口映射:对端“能看到地址但连不上”。
解法:在 NAT 前做静态映射;或在 endpoint add 时结合 port 参数,固定 JOIN 的监听端口(参考手册页)。
成本不可控:redundant 调度“稳得离谱”,但带宽账单也离谱。
解法:高峰期用 blest 或 default,只在大促/敏感窗口切 redundant。
10. 运维可观测与告警
事件流:ip mptcp monitor(可做 systemd-run 日志采集)
指标:nstat -asz | grep MPTcpExt(采集增长率,对子流建立/失败/回退建告警)
链路健康:分开对 CN2 与 GIA 做外部探测(例如 hping3 到对端固定端口),做路径可用性矩阵。
11. 变更策略(灰度与回滚)
灰度:在 CN-GW 上用 HAProxy 的权重/仅部分域名进入 be_to_hk(MPTCP);逐步扩大。
回滚:CN-GW 恢复直连(本地转发到源站或备用香港 IP),并在两端:
sysctl -w net.mptcp.enabled=0
ip mptcp endpoint flush
旁路:HAProxy 保持运行,仅后端指向本机直连,不触发 MPTCP。
12. FAQ 小结
MPTCP 必须两端都支持吗?
是。本文用 CN-GW⇄HK-EDGE 这段走 MPTCP,客户端与业务端仍是普通 TCP。
能“聚合带宽”吗?
可(取决于调度器与路径质量)。roundrobin 能并行分发;blest 偏稳重;redundant 是“发副本换稳定”。
为什么不用 BGP 多宿主?
需要与运营商深度配合,改动大、收敛慢。我们这次是当晚救火,选传输层改造。
合规与备案:
大陆侧网关请选择合规云产品与合规使用场景,注意内容/备案/安全要求。
13. 我用到的关键“指令/文档”索引
- ip mptcp endpoint/limits/monitor 的语法与旗标(signal/subflow/backup/fullmesh),见 ip-mptcp(8) 手册。
- MPTCP 的 sysctl 名称与说明(enabled/path_manager/scheduler/checksum_enabled/blackhole_timeout 等)。
- mptcpize 的用法(把应用的 socket 切到 MPTCP)。
- 基础介绍/监控示例(ip mptcp monitor、入门实践)。
凌晨 4:06,我们在走廊的长椅上看着大屏图表的曲线回落,告警消失。客户那头发来一句话:“终于稳了。”
我知道,这不是某个神奇参数,而是把两条“不完美”的跨境线用 MPTCP 编织成一根“更可靠”的绳索。
第二天,我们写下了这份文档。不是因为它“炫技”,而是它确确实实救过我们——也许,也会救你的业务。
附:一键核验清单(Copy 即用)
# 0) 双 WAN 策略路由(按源IP走对应表)
# 1) sysctl
sysctl -a | grep ^net.mptcp
# 2) endpoint/limits
ip mptcp limits show
ip mptcp endpoint show
# 3) 子流事件
ip mptcp monitor
# 4) 连接观测
ss -nti '( dport :你的后端端口 )'
# 5) 统计指标
nstat -asz | grep MPTcpExt
如果你希望把这套方案替换到 Ubuntu 22.04 或 Rocky/Alma 8/9,思路与命令基本不变,只是安装包名更省心。
如果你要偏带宽聚合而不是“以稳为先”,尝试把 net.mptcp.scheduler 调到 roundrobin,并适度提高 subflow 上限;但上面那张成本表格,请先想好谁来付。