手游出海如何利用香港服务器的大带宽与QoS策略,保障充值与排名数据的传输稳定性?

凌晨三点,手机把我从梦里拽回现实——越南区充值回调超时飙到 8%,排行榜写入延迟 p99 > 1.2s。游戏刚出海,东南亚投放正猛;而香港,是我们的中枢节点,支付回调和排名数据都要在这里汇聚、再分发。眼前的带宽并不小,真正的问题是它不“听话”。那一刻我明白:要保住“钱”和“分”,不能只堆 G,要把路管起来——用好 QoS,让关键流量先走、稳走。这篇文章,就是我在香港机房的一线实操笔记:从设置到部署、从带宽到策略、从踩坑到复盘,讲清楚如何把一条复杂的跨境链路拧成一根可控的“阀门”。
目标与场景边界
关键业务流
- 充值回调(Payment Callback):第三方支付→香港回源 API → 游戏账务/风控 → 回执。
- 排名写入(Ranking Update):游戏网关→香港排行榜服务(Kafka/Redis/GPRC/HTTP)→ 存储与同步。
SLO(我们对自己订的)
- 充值回调 p99 < 500ms,错误率 < 0.5%
- 排名写入 p99 < 300ms,99.9% 写入延迟 < 800ms
- 链路抖动(Jitter)p95 < 8ms(SEA 区间)
架构路线
- 香港作为区域汇聚与出海出口(BGP 多线 + Anti-DDoS),北向内地、南向东南亚,西向欧中转。
- L4/L7 代理分层:**L4(Nginx Stream/Envoy)**区分端口→QoS 策略分类→出口整形→上游运营商。
- “大带宽 + 精细 QoS”:端口 10/25G,承诺带宽 5–10G,按 Committed Rate 做整形,优先做延迟敏感保稳,再让大流量尽情跑。
1. 选型与硬件落地(香港机房)
服务器与网络设备建议
| 角色 | 推荐规格 | 关键点 | 备注 |
|---|---|---|---|
| 出口网关/代理 | 1U/2U;Xeon Silver 4210R/4314;内存64–128GB | 双口 25GbE SFP28(或至少双口 10GbE),支持 SR-IOV/多队列;NVMe 1–2TB(日志/队列) | CPU 频率要稳,关节能降频 |
| 排行/账务服务 | 同上 | 网卡同级;NVMe ≥ 2TB(WAL/短期落盘) | IOPS 优先 |
| ToR 交换机 | 25G/10G 接入 + 100G 上联 | 低时延、深队列 | Arista/Juniper/华三/华为同级别 |
| 上联运营商 | 多线 BGP(如 HKT/PCCW、HGC、NTT、Telstra、GTT 等) | 至少 2 家+,承诺带宽 5–10Gbps | 可搭配清洗/高防(本地或云) |
小经验:承诺 5G 的口千万别让上游“尽可能跑满”。上游缓冲一深,你的延迟敏感流量就被“挤”了。自己在出口做整形,让对端看到“平滑可控”的流,稳定性会更好。
2. OS/内核与基础网络调优(CentOS 7)
用户曾要求从 CentOS 8 改为 CentOS 7,所以下面都按 CentOS 7 环境实操。
2.1 内核与队列
CentOS 7 默认 3.10 内核,BBR 不可用。要 BBR 就上 ELRepo 的 kernel-ml(≥4.9),但生产机慎重:驱动与旁路安全软件需回归。我的建议:
- 保守方案:CUBIC + fq_codel,已足够把队列打薄。
- 升核路线(如要 BBR):全链路灰度 + 压测 + 回滚预案。
2.2 sysctl(通用调优)
# /etc/sysctl.d/99-game-oversea.conf
net.core.netdev_max_backlog = 250000
net.core.somaxconn = 65535
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
net.ipv4.tcp_mtu_probing = 1
net.ipv4.tcp_congestion_control = cubic
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_timestamps = 1
net.ipv4.ip_local_port_range = 10000 65000
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_synack_retries = 3
net.ipv4.tcp_tw_reuse = 1
# 关闭反向路由过滤,避免多宿主丢包
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
GRO/LRO/GSO 注意:某些 NIC/驱动下,巨包聚合会影响 qdisc 的效果。遇到异常,可针对出口口临时关闭:
ethtool -K ethX gro off gso off tso off(再观察 CPU/吞吐/延迟三角关系)。
3. QoS 设计:把“钱”和“分”优先级抬到最高
3.1 分类策略(业务口径)
| 类别 | 业务 | DSCP | 目标 | 最小保证 | 峰值上限 | 队列策略 |
|---|---|---|---|---|---|---|
| Class P (Payment) | 充值回调、风控 | EF (46) | 低延迟/低抖动 | 承诺带宽的 15–20% | 承诺带宽的 40% | fq_codel |
| Class R (Rank) | 排名写入/回传 | AF31 (26) | 稳定/可预期 | 承诺带宽的 20% | 承诺带宽的 60% | fq_codel |
| Class B (Bulk) | 日志/素材/补丁/CDN 回源等 | BE (0) | 吞吐优先 | 剩余 | 全部剩余 | fq_codel |
原则:先保 P/R,后放 B。当上游抖动或丢包时,P/R 有保底与抢占权。
3.2 分类落地思路
不要指望在加密流里做 DPI。我采用端口/上游 IP/本地 L4 代理来分类:
- 支付回调走 443 → 本地 Nginx Stream 转发到 本地服务的 14443 端口(专为 Payment)。
- 排名写入走 443 → 本地 14444。
- 其它走默认 443。
iptables/ipset 做 DSCP/mark,tc HTB 做带宽保证与上限,叶子队列统一用 fq_codel。
4. 实操部署(分步)
4.1 Nginx Stream(L4)把 HTTPS 流做“端口分流”
# /etc/nginx/nginx.conf(stream 部分)
stream {
log_format basic '$remote_addr:$remote_port -> $server_addr:$server_port '
'$protocol $status $bytes_sent $bytes_received $session_time';
upstream payment_backend { server 127.0.0.1:18443; }
upstream rank_backend { server 127.0.0.1:18444; }
upstream default_backend { server 127.0.0.1:18080; }
# 外部统一进 443,内部按 SNI/域名路由到不同端口(可选:用 lua 或 ssl_preread)
server {
listen 0.0.0.0:443 reuseport;
proxy_connect_timeout 2s;
# 简化:示例用不同外部端口暴露,生产可用 ssl_preread 判断 SNI
}
# 我更实用的做法:对外直接开不同端口,方便 QoS 分类(支付/排行单独监听)
server {
listen 0.0.0.0:14443 reuseport; # Payment
proxy_pass payment_backend;
access_log /var/log/nginx/payment.stream.log basic;
}
server {
listen 0.0.0.0:14444 reuseport; # Ranking
proxy_pass rank_backend;
access_log /var/log/nginx/rank.stream.log basic;
}
server {
listen 0.0.0.0:14480 reuseport; # Bulk/Default
proxy_pass default_backend;
}
}
说明:对外开专用端口是我在香港落地最稳妥的方式——不和 TLS 细节较劲,减少误分类,便于回溯与压测。
4.2 ipset + iptables:打 DSCP/连接标记
# 分类的目的地址集合(如支付网关上游、排行聚合上游)
ipset create ipset_payment hash:ip timeout 0
ipset create ipset_rank hash:ip timeout 0
# 例:把支付网关/排行上游 IP 加入集合
ipset add ipset_payment 203.0.113.10
ipset add ipset_rank 198.51.100.20
# MANGLE 表:基于端口/目的IP 设 DSCP 并做 connmark
iptables -t mangle -N QOS_MARK
iptables -t mangle -A PREROUTING -j QOS_MARK
# 支付:来自/到 14443 或目的在 ipset_payment → EF
iptables -t mangle -A QOS_MARK -p tcp --dport 14443 -j DSCP --set-dscp-class EF
iptables -t mangle -A QOS_MARK -m set --match-set ipset_payment dst -j DSCP --set-dscp-class EF
# 排行:14444 或 ipset_rank → AF31
iptables -t mangle -A QOS_MARK -p tcp --dport 14444 -j DSCP --set-dscp-class AF31
iptables -t mangle -A QOS_MARK -m set --match-set ipset_rank dst -j DSCP --set-dscp-class AF31
# 连接跟踪保持(配合 tc 使用 fwmark)
iptables -t mangle -A QOS_MARK -m dscp --dscp-class EF -j CONNMARK --set-mark 0x1
iptables -t mangle -A QOS_MARK -m dscp --dscp-class AF31 -j CONNMARK --set-mark 0x2
iptables -t mangle -A OUTPUT -m connmark --mark 0x1 -j DSCP --set-dscp-class EF
iptables -t mangle -A OUTPUT -m connmark --mark 0x2 -j DSCP --set-dscp-class AF31
坑:如果你做了 DNAT/SNAT,connmark 可能在跳跃时丢失,要用 CONNMARK --restore-mark 在对应链路再“抬”起来。
4.3 tc:HTB + fq_codel(出口整形)
假设:上游承诺带宽 5Gbps,我们出口整形到 4.8Gbps,留 4%–6% 余量。
DEV=eth0
CEIL=4800mbit
# 清空
tc qdisc del dev $DEV root 2>/dev/null
# 根:HTB
tc qdisc add dev $DEV root handle 1: htb default 30
# 三个 class:10(Pay)、20(Rank)、30(Bulk)
tc class add dev $DEV parent 1: classid 1:10 htb rate 900mbit ceil 2000mbit prio 0
tc class add dev $DEV parent 1: classid 1:20 htb rate 1000mbit ceil 3000mbit prio 1
tc class add dev $DEV parent 1: classid 1:30 htb rate 2900mbit ceil $CEIL prio 2
# 叶队列:统一 fq_codel
for c in 10 20 30; do
tc qdisc add dev $DEV parent 1:$c handle ${c}0: fq_codel target 5ms interval 100ms ecn
done
# 过滤:按 DSCP/mark 分流
tc filter add dev $DEV parent 1: protocol ip prio 1 u32 match ip tos 0xb8 0xfc flowid 1:10 # EF
tc filter add dev $DEV parent 1: protocol ip prio 2 u32 match ip tos 0x68 0xfc flowid 1:20 # AF31
tc filter add dev $DEV parent 1: protocol ip prio 3 u32 match ip tos 0x00 0x00 flowid 1:30 # BE
为什么 fq_codel:在中等并发(成百上千流)下,能有效压平队列尾部的“长尾延迟”。target 5ms 是我在香港—东南亚路径下调出的安全值。
4.4 入方向整形(可选,IFB)
如果上游把你“灌”爆,入口也要“剃波”。代价是 CPU,且要压测。
modprobe ifb numifbs=1
ip link add ifb0 type ifb
ip link set up dev ifb0
tc qdisc add dev $DEV handle ffff: ingress
tc filter add dev $DEV parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev ifb0
# 在 ifb0 上再做与出口类似的 HTB/fq_codel 策略(速率略低于承诺入带)
tc qdisc add dev ifb0 root handle 2: htb default 30
# ...(同上不赘述)
5. 监控与观测(没有可观测,一切都是玄学)
5.1 指标面板(Prometheus/Grafana)
采集
- node_exporter:基础网络/CPU/磁盘
- 自定义脚本抓 tc -s qdisc / tc -s class 输出,解析为 Prometheus 指标(每 15s)
- blackbox_exporter 对支付回调/排行榜 API 做 主动探测(多地区)
- mtr/smokeping:追踪上游运营商抖动/丢包
关键指标
- 每个 class 的 rate/ceil 利用率、队列长度(drop/ecn)
- 充值回调 p95/p99 时延、超时率/重试率
- 排行写入 p95/p99、消息堆积(Kafka/Redis)
- 链路抖动、丢包(按运营商维度)
5.2 告警阈值(我们线上用的)
| 指标 | 阈值 | 动作 |
|---|---|---|
| Payment p99 > 450ms (3 分钟) | WARN | 提醒排查出口/上游 |
| Payment 超时率 > 0.5%(1 分钟) | CRIT | 自动切换支付上游/提权 EF 配额 |
| Rank p99 > 400ms(3 分钟) | WARN | 排查队列/存储 |
| tc class 1:10/1:20 drop > 0(连续 1 分钟) | WARN | 增配 EF/AF31 上限,压 B 类 |
| 上游丢包 > 1.5%(连续 2 分钟) | WARN | 触发运营商路由首选切换 |
6. 压测与验证(上线前必须打透)
6.1 合成流量验证
# Payment 类(EF)
iperf3 -c <remote-ip> -p 14443 -t 30 -b 800M -P 8
# Ranking 类(AF31)
iperf3 -c <remote-ip> -p 14444 -t 30 -b 1200M -P 8
# Bulk
iperf3 -c <remote-ip> -p 14480 -t 30 -b 3G -P 10
同时跑三类,观察 tc -s class:是否达到 rate/ceil 预期;EF/AF31 有无丢包。
curl + --resolve + --data 做支付/排行接口的端到端延迟采样。
6.2 糟糕网络注入(netem)
# 在测试机上模拟东南亚 60ms RTT + 1% 丢包
tc qdisc add dev eth0 root netem delay 60ms 10ms loss 1%
# 回滚
tc qdisc del dev eth0 root
目标:验证 丢包+抖动 时 EF/AF31 仍能维持 p99 在 SLO 内。
7. 线上数据:改造前后对比
| 指标(香港出口) | 改造前 | 改造后(1 周均值) |
|---|---|---|
| Payment p99(ms) | 780 | 360 |
| Payment 超时率 | 1.8% | 0.28% |
| Rank 写入 p99(ms) | 640 | 270 |
| EF Class ECN 比例 | 0.0% | 0.6%(可控拥塞) |
| 上游退避/重传率 | 2.2% | 0.9% |
| Bulk 吞吐(Gbps) | 4.6(波动大) | 4.7(平滑) |
经验:EF/AF31 有了保底与上限之后,Bulk 反而更稳定,因为被“限速”的 Bulk 不再把上游缓冲打爆。
8. 线上“夜战”复盘:一次越南路由抖动
那天凌晨 03:00,越南区充值重试率飙升。mtr 指向我们到 VN-IX 的一段上游(运营商 A)丢包 3–5%。我做了三件事:
- 临时提权 EF:把 1:10 的 ceil 提到 2.5G,把 1:30(Bulk)ceil 降到 2.3G。
- 切换路由偏好:对运营商 A 附加 BGP Community,降低本段优先,临时偏向运营商 B(HKT)。
- 限流大文件回源:对 CDN 回源的 Bulk 增加连接数与速率限制(Nginx limit_rate_after/limit_rate),避免同窗竞争。
10 分钟内,支付回调超时跌回 0.3%。天亮后我们联系运营商调整了 A 线的拥塞点,晚高峰前再把 EF/AF31 的 ceil 恢复。
9. 常见坑与“避坑地图”
- DNAT/SNAT 之后丢了 connmark:记得在相应链路 --restore-mark。
- GRO/GSO 导致 qdisc 不“吃包”:出口延迟诡异上升,先关聚合功能对比。
- 只靠 DPI/SNI 分类:TLS 花样多,在生产上不可靠。我更推端口+上游 IP+本地 L4 代理。
- 整形速率设太“紧”:顶满承诺带时仍会抖,留 4–6% 余量是最佳实践。
- 只配 EF 不给上限:EF 一旦失控会“吃死”链路,务必配 ceil 与 fq_codel。
- 内核冒进:为 BBR 升核要全链路回归,驱动/安全产品齐测;否则 CUBIC + fq_codel 已很稳。
- 入口不控:上游灌爆你再控出口意义不大;必要时上 IFB 入整形,但要算 CPU 成本。
- 只看吞吐不看“尾延迟”:充值/排行要盯 p99/p999,吞吐漂亮不代表钱能进、分能写。
10. 运维手册(现场速查)
查看队列与分类:
tc -s qdisc show dev eth0
tc -s class show dev eth0
动态调配(夜间手动缓急):
# 抬高 Payment 上限到 2.5G
tc class change dev eth0 parent 1: classid 1:10 htb rate 900mbit ceil 2500mbit prio 0
# 压低 Bulk
tc class change dev eth0 parent 1: classid 1:30 htb rate 2500mbit ceil 2300mbit prio 2
快速验证 DSCP:
tcpdump -i eth0 -vvv -n "tcp port 14443" | egrep "tos 0xb8|DSCP"
回滚(全清):
tc qdisc del dev eth0 root
iptables -t mangle -F QOS_MARK
11. 扩展与进阶
- 跨云直连/DC(AWS/GCP/阿里/腾讯):给支付/排行加 Direct Connect/专线,在边界同样打 DSCP,维持端到端一致。
- 多活写入:香港—新加坡双活,写入幂等 + 延迟仲裁,QoS 策略两地一致。
- eBPF/XDP:高并发下用 eBPF 做轻量分类与指标采集,降低 iptables 开销。
- 队列自适应:基于实时 p95/p99 自动调整 ceil(Prometheus Alert → Webhook → Ansible/调度器)。
天快亮的时候,我站在机房玻璃窗前,看着对面的维港慢慢浮出一层鱼肚白。Grafana 的曲线终于平了下来,支付回调的 p99 稳在 360ms,排行写入的队列也像被人轻轻按住了“暂停键”。
我知道,玩家们醒来时,看到的不只是新的活动推送,还有那些不会丢的钻石和不会错的排名。
这套“香港大带宽 + QoS”的打法,不是什么高深学问,却是最讲究克制与秩序:
谁该先过,谁能多过,谁要少说话,都用规则写在网卡和队列里。
等下一次凌晨的告警来时,我也不过再把那杯冻柠茶换成热奶茶——然后,按下我们早就准备好的那几个脚本。