上一篇 下一篇 分享链接 返回 返回顶部

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

发布人:Minchunlin 发布时间:2025-09-10 19:11 阅读量:862


凌晨 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 比例小) 未启用 slicecache_key 未含 $slice_range slice 1m/4mproxy_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 给内地训练集群喂数据,照着这份手册落地,你也会在某个凌晨,看见那条绿线安静地贴着天花板走。

目录结构
全文