yamux 多路复用把隧道吞吐压到 1/10:一次 frp 性能排查
现象
两个节点之间 RTT 约 70ms,用 frp 做内网穿透,单连接下载吞吐只有 1.5 MB/s 左右,而且越跑越慢。同一链路改用 ssh 反向隧道(同样加密、同样走这条跨境路径)能稳定跑到 14.7 MB/s,差距接近 10 倍。
先排除的三件事
- 带宽。用不可压缩数据测裸 TCP 上行,15.5 MB/s,链路本身没问题。这一步容易踩坑:第一版基线是用
dd if=/dev/zero通过 ssh 测的,拿到 20.8 MB/s 的"好数据",但 ssh 压缩把零流几乎消光了,这个基线是假的。换成/dev/urandom才看到真实值。 - 限速与流控。
tc qdisc是默认的fq,没有额外限速规则;两端 frp 进程 CPU 都很低。 - 并发争抢。用多条并发连接聚合测试,4 条并发能到 8.5 MB/s,说明不是总带宽被限死,而是单条连接被卡住。
定位到 tcpMux
frp 的 transport.tcpMux 默认开启,用 yamux 在一条 TCP 连接上复用多条逻辑流。yamux 给每条流分配独立的流控窗口,默认 256 KB。单连接吞吐上限就是「窗口 ÷ RTT」:
256 KB ÷ 70 ms ≈ 3.6 MB/s
实测只有 1.5 MB/s,比这个理论上限还低,因为多条逻辑流共用同一个窗口,还要等窗口回补的往返。
验证
同一台机、同一条链路、同一个文件、同样 32 MB 的 Range 请求:
| 传输方式 | 吞吐 |
|---|---|
| 裸 TCP(urandom,不可压缩) | 15.5 MB/s |
| ssh 反向隧道 | 14.7 MB/s |
| frp,tcpMux = true | 3.4 ~ 9.4 MB/s,越跑越慢 |
| frp,tcpMux = false | 10.7 ~ 19.8 MB/s |
关掉 mux 后,每条隧道连接独占一个 TCP socket,交给内核的拥塞控制和窗口自动调优,不再受固定流控窗口约束。
改法与两个坑
两侧配置都要改:
# frps.toml 与 frpc.toml
transport.tcpMux = false
顺序不能颠倒。 tcpMux 是协商参数,两侧不一致时 frpc 收到 EOF 会直接退出(loginFailExit 默认 true),不会重试。正确顺序是先改并重启 frps,再启动 frpc。
验证不能只看进程在不在。 不一致的失败模式恰好是「进程活着但隧道不通」,systemctl is-active 和 ps 都看不出来。要实际拉一次数据。
结论
frp 的吞吐瓶颈通常不在带宽,而在 tcpMux 的固定流控窗口。RTT 越高、窗口越小,损失越大。70ms 的跨境链路上,这一项就能造成 3 倍以上的差距。判断是否需要关闭,先算一遍「窗口 ÷ RTT」,再和实际带宽比一比,就知道天花板在哪。
- 点赞
- 收藏
- 关注作者
评论(0)