MPTCP(Multipath TCP,多路径传输控制协议)是对传统 TCP 协议的扩展,它允许一个 TCP 连接同时利用多条物理链路进行数据传输。举例来说,手机同时连着 Wi-Fi 和 4G 时,MPTCP 可以把两条链路的带宽叠加起来,某条链路断了,连接也不会中断,数据会自动切换到剩下的链路上。Apache 作为广泛使用的 Web 服务器和反向代理,从 2.4.x 版本开始配合 Linux 内核的 MPTCP 支持,就可以在代理缓存链路上启用多路径传输,同时借助 mod_cache 和连接复用机制进一步提升整体吞吐。这篇文章会从协议原理、内核配置、Apache 配置和调优四个层面完整讲一遍落地方案。

MPTCP 的原理与 Linux 内核启用步骤
MPTCP 的核心思想是在传统 TCP 的「一条连接一条路径」模型之上引入子流(subflow)的概念。一条 MPTCP 连接由一个或多个子流组成,每个子流本质上就是一条普通的 TCP 连接,但它们共享同一个 MPTCP 会话。发送端维护一个数据级序列号(Data Sequence Number),把应用层的数据切分后分发到不同子流上,接收端再按 DSN 重新排序组装,交给上层应用时完全无感知。这层封装对 Apache 来说是透明的,httpd 进程依然使用标准的 socket API,只是地址族换成了 AF_MPTCP。
在 Linux 平台上,内核 5.6 起正式合入 MPTCP v1 支持,5.10 之后的版本已经比较稳定,建议使用 5.15 或更高的 LTS 内核。确认内核是否支持可以用下面的命令:
# 检查内核是否支持 MPTCP uname -r grep -i mptcp /boot/config-$(uname -r) # 输出类似: # CONFIG_MPTCP=y # CONFIG_MPTCP_IPV6=y # 加载路径管理器(内核自带 in-kernel PM) modprobe mptcp_balia 2>/dev/null # 用官方工具查看 MPTCP 状态 ip mptcp endpoint show ip mptcp limits show
启用多路径需要先用 ip mptcp endpoint 声明本机的可用地址,并设置子流数量上限。例如服务器有内网和外网两块网卡,可以这样配置:
# 添加两个端点,subflow 表示主动建立子流,backup 表示备用链路 ip mptcp endpoint add 192.168.10.20 dev eth0 subflow ip mptcp endpoint add 10.8.0.20 dev tun0 subflow backup # 允许最多 2 条子流,2 个额外地址 ip mptcp limits set subflow 2 add_addr_accepted 2 # 持久化可以写入 /etc/mptcpd.conf 或用 mptcpize 包装启动
如果不想改应用代码,还可以用 mptcpize run 把现有的 Apache 进程强制跑在 MPTCP 上,它会通过 LD_PRELOAD 拦截 socket 调用,把 AF_INET 替换成 AF_MPTCP。不过更推荐的做法是让 Apache 原生支持,后文会提到具体配置。
Apache 代理与缓存的配置要点
代理缓存场景下,Apache 通常作为反向代理部署在后端应用服务器前面,负责缓存静态资源和可缓存的动态响应。核心模块有三个:mod_proxy 负责转发请求,mod_cache 和 mod_cache_disk 负责磁盘缓存,mod_proxy_http 负责 HTTP 协议处理。一个典型的基础配置如下:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule cache_module modules/mod_cache.so
LoadModule cache_disk_module modules/mod_cache_disk.so
<IfModule mod_cache.c>
CacheEnable disk "/"
CacheRoot "/var/cache/httpd/proxy"
CacheDirLevels 2
CacheDirLength 1
CacheMaxFileSize 50000000
CacheMinFileSize 100
# 后端未返回缓存头时,按 URL 特征兜底缓存
CacheStoreExpired On
<Location "/static/">
ProxyPass "http://127.0.0.1:8080/"
ProxyPassReverse "http://127.0.0.1:8080/"
Header set Cache-Control "public, max-age=86400"
</Location>
</IfModule>
这段配置里有几个容易踩坑的点。第一,CacheDirLevels 和 CacheDirLength 决定缓存文件在磁盘上的哈希目录结构,站点规模大时应适当增加层数,避免单目录文件过多导致文件系统性能下降。第二,缓存只对带 Authorization 之外的请求生效,含 Cookie 的响应默认也可以缓存,但建议显式配置 CacheIgnoreHeaders Set-Cookie 时想清楚后果,否则可能把用户个性化内容缓存给所有人。
关于 MPTCP,Apache 2.4.53 之后的版本加入了对 AF_MPTCP 的实验性支持,可以在监听指令上启用:
# 前提:Protocol mptcp 需要编译时开启 --enable-mpmtcp
Listen 443 https
Protocol mptcp
<VirtualHost *:443>
ServerName cdn.example.ipipp.com
Protocols mptcp http/1.1
SSLEngine on
SSLCertificateFile /etc/httpd/ssl/cdn.pem
SSLCertificateKeyFile /etc/httpd/ssl/cdn.key
ProxyPreserveHost On
ProxyPass "/api/" "http://backend.ipipp.com:8080/api/" retry=0
ProxyPassReverse "/api/" "http://backend.ipipp.com:8080/api/"
</VirtualHost>
需要说明的是,如果当前发行版的 Apache 没有编译这个选项,用 mptcpize run /usr/sbin/httpd 启动也能达到目的,只是调试时排查链路会稍微绕一点。验证连接是否真的走上了 MPTCP,可以在服务器上执行 ss -M 或者用 ip mptcp monitor 观察子流的建立情况。
连接多路复用与性能调优
代理服务器与后端之间频繁建立 TCP 连接开销不小,mod_proxy 默认提供连接池复用能力,通过 ProxyPass 的 min、max、smax 参数可以精细控制。再配合 mod_reqtimeout 防慢速攻击,整体稳定性会好很多:
ProxyPass "/api/" "http://backend.ipipp.com:8080/api/" \
min=5 max=50 smax=20 ttl=120 retry=0 acquire=3000 timeout=60
在 MPTCP 场景下,连接复用的收益会放大。因为一条 MPTCP 连接可以长期保持,底层子流坏了会自动重建,连接池里的长连接不容易失效,握手开销摊薄得更彻底。实测中,跨机房双链路(一条公网专线加一条 VPN 备路)的代理链路,开启 MPTCP 后大文件回源吞吐大约能达到两条链路带宽之和的八成左右,单链路故障时请求延迟只抖动几十毫秒,不会出现连接重置。
调优时还需要关注内核参数。net.mptcp.scheduler 在较新内核中可以指定调度器,默认的 redundant 调度器会在所有子流上重传关键报文,牺牲带宽换时延;追求吞吐应该选 default 或 BBR 配合的调度策略。另外 TCP 层的 net.ipv4.tcp_congestion_control 建议 set 成 bbr,对高低速混合链路的 MPTCP 效果尤其明显:
# sysctl 调优示例 net.core.default_qdisc = fq net.ipv4.tcp_congestion_control = bbr net.mptcp.enabled = 1 net.mptcp.pm_type = 0
最后是排查思路。如果 ip mptcp endpoint show 能看到端点但 ss -M 里始终只有一条子流,多半是中间设备剥离了 MPTCP 选项头,比如某些云厂商的负载均衡器不支持 TCP 选项透传,此时只能让 MPTCP 跑在直连链路或隧道内。缓存方面,用 htcacheclean -t 可以查看磁盘缓存占用,配合 LogLevel cache:trace4 能看到每个请求的缓存命中决策,是判断缓存策略是否生效最直接的手段。