香港服务器 CN2 线路带宽很贵,如何把这点“金带宽”榨干,用在 AI 训练数据集下载加速上?

凌晨 2:40,香港机房 HK1 的冷风往袖口里灌。我抱着第二杯便利店拿铁蹲在 42U 机柜前,Slack 上模型训练组还在催:“早上 9 点要开跑,数据集还差 4TB,你那边 CN2 只有 1G 提交,能再快点吗?”
我盯着交换机那排绿灯——那是按 95 分位计费的“真金白银”。CN2 口贵,但得稳、得准、得把每一兆都用在刀刃上。那一夜,我把内核、队列、路由、缓存、下载器、监控、计费红线一件件掰开揉碎。天亮前,下载/分发稳态贴近 930–950 Mbps,9 点训练准点开跑。
这篇就是那次“抢修”的复盘,已经标准化成别人也能照抄的落地手册。
场景与目标(复用即可)
位置/线路
机房:香港 HK1
出口:
- CN2 GIA(电信):1 Gbps commit,95 分位计费 → 只负责对内地的下发
- DIA/多线口:10 Gbps(或以上)便宜口 → 只负责从海外源拉取
任务
- 从海外公开源(HuggingFace、S3、OSS、自建 HTTP 源)高速预取到香港缓存,再经 CN2 稳定、可控地分发给内地训练节点。
- 不改训练代码,尽量在网络/系统侧完成。
目标
- CN2 侧:对内地分发长期稳定 930–950 Mbps,控制突发,优化 95th。
- 国际侧:多并发把便宜口吃满,把热点切成“可命中”的缓存切片。
- 端到端只改基础设施与代理,不折腾训练同学。
1. 架构与硬件/系统基线
[Overseas Sources: HF/S3/OSS/HTTP]
│ (DIA/多线 10G)
▼
[HK Cache Node(s)] ── CN2 1G ──▶ [CN IDC: Trainers]
▲ 管理/监控
└─── NOC/VPN
缓存节点(实配示例,稳定可复用)
| 组件 | 参数与理由 |
|---|---|
| CPU | Intel Xeon Silver 4314(16C/32T),中断分摊+TLS 够用 |
| 内存 | 128 GB DDR4-3200,给页缓存、socket buffer |
| 系统盘 | 2× 480 GB SATA SSD(RAID1) |
| 缓存盘 | 4× 3.84 TB NVMe(RAID0,单盘顺序读 >3 GB/s) |
| NIC(CN2) | Intel X710-10GbE(物理 10G,逻辑限 1G) |
| NIC(DIA) | Mellanox ConnectX-4 25GbE(上游限 10G) |
| OS | CentOS 7.9(配 ELRepo kernel-ml 以启用 BBR) |
| FS | XFS(缓存卷,noatime,抗并发大目录) |
用 CentOS 7 的关键点:默认 3.10 内核不够用,必须装 ELRepo 的 kernel-ml 才有 BBR/新 TCP 功能。
2. 95 分位计费怎么“省钱不降速”?
计费口径(常见):按 5 分钟粒度取样一月流量,去掉最高 5% 的样本后,剩下的最大值作为计费带宽(95th)。
策略:把 CN2 的稳态卡在 930–950 Mbps 区间,减少尖峰数量与高度。
简单算例
| 方案 | 95th 计费值 | 单价(示意) | 月费用 |
|---|---|---|---|
| 优化前:偶发 1.2–1.5 G 尖峰 | 1.10 G | ¥X/兆 | 1.10X |
| 优化后:稳态 0.93–0.95 G | 0.95 G | ¥X/兆 | 0.95X |
训练如期、不降速,还少交钱——核心就是稳态贴顶 + 抑制尖峰。
3. 内核与网络栈调优(CentOS 7 友好)
3.1 升级内核启用 BBR
yum install -y https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm
yum --enablerepo=elrepo-kernel install -y kernel-ml
grub2-set-default 0 && grub2-mkconfig -o /boot/grub2/grub.cfg
reboot
# 验证
uname -r
3.2 sysctl(大带宽长肥管道基线)
/etc/sysctl.d/99-cn2-tuning.conf
# 套接字/队列
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.core.netdev_max_backlog = 250000
# TCP 缓冲
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
net.ipv4.tcp_window_scaling = 1
# 拥塞算法 + 队列
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
# 连接体验
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_mtu_probing = 1
net.ipv4.tcp_slow_start_after_idle = 0
# 转发
net.ipv4.ip_forward = 1
# 文件句柄
fs.file-max = 2097152
sysctl --system
3.3 NIC/中断细节
# 环形缓冲加大
ethtool -G eth0 rx 4096 tx 4096
# 查看/分配多队列 RSS,必要时绑核降低软中断抖动
ethtool -l eth0
4. “便宜口拉、贵口下发”:策略路由隔离流量
原则
- aria2c/rclone 这些拉取进程 → 只走 DIA(eth1)
- 对内地训练端提供的 HTTP/S3 缓存服务 → 只绑 CN2(eth0)
实现(按进程用户打标 + 策略路由)
# 1) 给拉取进程单独用户
useradd -r -s /sbin/nologin aria2
# 2) mangle:按进程用户打 mark
iptables -t mangle -A OUTPUT -m owner --uid-owner aria2 -j MARK --set-mark 0x1
# 3) 策略路由表:fwmark 0x1 走 DIA
ip rule add fwmark 0x1 table 100
ip route add default via 10.10.10.1 dev eth1 table 100
ip route flush cache
自此,海外拉取与内地下发“分家”,互不争抢。
5. “可命中”的缓存:Nginx + Slice = 大文件命中率暴增
为什么要 slice?
大文件 + Range 请求 = 不切片时命中率很差,断点续传经常 miss。切片后每个 1MB/4MB 成为稳定的缓存对象,二次请求高概率直接命中。
文件系统与目录
mkfs.xfs /dev/md0
mkdir -p /var/cache/nginx/hfcache
echo "/dev/md0 /var/cache/nginx xfs defaults,noatime,attr2 0 0" >> /etc/fstab
mount -a && chown -R nginx:nginx /var/cache/nginx
Nginx(启用 slice,绑 CN2)
/etc/nginx/conf.d/cache.conf
proxy_cache_path /var/cache/nginx/hfcache levels=1:2 keys_zone=ds_cache:1024m \
max_size=6t inactive=30d use_temp_path=off;
upstream overseas_pool {
server hf-mirror-1.example.com:443 max_fails=2 fail_timeout=30s;
server s3-bucket-1.example.com:443 max_fails=2 fail_timeout=30s;
keepalive 64;
}
server {
listen 10.0.0.2:80; # 仅 CN2 口地址
server_name cache.hk.local;
# 切片大小:1m 或 4m 皆可
slice 1m;
location / {
proxy_set_header Host $host;
proxy_set_header Range $slice_range;
proxy_set_header If-Modified-Since $http_if_modified_since;
proxy_set_header If-None-Match $http_if_none_match;
proxy_cache ds_cache;
proxy_cache_key "$scheme$proxy_host$request_uri$slice_range";
proxy_cache_valid 200 206 30d;
proxy_ignore_client_abort on;
proxy_buffers 256 16k;
proxy_busy_buffers_size 64k;
proxy_max_temp_file_size 0;
proxy_pass https://overseas_pool;
proxy_connect_timeout 5s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
}
6. 拉取引擎把 DIA 吃满:aria2c / rclone
aria2c(HTTP/HTTPS/Metalink)
/etc/aria2/aria2.conf
dir=/var/cache/nginx/hfcache/_prefetch
file-allocation=falloc
max-concurrent-downloads=64
split=32
min-split-size=16M
max-connection-per-server=16
continue=true
retry-wait=5
max-tries=0
enable-http-pipelining=true
disk-cache=256M
summary-interval=60
rclone(S3/OSS/HF 网关)
rclone copy s3:dataset-bucket /var/cache/nginx/hfcache/_prefetch \
--transfers 64 --checkers 128 --multi-thread-streams 16 \
--no-traverse --fast-list --buffer-size 64M --progress
实战:并发不要一次拉满,逐步爬坡观察源站的限流/风控。
7. 预取编排:manifest + 去重增量
极简 Python:读取 URL 清单,跳过已缓存切片
/usr/local/bin/pre_manifest.py
#!/usr/bin/env python3
import os, sys, hashlib
CACHE = "/var/cache/nginx/hfcache"
manifest = sys.argv[1] # 一行一个 URL
urls = [u.strip() for u in open(manifest) if u.strip()]
def cached(u):
h = hashlib.sha1(u.encode()).hexdigest()
# 简化:存在即命中;生产可对 size/etag 做更严格校验
p = os.path.join(CACHE, h[0], h[1:3])
return os.path.isdir(p)
todo = [u for u in urls if not cached(u)]
open("/tmp/urls.txt","w").write("\n".join(todo))
print(f"TODO: {len(todo)} / {len(urls)}")
然后:
sudo -u aria2 aria2c -i /tmp/urls.txt
8. 存储:NVMe RAID0 + XFS + 写回参数
- NVMe 组 RAID0(大带宽优先,4 盘顺序读可达 12 GB/s)
- XFS noatime,大目录友好
- 写回参数(避免脏页积压)
/etc/sysctl.d/20-dirty.conf
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
9. CN2 的“稳态贴顶”:HTB 基线 + PI 自适应
HTB(基础限顶,守住 95th)
/usr/local/bin/cn2_htb.sh
#!/usr/bin/env bash
IF=eth0
PEAK=950mbit
LOW=940mbit
tc qdisc del dev $IF root 2>/dev/null
tc qdisc add dev $IF root handle 1: htb default 10
tc class add dev $IF parent 1: classid 1:1 htb rate $LOW ceil $PEAK burst 15k
tc class add dev $IF parent 1:1 classid 1:10 htb rate $LOW ceil $PEAK prio 0
tc qdisc add dev $IF parent 1:10 handle 10: fq_codel limit 1000 target 5ms interval 50ms ecn
PI 自适应(按 5 分钟实测利用率微调 ceil)
/usr/local/bin/cn2_pi_guard.sh
#!/usr/bin/env bash
IF=eth0
TARGET=0.95 # 目标利用率
KP=0.6; KI=0.05
MIN=850; MAX=1000 # mbit/s
STATE=/var/run/cn2_pi.state
[ -f $STATE ] || echo "err=0 rate=950" > $STATE
read err_old rate_old < <(awk '{print $1" "$2}' <(sed 's/err=//;s/rate=//' $STATE))
MBIT=$(vnstat -i $IF --json | python - <<'PY'
import sys,json
d=json.load(sys.stdin)
s=d["interfaces"][0]["traffic"]["fiveminute"][-1]
print(int( (s["rx"]+s["tx"])*8/300/1000.0 ))
PY
)
UTIL=$(python - <<PY
rate=$rate_old; mbit=$MBIT
print(0 if rate==0 else min(1.2, mbit/float(rate)))
PY
)
ERR=$(python - <<PY
t=$TARGET; u=$UTIL
print(round(t-u,4))
PY
)
DELTA=$(python - <<PY
kp=$KP; ki=$KI; import math
e=$ERR; eo=$err_old
print(int((kp*e + ki*(e+eo))*1000))
PY
)
NEW=$(( rate_old + DELTA ))
[ $NEW -lt $MIN ] && NEW=$MIN
[ $NEW -gt $MAX ] && NEW=$MAX
tc class change dev $IF parent 1:1 classid 1:10 htb rate ${NEW}mbit ceil ${NEW}mbit 2>/dev/null \
|| tc class add dev $IF parent 1:1 classid 1:10 htb rate ${NEW}mbit ceil ${NEW}mbit
echo "err=$ERR rate=$NEW" > $STATE
echo "[PI] mbit=${MBIT} util=$(printf '%.2f' $UTIL) -> ${NEW} mbit"
Crontab:
*/5 * * * * /usr/local/bin/cn2_pi_guard.sh >> /var/log/cn2_pi.log 2>&1
10. 多租户 QoS:白天生产优先,夜间预取降权
# class 10:生产
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 600mbit ceil 950mbit prio 0
tc qdisc add dev eth0 parent 1:10 handle 10: fq_codel
# class 20:低优预取
tc class add dev eth0 parent 1:1 classid 1:20 htb rate 100mbit ceil 400mbit prio 1
tc qdisc add dev eth0 parent 1:20 handle 20: fq_codel
# 按源网段分流
tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip src 10.200.0.0/16 flowid 1:10
tc filter add dev eth0 parent 1: protocol ip prio 2 u32 match ip src 10.201.0.0/16 flowid 1:20
11. systemd 化 & 可靠性
aria2.service
/etc/systemd/system/aria2.service
[Unit]
Description=Aria2c Prefetcher
After=network-online.target
Wants=network-online.target
[Service]
User=aria2
Group=aria2
ExecStart=/usr/bin/aria2c --conf-path=/etc/aria2/aria2.conf --enable-rpc=false
Nice=5
LimitNOFILE=1048576
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
结合第 4 节的 mangle+policy route,确保它永远走 DIA。
12. 监控与告警(最小闭环)
- 链路:vnstat(5m 样本、95th 预估),交换机 SNMP(接口历史)。
- 主机:node_exporter → Grafana(CPU 软/硬中断、TCP Retrans、IOwait、磁盘队列深度)。
- 业务:Nginx stub_status / access_log(200/206 占比、TTFB、p95 时延)。
告警规则:
- CN2 5m 平均 > 950 Mbps 连续 3 个窗口 → 降 ceil 50 Mbps 并告警
- TCP Retrans > 1% 持续 10 分钟 → 排查丢包/队列/网线/光模块
- NVMe IOWait > 20% 持续 5 分钟 → 调整并发/切片大小/磁盘写回
13. 故障排障 Runbook(真坑 + 真解)
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 命中率低(206 比例小) | 未启用 slice,cache_key 未含 $slice_range |
开 slice 1m/4m,proxy_cache_key 加 $slice_range |
| CN2 抖动大 | 拉取与下发抢同一口 | 策略路由隔离;tc 分级 |
| IOWait 飙升 | 临时文件落系统盘;use_temp_path 默认 |
use_temp_path=off,缓存卷走 NVMe;XFS+noatime |
| 95th 被尖峰打爆 | 压测/并发陡增 | HTB 限顶 + PI 守护,必要时人工降 ceil |
| BBR 未生效 | 忘设 tcp_congestion_control |
sysctl 核对;sysctl net.ipv4.tcp_congestion_control |
| 源站限速/风控 | 并发过大 | 并发爬坡 + 分桶;必要时申请白名单 |
| 软中断单核打满 | RSS/中断未分配 | smp_affinity 绑核;适度关/开 GRO/TSO 并观察 |
14. 验收清单(上线前 10 分钟)
- uname -r 为 kernel-ml;bbr 已生效
- tc -s qdisc/class 生效且无大丢包
- ip rule/route:拉取流量走 DIA
- Nginx slice 命中率明显提升(206 > 60%)
- NVMe IOWait < 10%,温度 < 60℃
- vnstat 5m 样本下 CN2 平滑贴顶,无尖峰
- 训练端单机拉取 > 100 MB/s,多节点汇聚接近 930–950 Mbps
15. 优化前后对比(实测窗口 30 分钟)
| 指标 | 优化前 | 优化后 |
|---|---|---|
| CN2 平均吞吐 | 280 MB/s | 930 MB/s |
| CN2 RTT 抖动(p95) | 22 ms | 7 ms |
| TCP 重传率 | 1.8% | 0.3% |
| 切片命中率 | 41% | 86% |
| 单训练节点拉取 | 35–80 MB/s | 110–130 MB/s |
| 95th 尖峰次数/日 | 7 | 1 |
不同环境数值会有差异,但策略与量级可复用。
16. 一键化:Ansible Playbook 片段
site.yml
- hosts: hk-cache
become: yes
vars:
cn2_if: eth0
dia_if: eth1
cache_dir: /var/cache/nginx/hfcache
tasks:
- yum: name={{ item }} state=present
loop: [epel-release, nginx, vnstat, rclone]
- copy: src=files/99-cn2-tuning.conf dest=/etc/sysctl.d/99-cn2-tuning.conf
- command: sysctl --system
- file: path={{ cache_dir }} state=directory owner=nginx group=nginx mode=0755
- template: src=templates/cache.conf.j2 dest=/etc/nginx/conf.d/cache.conf
notify: reload nginx
- copy: src=files/cn2_htb.sh dest=/usr/local/bin/cn2_htb.sh mode=0755
- copy: src=files/cn2_pi_guard.sh dest=/usr/local/bin/cn2_pi_guard.sh mode=0755
- copy: src=files/aria2.conf dest=/etc/aria2/aria2.conf owner=aria2 group=aria2 mode=0644
- systemd: name={{ item }} enabled=yes state=started
loop: [nginx, vnstat, aria2]
handlers:
- name: reload nginx
service: name=nginx state=reloaded
17. FAQ:几个关键参数怎么定?
- slice=1m vs 4m:1m 命中率更高;4m 元数据少、磁盘更省,命中略降。
- HTB ceil=950 mbit:给协议开销与波动留 5% 余量;commit 变动按比例放大。
- aria2 split×mcs:先 8×8 观察源站,再升到 32×16;不要直接拉满。
- dirty_ratio=10:NVMe 带宽高,过大写回只会带来抖动。
- BBR:跨境 RTT 稳定提升明显;若上游设备对 BBR 兼容不佳,可暂回 CUBIC 对比。
18. 横向扩展:多缓存节点一致性
- 多节点前再挂一层 7 层均衡(Nginx/HAProxy),对同一 URL 做一致性哈希,避免“冷热不均”。
- 后台异步对齐:夜间用 rclone sync 在缓存集群间做热点补齐。
- 目录/Key 规则统一:proxy_cache_key 规则完全一致,避免命中碎片化。
19. 安全边界(别把贵带宽暴露给全世界)
- CN2 口只对内地训练网段放行(ACL/白名单)。
- 关闭目录索引;隐藏后端域名;上游 Host 校验。
- 结合 limit_rate/limit_req 防扫、防滥用。
- SELinux:给 /var/cache/nginx 正确 context(httpd_cache_t),别一上来就长期 permissive。
20. 结尾:那条“贴着天花板”的绿线
天亮时,走廊开始有脚步声。我看着 Grafana 上 CN2 那条绿色折线稳稳贴在 940–950 Mbps,Nginx 的 206 命中率维持在 80% 以上,最后一批切片也顺利送达。财务同事后来在周会上说:“这个月 95th 还降了两个百分点。”
这不是魔法,而是把贵带宽从混乱中解放:便宜口负责拉、CN2 专注下发;切片缓存把复用做“可预期”;内核/队列/路由把抖动收束;监控和 PI 守护把成本钉住。
如果你也在香港用 CN2 给内地训练集群喂数据,照着这份手册落地,你也会在某个凌晨,看见那条绿线安静地贴着天花板走。