香港服务器上,我们如何设置并结合 CN2 带宽来优化游戏 TCP/UDP 传输,降低玩家跨境延迟?

那天是周五的凌晨两点,香港旺角机房的空调像往常一样嗡嗡作响。我盯着大屏上从华南到香港的 RTT 曲线,它在 20:00–23:30 间像心电图一样抖动。社区里玩家的反馈已经炸开了锅:“晚高峰打团抖成 PPT,技能延迟 200+ms。”
我们服务器在香港,骨干走的是普通国际回程。直觉告诉我,这不是服务器算力问题,而是跨境链路在晚高峰被挤爆。我决定把**CN2(China Telecom Next Carrier Network)**拉进来:用 CN2 GIA 兜住电信用户、用 CMI(中国移动国际)兜住移动用户;必要时再给联通用户提供 9929/精品网备胎。思路清晰后,我在机房铺开笔记本,开始下面这套“分流 + 内核 + 队列 + MTU + 监控”组合拳。
环境与目标
我们的目标
- 晚高峰跨境延迟(华南/华东 → HK)降到 40–70 ms 的稳定区间,抖动(Jitter)< 10 ms
- 游戏 TCP:丢包友好、避免重传雪崩
- 游戏 UDP:抗抖动、避免分片、尽量走就近优线路
- 故障自动切换:CN2/CMI 任一线路波动不殃及全量玩家
机器与线路(真实可落地)
| 角色 | 规格/参数 |
|---|---|
| 机型 | 1× Dell R7525(或同档) |
| CPU | AMD EPYC 7313P(16C/32T) |
| 内存 | 128 GB DDR4 |
| 磁盘 | 2× NVMe (PCIe 3.0,读 3GB/s) |
| 网卡 | Intel XXV710(25G,RSS/TSO/GRO 支持) |
| 操作系统 | CentOS 7.9(kernel 升级至 5.x,启用 BBR) |
| 上游 | CN2 GIA 100 Mbps commit、CMI 200 Mbps commit(或等价) |
| 形态 | 直连 +(必要时)GRE/WireGuard 隧道;多表路由分流 |
| 游戏 | TCP(登录/交易)+ UDP(战斗/状态)混部,端口段 20000–21000 |
方案总览(四层优化栈)
- 线路与分流:CN2/CMI/(9929 可选)多挂;按来源 ASN / 目的网段做策略路由,让电信→CN2、移动→CMI,避免“走错线”。
- 链路与 MTU/MSS:如用隧道(GRE/WireGuard/IPsec),计算开销,设置 MTU 与 MSS Clamping,避免分片。
- 主机网络栈:CentOS 7 升级内核启用 BBR + fq/fq_codel/cake;合理的 rmem/wmem/backlog/conntrack。
- 应用层:TCP(TFO/SACK/低重传抖动)、UDP(NOTRACK + recvmsg/recvmmsg + SO_REUSEPORT)、端口隔离与限速保护。
- 可观测与回归:iperf3/mtr/tcping/SmokePing + 黑白盒监控 + 自动故障切换脚本。
实操步骤
以下命令默认 CentOS 7.9。BBR 需要 Linux 4.9+,故我们从 ELRepo 升级到 5.x 内核。
Step 1:升级内核、启用 BBR 与队列
安装 ELRepo 并切换 kernel-ml(5.x)
sudo yum install -y https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm
sudo yum --enablerepo=elrepo-kernel install -y kernel-ml
sudo grub2-set-default 0
sudo reboot
重启后:
uname -r # 确认5.x内核
启用 BBR 与 FQ:
cat <<'EOF' | sudo tee /etc/sysctl.d/99-game-net.conf
# 拥塞算法
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
# 缓冲相关(UDP/TCP)
net.core.rmem_max = 268435456
net.core.wmem_max = 268435456
net.ipv4.udp_rmem_min = 16384
net.ipv4.udp_wmem_min = 16384
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
# 队列/接收
net.core.netdev_max_backlog = 250000
net.ipv4.tcp_max_syn_backlog = 16384
net.core.somaxconn = 65535
# 连接追踪(按需微调)
net.netfilter.nf_conntrack_max = 262144
net.netfilter.nf_conntrack_udp_timeout = 30
net.netfilter.nf_conntrack_udp_timeout_stream = 180
# 其他
net.ipv4.tcp_mtu_probing = 1 # 避免路径MTU问题
net.ipv4.tcp_sack = 1
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_fastopen = 3 # client+server
EOF
sudo sysctl --system
sysctl net.ipv4.tcp_congestion_control # 看到 bbr 即生效
若内核已包含 cake(部分内核需要模块或 backport),可以把根队列切为 fq_codel/cake(见 Step 3)。
Step 2:打通 CN2/CMI 与健康探测
直连优先;若运营商提供的是隧道到 HK POP(常见 GRE/WireGuard),注意 MTU。
以 WireGuard 为例(加密少损耗,易运维):
# 安装 WireGuard(EL7)
sudo yum install -y epel-release
sudo yum install -y wireguard-dkms wireguard-tools
# /etc/wireguard/wg-cn2.conf
cat <<'EOF' | sudo tee /etc/wireguard/wg-cn2.conf
[Interface]
Address = 10.10.2.2/30
PrivateKey = <SERVER_PRIVATE_KEY>
ListenPort = 51820
MTU = 1420
[Peer]
PublicKey = <CN2_POP_PUBLIC_KEY>
Endpoint = cn2.pop.example.com:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 15
EOF
sudo systemctl enable wg-quick@wg-cn2
sudo systemctl start wg-quick@wg-cn2
ip link show wg-cn2
MTU 计算:以 WireGuard 为例,开销 ~60–80 Byte,1420–1440 常见。GRE 通常建议 1476。后文会配合 TCP MSS clamp。
健康探测与自动切换(简版):
# /usr/local/bin/healthcheck_cn2.sh
#!/usr/bin/env bash
TARGET="223.120.2.1" # CN2/电信近端探针或对端IP
TABLE=100
IF="wg-cn2"
COUNT=3
LOSS=$(ping -I $IF -c $COUNT -W1 $TARGET | awk -F',' '/packet loss/{print $3+0}')
if (( LOSS >= 66 )); then
# 高丢包 -> 降权(删除策略),走默认CMI
ip rule del fwmark 0x1 table $TABLE 2>/dev/null
else
# 健康 -> 恢复优先策略
ip rule add fwmark 0x1 table $TABLE pref 100 2>/dev/null
fi
chmod +x /usr/local/bin/healthcheck_cn2.sh
echo '*/1 * * * * root /usr/local/bin/healthcheck_cn2.sh' | sudo tee /etc/cron.d/hc_cn2
Step 3:按运营商/ASN 分流(电信→CN2、移动→CMI)
我们采用 ipset + iptables mangle 标记 + 多表路由:
准备路由表:
echo "100 cn2" | sudo tee -a /etc/iproute2/rt_tables
echo "200 cmi" | sudo tee -a /etc/iproute2/rt_tables
# 指定下一跳(示例:wg-cn2 / eth0)
sudo ip route add default dev wg-cn2 table cn2
sudo ip route add default via 10.0.0.1 dev eth0 table cmi # CMI 直连网关
维护电信/移动网段的 ipset(可按 ASN 过滤生成,下面示例手动演示少量前缀——生产用脚本每天拉 APNIC/各家 CIDR 刷新):
sudo ipset create ct hash:net -exist
sudo ipset add ct 36.128.0.0/10
sudo ipset add ct 61.48.0.0/12
# ...(省略:生产环境用脚本按 AS4134/AS4809 等生成)
sudo ipset create cm hash:net -exist
sudo ipset add cm 39.128.0.0/10
sudo ipset add cm 117.128.0.0/9
# ...(省略:AS9808、AS56040 等)
mangle 标记 + policy route:
# 电信 -> 打标 0x1
sudo iptables -t mangle -A PREROUTING -m set --match-set ct src -j MARK --set-mark 0x1
sudo ip rule add fwmark 0x1 table cn2 pref 100
# 移动 -> 打标 0x2
sudo iptables -t mangle -A PREROUTING -m set --match-set cm src -j MARK --set-mark 0x2
sudo ip rule add fwmark 0x2 table cmi pref 200
如果你做回程分流(根据目的地选择出口),把 src 改成 dst 即可。
联通 9929 可另建 table 300 与 ipset cu,原则同上。
Step 4:队列管理(fq_codel/cake) 降抖动
晚高峰的排队抖动比纯延迟更伤游戏体验。我们在出口施加 Smart Queue Management:
# fq_codel(内核通用)
sudo tc qdisc replace dev eth0 root fq_codel target 5ms interval 100ms limit 20000 flows 4096
# 如果内核有 cake(更智能)
# 例:总带宽 300Mbps(上下行可分别配置)
sudo tc qdisc replace dev eth0 root cake bandwidth 300M besteffort nat
落地经验:给外发方向加队列效果最明显。入方向只能在上游做(或用 IFB 中转,配置复杂度上升)。当 commit 只有 100–200 Mbps 时,cake对晚高峰控抖很有效。
Step 5:MTU/MSS 与分片火坑
有隧道就有开销。WireGuard 1420、GRE 1476 是稳妥起点。为防 TCP 分片,做 MSS Clamp:
# 针对外发 TCP SYN
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
UDP 无 MSS 概念,避免应用层 payload 过大(<1200 Byte 是比较保险的通用值),尤其穿隧道时。
Step 6:NIC 与 CPU 亲和(低延迟不是只靠内核参数)
# 调大 ring buffer
sudo ethtool -G eth0 rx 4096 tx 4096
# 自适应中断合并(低延迟/高吞吐取平衡)
sudo ethtool -C eth0 adaptive-rx on adaptive-tx on
# 观察中断分布
cat /proc/interrupts | egrep 'eth0|ixgbe|i40e|iavf'
# 开启RPS/RFS(根据CPU核数分散软中断)
echo ffffffff | sudo tee /sys/class/net/eth0/queues/rx-0/rps_cpus
echo 32768 | sudo tee /proc/sys/net/core/rps_sock_flow_entries
for f in /sys/class/net/eth0/queues/rx-*/rps_flow_cnt; do echo 4096 | sudo tee $f; done
# SO_BUSY_POLL 可以少量开启(谨慎)
echo 50 | sudo tee /proc/sys/net/core/busy_poll
echo 50 | sudo tee /proc/sys/net/core/busy_read
irqbalance 默认已开;极端低延迟场景可手工绑核(NUMA 亲和),但要结合压测与温度监控。
Step 7:UDP 高并发与 NOTRACK(游戏房间用得上)
对指定 UDP 端口段关闭 conntrack,降低 CPU 与延迟:
# raw 表 NOTRACK
sudo iptables -t raw -A PREROUTING -p udp --dport 20000:21000 -j NOTRACK
sudo iptables -t raw -A OUTPUT -p udp --sport 20000:21000 -j NOTRACK
应用侧建议开启 SO_REUSEPORT,多进程抢同端口,提高并发收包能力;读取用 recvmmsg() 比 recvfrom() 更省系统调用。
Step 8:TCP 的稳与快
BBR 已启;保留 SACK + TS。
TCP Fast Open (TFO):对新连接首包可提速,但国内少部分中间盒会干扰;若观测到异常握手,降级关闭或仅对可信 NAT 开启。
超时与重传:适当降低 tcp_syn_retries=5→3,避免慢死链路拖垮排队;tcp_fin_timeout=15。
Step 9:安全与限速
晚高峰常伴随UDP Flood 与 SYN Flood:
# SYN cookie
sysctl -w net.ipv4.tcp_syncookies=1
# 基础限速(示例)
sudo iptables -A INPUT -p udp --dport 20000:21000 -m hashlimit \
--hashlimit 2000/sec --hashlimit-burst 4000 --hashlimit-mode srcip \
--hashlimit-name udprate -j ACCEPT
sudo iptables -A INPUT -p udp --dport 20000:21000 -j DROP
真正的大流量还是建议上游清洗或引入 Anycast + 多点就近。
监控与压测方法
- 链路层:mtr -rwzbc100 目标IP(看丢包/波动);iperf3 -u -b 50M(UDP 抖动与丢包)。
- TCP 首包:tcping 对登录服端口测 RTT。
- 持续可视化:SmokePing 针对CN2 与 CMI 分别打点;Prometheus + Grafana 监控 conntrack、qdisc 队列、软中断、网卡丢包。
优化前后对比(实测样本)
| 地区 → HK | 业务 | 优化前 RTT (avg) | 优化后 RTT (avg) | 抖动 (p95-p50) | 备注 |
|---|---|---|---|---|---|
| 深圳(电信) | UDP | 85 ms | 38 ms | 5 ms | CN2 分流 |
| 广州(移动) | UDP | 110 ms | 55 ms | 7 ms | CMI 分流 |
| 上海(电信) | TCP | 140 ms | 68 ms | 8 ms | BBR + MSS Clamp |
| 成都(移动) | UDP | 160 ms | 78 ms | 9 ms | fq_codel 生效 |
统计口径:晚高峰 20:00–23:00,5 分钟粒度,剔除异常峰值与丢包>10% 样本。
常见坑与我的填坑记录
只做单向优化:接入 CN2 只优化去程或回程的一侧,另一侧仍走拥塞网,RTT 抖动仍高。
对策:尽量让往返都能命中优线路(必要时两侧都引流到同一 POP)。
隧道 MTU 算错:设置 1500 不管,结果 TCP 分片、UDP 丢包。
对策:WireGuard 1420 起步,配合 --clamp-mss-to-pmtu,UDP payload 控制 < 1200。
cake + BBR 的参数冲突:过小的 quantum/flows 导致吞吐飘。
对策:以出口带宽为基准配置 cake,观察 tc -s qdisc 的丢包与延迟分布做回归。
TFO 在部分 NAT 异常:玩家登录卡死。
对策:对登录服灰度启用,异常 ASN 段降级关闭。
NOTRACK 过猛:忘了对外联机调试端口;导致排查困难。
对策:只对游戏房间端口段 NOTRACK,控制面/管理面仍走 conntrack。
ipset 更新不及时:前缀漂移后电信用户走到 CMI。
对策:每天 4 次自动拉取 APNIC/各 ASN 前缀,原子更新 ipset。
上游突发限速:commit 不够,出口包被丢。
对策:cake 的 bandwidth 与实际 commit 保持一致,预留 5–10% 头寸。
附:自动更新 ASN 前缀到 ipset(示例骨架)
生产环境可从 APNIC/各运营商公开表或自有路由器导出。这里放个简化脚本骨架,思路是生成新集合 → 原子切换。
#!/usr/bin/env bash
# /usr/local/bin/update_asn_ipset.sh
set -euo pipefail
TMPSET=ct_new
REALSET=ct
ASN_LIST=("4134" "4809") # 电信常见 ASN(示意)
ipset create $TMPSET hash:net -exist
for ASN in "${ASN_LIST[@]}"; do
curl -fsSL "https://example.com/asn/${ASN}.cidr" | \
awk '/^[0-9]+\./{print $1}' | while read -r cidr; do
ipset add $TMPSET $cidr -exist
done
done
# 原子切换
ipset swap $TMPSET $REALSET
ipset destroy $TMPSET
定时:
echo '*/15 * * * * root /usr/local/bin/update_asn_ipset.sh' | sudo tee /etc/cron.d/ipset_update
关键参数速查表
| 类别 | 参数/命令 | 建议 |
|---|---|---|
| 拥塞 | net.ipv4.tcp_congestion_control |
bbr |
| 队列 | tc qdisc ... fq_codel / cake |
出口设备启用,带宽按 commit 配置 |
| 缓冲 | net.core.rmem_max/wmem_max |
256 MiB 级别 |
| backlog | net.core.netdev_max_backlog |
250k 起 |
| 追踪 | nf_conntrack_* |
对 UDP 房间端口 NOTRACK |
| MTU | ip link set dev ... mtu |
WireGuard ≈ 1420;GRE ≈ 1476 |
| MSS | TCPMSS --clamp-mss-to-pmtu |
必开 |
| 亲和 | rps_cpus/rps_flow_cnt |
按核数分配 |
| ring | ethtool -G |
rx/tx = 4096 |
| 合并 | ethtool -C ... adaptive-* on |
on |
结尾:告别“晚高峰综合征”的那个夜晚
凌晨四点,SmokePing 上两条曲线终于贴着地面走。第二天晚上八点,我把监控投到会议室的大屏,团队一起盯着那些曾经暴躁的抖动点变得安静。社区里最先冒出来的一条反馈是:“今晚团战稳得离谱,手感回来了!”
这套方案并不神秘:把用户分到该走的路上(CN2/CMI/9929),把主机栈打磨到位(BBR + 队列 + MTU/MSS),再用可观测与自动化把波动压回去。它像我们在机房里反复拧紧的一颗颗螺丝,没有花哨,但每一颗都咬得更紧。
如果你也在香港跑游戏业务、也在为跨境延迟头大,不妨按这套步骤试一遍。也许,下一次凌晨两点,你能比我更早把那条心电图拉直。
说明:本文以 CentOS 7.9 为基础演示,如你的内核仍是 3.10,请务必先按文中步骤升级到 4.9+ 才能启用 BBR。若你的上游已提供原生 CN2/CMI 直连且支持 BGP,可把 ipset+PBR 替换为 BGP 社区策略 做更精细的流量工程;但对租用型单机/单段,文中方案更容易直接落地。