上海 9929 优选低延迟路径: GOST MTCP + ECMP 启动预热
zcp19970603:
## 0. 优选后测试结果截图
RTT 延迟稳定不抖动,不掉包,单线程测速也能跑起来。



## 1. 问题背景
部分跨境或跨运营商线路(这里主要说上海 9929 宽带,存在多条 4837 和 9929 混合 ECMP):据说联通将这项业务外包给第三方,公司后端接入了多条 AS4837(普通联通骨干网)和 AS9929(联通精品网)线路。
这种 ECMP(等价多路径)导致 ICMP ping 很稳定,但不同 TCP 连接的 RTT 会落到几个明显不同的档位,比如一部分约 33ms,另一部分约 51ms。每次新建 TCP 连接时,会重新参与 ECMP 哈希,导致不同业务连接分别经历快路或慢路,延迟因此波动。
## 2. 核心思路
***启动 GOST MTCP 阶段,选出一条低延迟的外层 TCP,后续所有业务都复用这条连接***,而不是在运行过程中持续探测或踢慢连接。
```
任意 TCP 客户端 → 前置 GOST :12000 → 单个长期 MTCP session(4 MiB Stream/32 MiB Receive)
→ 启动阶段检测并只保留低延迟外层 TCP → 远端 GOST :11000 → 目标 TCP 服务
```
GOST MTCP 将多个逻辑 TCP 流复用到同一条外层 TCP 上,因此 ECMP 抽路的影响从“每个业务连接抽一次”收敛到“MTCP 外层连接建立时抽一次”。
我之前试过维持多条 MTCP 连接,定期探测 RTT,结合 `ss -K` 命令踢掉慢路,但存在两个问题:一是 `ss -K` 踢掉后会影响该 session 下所有逻辑流;二是踢掉重建的连接又要重新参与 ECMP,有可能落到慢路,导致“抽路-清理”死循环。最终改成只配置一个 MTCP 节点,正常运行只允许一个外层 TCP,只在启动阶段做 ECMP 预热,命中快路后不再干预,运行时不做周期检测、不踢连接也不主动重建。
## 3. MTCP 缓冲区参数
单个流缓冲区太小会限制吞吐,需根据 BDP(带宽延迟积)估算。例如 RTT 大约 33ms,目标带宽约 400Mbps,BDP ≈ 400Mbps × 0.033秒 ≈ 1.65MiB,4 MiB 的流缓冲较为充足:
```yaml
mux.maxFrameSize: 32768 # 32 KiB
mux.maxReceiveBuffer: 33554432 # 32 MiB
mux.maxStreamBuffer: 4194304 # 4 MiB
```
## 4. 远端 GOST 配置
远端只需监听 `:11000`,具体转发目标由前置 GOST 的 `forwarder` 指定。
配置示例 `/root/gost/remote.yaml`:
```yaml
services:
- name: mtcp-server
addr: :11000
handler:
type: relay
listener:
type: mtcp
metadata:
mux.version: 2
mux.keepaliveDisabled: false
mux.keepaliveInterval: 10s
mux.keepaliveTimeout: 30s
mux.maxFrameSize: 32768
mux.maxReceiveBuffer: 33554432
mux.maxStreamBuffer: 4194304
```
## 5. 前置 GOST 配置
前置监听 `:12000`,通过 `chain-mtcp` 使用 MTCP dialer 连接远端。
```yaml
services:
- name: tcp-entry
addr: :12000
handler:
type: tcp
chain: chain-mtcp
listener:
type: tcp
forwarder:
nodes:
- name: target
addr: 127.0.0.1:2345
chains:
- name: chain-mtcp
hops:
- name: remote
nodes:
- name: remote-mtcp
addr: 203.0.113.20:11000
connector:
type: relay
dialer:
type: mtcp
metadata:
mux.version: 2
mux.keepaliveDisabled: false
mux.keepaliveInterval: 10s
mux.keepaliveTimeout: 30s
mux.maxFrameSize: 32768
mux.maxReceiveBuffer: 33554432
mux.maxStreamBuffer: 4194304
```
最终链路大致是:
```
TCP 客户端 → 前置:12000 → GOST TCP Handler → GOST MTCP Dialer → 203.0.113.20:11000 → 远端 GOST Relay → 127.0.0.1:2345
```
示例目标地址,可以替换成任意内网/本机地址,本质是 TCP 到 MTCP 隧道,再到 TCP。
## 6. 判断 ECMP 快路 / 慢路
使用命令查看连接:
```bash
ss -tin "dst 203.0.113.20:11000"
```
重点看 `minrtt:` 字段,而不是当前 RTT,因为当前 RTT 可能受排队、突发流量、重传、ACK 延迟等影响,`minrtt` 更能反映这条 TCP 连接最初选中的路径延迟。
举例:
- 快路 RTT 32~34ms
- 慢路 RTT 50~52ms
阈值可设 `THRESH=40`,`minrtt < THRESH`判为快路,否则算慢路。
## 7. 启动预热脚本 `mtcp-prewarm.sh`
脚本只负责在启动阶段做选路,核心逻辑:
- 目标地址上不存在 ESTAB 连接时,主动 connect 触发 GOST 建立 MTCP,等待连接建立。
- ESTAB 连接数为 1 时,读取 `minrtt`,若小于阈值连续确认多次,即判定预热成功;若不满足,则用 `ss -K` 踢掉慢连接,等待它彻底断开,重新触发新连接加入 ECMP。
- ESTAB 超过1条时,认为状态异常,直接报错退出,不自动踢,避免影响正常业务,请人工重启对应 GOST 服务。
脚本支持多目标、自定义触发命令 `PREWARM_CMD`,所有阈值和超时可通过环境变量覆写,详细用法见脚本内说明。
(此处省略脚本全文,详见原文链接)
校验脚本语法:
```bash
chmod +x /root/mtcp-prewarm.sh
bash -n /root/mtcp-prewarm.sh
```
## 8. 使用方法
单目标手动执行:
```bash
THRESH=40 \
PREWARM_ADDR=127.0.0.1:12000 \
/root/mtcp-prewarm.sh 203.0.113.20 11000
```
如果首次选到慢路,脚本会执行 `SLOW → KILL → 等待旧连接消失 → 重新触发`,直到连续两次确认选到快路,输出 `PREWARM SUCCESS`。
多目标一次执行:
```bash
./mtcp-prewarm.sh \
203.0.113.20 11000 \
198.51.100.20 11000
```
不同目标用不同本地触发入口:
```bash
./mtcp-prewarm.sh \
203.0.113.20:11000@127.0.0.1:12000 \
198.51.100.20:11000@127.0.0.1:13000
```
如果普通 TCP connect 无法真正触发业务链,可自定义触发命令:
```bash
PREWARM_CMD="timeout 1 bash -c 'exec 3<>/dev/tcp/127.0.0.1/12000; sleep 0.3'" \
./mtcp-prewarm.sh 203.0.113.20 11000
```
不同线路 RTT 档位不同时,建议为它们创建独立 systemd 服务,分别设置对应的 `THRESH`(例如线路 A 快路 33ms/慢路 51ms 用 `THRESH=40`,线路 B 快路 46ms/慢路 60ms 用 `THRESH=52`)。
## 9. systemd 集成
推荐用 systemd 管理 GOST,包括开机启动、异常拉起、日志统一,并在每次启动后自动做一次 ECMP 预热(通过 `ExecStartPost`)。
前置 GOST(主线路)示例 systemd 单元文件 `/etc/systemd/system/gost-mtcp-main.service`:
```bash
[Unit]
Description=GOST MTCP Main
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/root/gost
ExecStart=/root/gost/gost -D -C /root/gost/cn.yaml
Environment="PREWARM_ADDR=127.0.0.1:12000"
Environment="THRESH=40"
ExecStartPost=/bin/bash -lc '/root/mtcp-prewarm.sh 203.0.113.20 11000 || true'
Restart=on-failure
RestartSec=2
TimeoutStartSec=120
KillSignal=SIGTERM
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
```
启动及查看状态:
```bash
systemctl daemon-reload
systemctl enable --now gost-mtcp-main
systemctl status gost-mtcp-main --no-pager
journalctl -u gost-mtcp-main -n 100 -l --no-pager
```
`ExecStartPost` 后加上 `|| true`,避免预热失败被 systemd 视为启动失败,导致无限重启循环。预热失败只写日志,主进程正常运行,方便人工检查。
## 10. 验证与常见问题
**验证只有一个 MTCP 外层连接且命中快路后,即完成预热。**
(正文后续未提供,至此为止)
---
原文链接:[点击查看](https://www.v2ex.com/t/1233477)
## 0. 优选后测试结果截图
RTT 延迟稳定不抖动,不掉包,单线程测速也能跑起来。



## 1. 问题背景
部分跨境或跨运营商线路(这里主要说上海 9929 宽带,存在多条 4837 和 9929 混合 ECMP):据说联通将这项业务外包给第三方,公司后端接入了多条 AS4837(普通联通骨干网)和 AS9929(联通精品网)线路。
这种 ECMP(等价多路径)导致 ICMP ping 很稳定,但不同 TCP 连接的 RTT 会落到几个明显不同的档位,比如一部分约 33ms,另一部分约 51ms。每次新建 TCP 连接时,会重新参与 ECMP 哈希,导致不同业务连接分别经历快路或慢路,延迟因此波动。
## 2. 核心思路
***启动 GOST MTCP 阶段,选出一条低延迟的外层 TCP,后续所有业务都复用这条连接***,而不是在运行过程中持续探测或踢慢连接。
```
任意 TCP 客户端 → 前置 GOST :12000 → 单个长期 MTCP session(4 MiB Stream/32 MiB Receive)
→ 启动阶段检测并只保留低延迟外层 TCP → 远端 GOST :11000 → 目标 TCP 服务
```
GOST MTCP 将多个逻辑 TCP 流复用到同一条外层 TCP 上,因此 ECMP 抽路的影响从“每个业务连接抽一次”收敛到“MTCP 外层连接建立时抽一次”。
我之前试过维持多条 MTCP 连接,定期探测 RTT,结合 `ss -K` 命令踢掉慢路,但存在两个问题:一是 `ss -K` 踢掉后会影响该 session 下所有逻辑流;二是踢掉重建的连接又要重新参与 ECMP,有可能落到慢路,导致“抽路-清理”死循环。最终改成只配置一个 MTCP 节点,正常运行只允许一个外层 TCP,只在启动阶段做 ECMP 预热,命中快路后不再干预,运行时不做周期检测、不踢连接也不主动重建。
## 3. MTCP 缓冲区参数
单个流缓冲区太小会限制吞吐,需根据 BDP(带宽延迟积)估算。例如 RTT 大约 33ms,目标带宽约 400Mbps,BDP ≈ 400Mbps × 0.033秒 ≈ 1.65MiB,4 MiB 的流缓冲较为充足:
```yaml
mux.maxFrameSize: 32768 # 32 KiB
mux.maxReceiveBuffer: 33554432 # 32 MiB
mux.maxStreamBuffer: 4194304 # 4 MiB
```
## 4. 远端 GOST 配置
远端只需监听 `:11000`,具体转发目标由前置 GOST 的 `forwarder` 指定。
配置示例 `/root/gost/remote.yaml`:
```yaml
services:
- name: mtcp-server
addr: :11000
handler:
type: relay
listener:
type: mtcp
metadata:
mux.version: 2
mux.keepaliveDisabled: false
mux.keepaliveInterval: 10s
mux.keepaliveTimeout: 30s
mux.maxFrameSize: 32768
mux.maxReceiveBuffer: 33554432
mux.maxStreamBuffer: 4194304
```
## 5. 前置 GOST 配置
前置监听 `:12000`,通过 `chain-mtcp` 使用 MTCP dialer 连接远端。
```yaml
services:
- name: tcp-entry
addr: :12000
handler:
type: tcp
chain: chain-mtcp
listener:
type: tcp
forwarder:
nodes:
- name: target
addr: 127.0.0.1:2345
chains:
- name: chain-mtcp
hops:
- name: remote
nodes:
- name: remote-mtcp
addr: 203.0.113.20:11000
connector:
type: relay
dialer:
type: mtcp
metadata:
mux.version: 2
mux.keepaliveDisabled: false
mux.keepaliveInterval: 10s
mux.keepaliveTimeout: 30s
mux.maxFrameSize: 32768
mux.maxReceiveBuffer: 33554432
mux.maxStreamBuffer: 4194304
```
最终链路大致是:
```
TCP 客户端 → 前置:12000 → GOST TCP Handler → GOST MTCP Dialer → 203.0.113.20:11000 → 远端 GOST Relay → 127.0.0.1:2345
```
示例目标地址,可以替换成任意内网/本机地址,本质是 TCP 到 MTCP 隧道,再到 TCP。
## 6. 判断 ECMP 快路 / 慢路
使用命令查看连接:
```bash
ss -tin "dst 203.0.113.20:11000"
```
重点看 `minrtt:` 字段,而不是当前 RTT,因为当前 RTT 可能受排队、突发流量、重传、ACK 延迟等影响,`minrtt` 更能反映这条 TCP 连接最初选中的路径延迟。
举例:
- 快路 RTT 32~34ms
- 慢路 RTT 50~52ms
阈值可设 `THRESH=40`,`minrtt < THRESH`判为快路,否则算慢路。
## 7. 启动预热脚本 `mtcp-prewarm.sh`
脚本只负责在启动阶段做选路,核心逻辑:
- 目标地址上不存在 ESTAB 连接时,主动 connect 触发 GOST 建立 MTCP,等待连接建立。
- ESTAB 连接数为 1 时,读取 `minrtt`,若小于阈值连续确认多次,即判定预热成功;若不满足,则用 `ss -K` 踢掉慢连接,等待它彻底断开,重新触发新连接加入 ECMP。
- ESTAB 超过1条时,认为状态异常,直接报错退出,不自动踢,避免影响正常业务,请人工重启对应 GOST 服务。
脚本支持多目标、自定义触发命令 `PREWARM_CMD`,所有阈值和超时可通过环境变量覆写,详细用法见脚本内说明。
(此处省略脚本全文,详见原文链接)
校验脚本语法:
```bash
chmod +x /root/mtcp-prewarm.sh
bash -n /root/mtcp-prewarm.sh
```
## 8. 使用方法
单目标手动执行:
```bash
THRESH=40 \
PREWARM_ADDR=127.0.0.1:12000 \
/root/mtcp-prewarm.sh 203.0.113.20 11000
```
如果首次选到慢路,脚本会执行 `SLOW → KILL → 等待旧连接消失 → 重新触发`,直到连续两次确认选到快路,输出 `PREWARM SUCCESS`。
多目标一次执行:
```bash
./mtcp-prewarm.sh \
203.0.113.20 11000 \
198.51.100.20 11000
```
不同目标用不同本地触发入口:
```bash
./mtcp-prewarm.sh \
203.0.113.20:11000@127.0.0.1:12000 \
198.51.100.20:11000@127.0.0.1:13000
```
如果普通 TCP connect 无法真正触发业务链,可自定义触发命令:
```bash
PREWARM_CMD="timeout 1 bash -c 'exec 3<>/dev/tcp/127.0.0.1/12000; sleep 0.3'" \
./mtcp-prewarm.sh 203.0.113.20 11000
```
不同线路 RTT 档位不同时,建议为它们创建独立 systemd 服务,分别设置对应的 `THRESH`(例如线路 A 快路 33ms/慢路 51ms 用 `THRESH=40`,线路 B 快路 46ms/慢路 60ms 用 `THRESH=52`)。
## 9. systemd 集成
推荐用 systemd 管理 GOST,包括开机启动、异常拉起、日志统一,并在每次启动后自动做一次 ECMP 预热(通过 `ExecStartPost`)。
前置 GOST(主线路)示例 systemd 单元文件 `/etc/systemd/system/gost-mtcp-main.service`:
```bash
[Unit]
Description=GOST MTCP Main
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/root/gost
ExecStart=/root/gost/gost -D -C /root/gost/cn.yaml
Environment="PREWARM_ADDR=127.0.0.1:12000"
Environment="THRESH=40"
ExecStartPost=/bin/bash -lc '/root/mtcp-prewarm.sh 203.0.113.20 11000 || true'
Restart=on-failure
RestartSec=2
TimeoutStartSec=120
KillSignal=SIGTERM
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
```
启动及查看状态:
```bash
systemctl daemon-reload
systemctl enable --now gost-mtcp-main
systemctl status gost-mtcp-main --no-pager
journalctl -u gost-mtcp-main -n 100 -l --no-pager
```
`ExecStartPost` 后加上 `|| true`,避免预热失败被 systemd 视为启动失败,导致无限重启循环。预热失败只写日志,主进程正常运行,方便人工检查。
## 10. 验证与常见问题
**验证只有一个 MTCP 外层连接且命中快路后,即完成预热。**
(正文后续未提供,至此为止)
---
原文链接:[点击查看](https://www.v2ex.com/t/1233477)
· 0 个赞