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

游戏出海中转:Anycast + GeoDNS + 智能回源在香港服务器上的一体化L4/L7限流

发布人:Minchunlin 发布时间:2025-09-24 19:07 阅读量:1103


凌晨 3:17,游戏群里炸锅。东南亚玩家延迟飙到 400ms、登录排队、交易接口被刷得喘不过气。报警把我从机房躺椅上拽醒,我端起昨晚没喝完的凉咖啡,一边把 BMC 打开看 CPU 温度,一边在交换机上敲命令确认上联没抖。那一刻我意识到:靠单点出口 + 传统 DNS 轮询,已经扛不住节日活动的把子火了。

接下来的两周,我把香港的这套“出海中转”重做了一遍:边缘 Anycast 承载接入、GeoDNS 精准首跳、智能回源跨区择优、配合 L4/L7 双栈限流与风控。这篇稿子是我从零到一落地的完整笔记,里面有我踩过的坑和当天切流的每一个参数。

1. 目标与整体设计

目标:

面向东亚/东南亚玩家的低时延接入(HK 作为中转 POP)。

多活(任一边缘节点下线不影响业务)。

可观测(全链路 RTT/Jitter/P99 可追)。

抗滥用(L4/L7 限流 + 行为风控)。

可演练(SOP + 自动回退)。

核心技术选型:

Anycast:边缘节点用 BGP 对外公告同一个服务 IP 段,让玩家就近接入。

GeoDNS:权威 DNS 按地理/运营商优先级指向最近/最稳的 Anycast POP。

智能回源:边缘对多地源站(Tokyo/Singapore/Frankfurt)做实时测量与加权一致性哈希,按用户维度稳定分流。

L4/L7 限流:L4 用 nftables + tc 做速率/并发护栏;L7(登录/支付/资产接口)用 NGINX+Lua+Redis 做令牌桶,配风控评分。

2. 机房与硬件清单(HK 单 POP 规模示例)

角色 数量 规格 关键点
边缘转发/代理节点(Edge) 4 AMD EPYC 7313P(16C/32T)/128GB RAM/2×NVMe 1.92TB(RAID1)/Mellanox ConnectX-5 2×25G 支持 XDP/DPDK,足够的 IRQ/RSS 队列
权威 DNS(GeoDNS) 2 Intel E-2278G / 32GB / 2×10G PowerDNS + GeoIP2,主主
Redis 限流存储 3 E-2278G / 64GB / NVMe / 10G Cluster 模式,副本因子 1
监控/日志 2 任意 x86 / 64GB / 大盘 Prometheus + VictoriaMetrics + Loki
ToR 交换机 2 48×25G + 6×100G MLAG/VSX,互联 2×100G
上联 2–3 HKT/PCCW/NTT(举例) 与各家 eBGP,收默认+区域前缀

操作系统:CentOS 7.9(内核建议换 ELRepo 5.4 LTS,XDP/封包能力稳)

时钟:Chrony 对齐(本地 PPS/上游 NTP 池),MaxSkew < 5ms。

盘用途:边缘本地 NVMe 只放 cache/落盘日志;配置与状态完全可重建。

3. 网络拓扑(文字版)

玩家 → 互联网 → [HK POP - Anycast VIP]
                         │  eBGP 多家上联
               ┌─────────┴─────────┐
           [Edge-1]  [Edge-2]  [Edge-3]  [Edge-4]   ← 4 台无状态边缘
               │          │         │         │
           (ToR MLAG 25G 接入)
               │
          上联骨干(HKT/PCCW/NTT)
               │
     智能回源:Tokyo / Singapore / Frankfurt 源站池


域名路径:玩家解析 game.example.com → GeoDNS 返回离玩家最近/质量最好的 Anycast VIP (A/AAAA) → BGP 把流量收敛到 HK POP 的就近 Edge → Edge 智能转发到最优源站。

4. Anycast:FRR + BGP 实战

前提:拿到可公告的 /24(或更大),对外分发同一 VIP(/32)由多台 Edge 同时公告。

/etc/frr/bgpd.conf(Edge 节点节选):

router bgp 65010
 bgp router-id 10.10.10.1
 no bgp default ipv4-unicast
 neighbor HKT peer-group
 neighbor HKT remote-as 9304
 neighbor HKT ebgp-multihop 2
 neighbor HKT timers 10 30
 neighbor 203.0.113.1 peer-group HKT
 !
 address-family ipv4 unicast
  network 203.0.113.100/32 route-map ANYCAST-ATTR
  neighbor HKT activate
  maximum-paths 4
 exit-address-family
!
route-map ANYCAST-ATTR permit 10
 set community 65010:100 additive
 set local-preference 200
 set metric 0
!

经验参数:

timers 10 30(抖动期可降到 3 9,稳定后回升)。

多上联开 ECMP,并控制 BGP community(对大陆回程可选 CN2/直连策略)。

有计划下线时打 Graceful Shutdown community(如 65535:0),避免黑洞。

坑 1: 某次 HKT 路由器升级导致我们被对端收得只剩默认路由,ECMP 失衡,PCCW 单边流量暴涨。
解法: 在上联 peer-group 上加 inbound prefix-list 与 as-path 过滤,确保前缀质量,同时 PromQL 对各 BGP 邻居的 bgp_routes_received 做差分告警。

5. GeoDNS:PowerDNS + GeoIP2

PowerDNS(Authoritative)配置要点:

GeoIP2 City/ASN 库,按 国家/AS 维度返回不同 Anycast VIP(多 POP 时用)。

HK 单 POP 场景也用它做运营商兜底与灰度。

示例(Lua Record):

-- /etc/powerdns/lua/geo.lua
function preresolve ( remoteip, domain, qtype )
  if domain ~= "game.example.com" or qtype ~= pdns.A then return false end
  local cc = geoip_lookup_country_code(remoteip)
  local asn = geoip_lookup_asn(remoteip)

  -- 中国大陆优先返回 HK Anycast VIP(CN2 回程更稳)
  if cc == "CN" then
    pdnslog("CN from " .. remoteip .. " ASN " .. asn)
    pdns.replace({{qtype=pdns.A, content="203.0.113.100", ttl=30}})
    return true
  end

  -- 东南亚 => 仍指向 HK(示意),多 POP 时可返回 SG/HK 混合
  if cc == "SG" or cc == "MY" or cc == "TH" or cc == "PH" or cc=="VN" then
    pdns.replace({{qtype=pdns.A, content="203.0.113.100", ttl=30}})
    return true
  end

  -- 兜底
  pdns.replace({{qtype=pdns.A, content="203.0.113.100", ttl=30}})
  return true
end

TTL 建议:活动期 A 记录 TTL=30s,平时 300–600s。

坑 2: MaxMind GeoIP 数据延迟导致新兴 ASN 识别错误。解法:关键国家/AS 做手工白名单优先级覆盖;并在日志侧抽样审计(DNS Query → 实际源 IP RTT)。

6. 智能回源:IPVS(UDP/TCP) + Envoy(HTTP/QUIC) 一致性哈希

我们的业务包含 游戏长连接(TCP/UDP) 和 HTTP 登录/支付 两类。

6.1 L4(TCP/UDP)用 IPVS

UDP/TCP 都可代理,开 sh(source-hash) 保持会话亲和;对源站定义权重由实时质量(RTT/Jitter/丢包)驱动。

健康检查用 keepalived 或自写 agent 打点。

示例:

# 建立虚拟服务(监听 Anycast VIP 上的 7001 端口,TCP)
ipvsadm -A -t 203.0.113.100:7001 -s sh
ipvsadm -a -t 203.0.113.100:7001 -r 198.51.100.10:7001 -g -w 5   # Tokyo
ipvsadm -a -t 203.0.113.100:7001 -r 203.0.113.10:7001 -g -w 3   # Singapore

# UDP 服(战斗服)
ipvsadm -A -u 203.0.113.100:9000 -s sh
ipvsadm -a -u 203.0.113.100:9000 -r 198.51.100.20:9000 -g -w 4
ipvsadm -a -u 203.0.113.100:9000 -r 203.0.113.20:9000 -g -w 2

-g 为 Direct Routing(DR);若跨网段用 -m(NAT)。我们 HK→Tokyo/SG 走公网,实际采用 NAT(-m)并在边缘开 SNAT 池避免端口耗尽。

权重更新:通过一个侧车 agent 每 5s 采集到各源站的 RTT/Jitter(TCP SYN/UDP echo),把分数写进本地 /run/ipvs-weights.json,由守护进程增量 ipvsadm -e。
坑 3: UDP NAT 端口耗尽。解法:/proc/sys/net/ipv4/ip_local_port_range 扩到 1024 65535,多 SNAT 源地址池,配合 conntrack 参数(见第 8 节)。

6.2 L7(HTTP/QUIC)用 Envoy Ring Hash

登录/充值类接口我们走 Envoy,拿 Header(X-UID)/Cookie 做 Key,算法用 maglev。

Envoy 路由片段:

load_balancing_policy:
  policies:
  - ring_hash:
      minimum_ring_size: 4096
      hash_function: MAGLEV
      consistent_hashing_lb_config:
        use_hostname_for_hashing: false

回源候选:Tokyo / Singapore / Frankfurt,按实时质量调整 endpoints.weight。

坑 4: 源站证书 OCSP 阻塞导致 TLS 建链尾部长尾。解法:Envoy 开 OCSP stapling 与证书缓存,并在边缘就地启用 session resumption。

7. L4 限流与防护:nftables + tc 护栏

目标:防 SYN 洪水/UDP 洪水、控制单 IP/前缀突刺、保护 conntrack。

/etc/nftables.conf(节选):

table inet filter {
  set trusted_asn { type inet_service; flags interval; }
  chain input {
    type filter hook input priority 0;
    ct state invalid drop

    # SYN proxy for new connections on TCP ports
    tcp flags syn tcp option maxseg size 1-65535 ct state new \
      meter synprotect { ip saddr limit rate over 200/second burst 400 packets } \
      accept

    # 每 IP 基础并发护栏(示意:TCP 7001)
    tcp dport 7001 ct state new \
      meter per_ip_7001 { ip saddr limit rate over 50/second burst 100 packets } \
      drop

    # UDP 每 /24 限速
    udp dport 9000 meter u9000 {/24 prefix: ip saddr limit rate over 2k/second burst 5k packets} drop
  }
}

tc 警戒线(根队列 + policer):

tc qdisc add dev eth0 root handle 1: htb default 10
tc class add dev eth0 parent 1: classid 1:10 htb rate 20gbit ceil 20gbit
# 攻击期对目的端口 9000 增加保守 policer
tc filter add dev eth0 protocol ip parent 1: prio 1 u32 \
  match ip dport 9000 0xffff \
  police rate 10gbit burst 5m drop flowid :1

坑 5: nftables set 规模过大(热键/恶 IP)导致评估退化。解法:热键放 ebpf/xdp 快速丢弃;次热键仍由 nft 管。

8. L7 限流 + 风控:NGINX + Lua + Redis

策略:按 用户 ID / 设备指纹 / IP 组合做令牌桶;风控评分(UA/JA3/失败率/时段/ASN)动态调节 burst。

NGINX 片段:

lua_shared_dict limits 100m;

init_by_lua_block {
  local redis = require "resty.redis"
  -- 连接池等初始化
}

server {
  listen 443 ssl http2;
  server_name api.example.com;

  set $uid $http_x_uid;
  if ($uid = "") { set $uid $remote_addr; }  # 兜底

  access_by_lua_block {
    local limiter = require "limit_token_bucket"
    local key = ngx.var.uid
    local allowed, wait = limiter.consume(key, 10, 30)  -- rate=10/s, burst=30
    if not allowed then
      ngx.status = 429
      ngx.say('{"code":429,"msg":"Too Many Requests"}')
      return ngx.exit(429)
    end
  }

  location /login {
    proxy_pass http://login_pool;
  }
}

Redis(Cluster)建议:

3 主 3 从,maxmemory-policy allkeys-lru,慢查询阈值 5ms。

限流 key 过期 60–120s,避免冷启动“爆发”。

坑 6: Redis p99 抖高导致“误伤”。解法:令牌桶逻辑本地 Lua shared dict 预扣,再异步补库存;Redis 仅做跨进程一致性校准。高风险接口做两级限流(本地 429、网关 302 到静态降级页)。

9. 内核与网卡调优(关键参数表)

参数 建议值 说明
net.core.somaxconn 16384+ listen 队列
net.ipv4.tcp_max_syn_backlog 262144 SYN 队列
net.ipv4.tcp_syncookies 1
net.netfilter.nf_conntrack_max 8,000,000(按内存评估) 连接跟踪
net.ipv4.ip_local_port_range 1024 65535 NAT 端口池
net.core.rmem_max/wmem_max 134217728 套接字缓冲
fs.file-max 10,000,000 fd 上限
NIC RX/TX ring 4096/4096 ethtool -G
IRQ 绑核 irqbalance --ban + 手动 pin 保证 NUMA 亲和
GRO/LRO 关 LRO,GRO 视流量 UDP 场景谨慎开

脚本要点:

开机根据 lspci 将 mlx5 队列均分到独立核,RPS/XPS 与队列一致。

sysctl -p 后跑 ss -s、nstat、ethtool -S 验证。

10. 部署步骤(我当时的顺序)

拉业务影子流量到新 POP(5%),灰度 48 小时。

部署 FRR,与三家上联建邻,观察路由收敛与回程 AS Path。

上线 GeoDNS(PDNS),TTL=300,抽样验证客户端 IP→POP 命中率。

Edge 上线 IPVS/Envoy,只接登录只读接口,验证一致性哈希与粘性。

开 L4/L7 限流 在可观测模式(仅打标不拦截),收集一周画像。

限流切到强制模式(逐接口),并配 风控白名单。

演练(见第 12 节),通过后放量到 100%。

11. 观测与告警(关键指标)

模块 指标 阈值/说明
BGP Established 保持时长、flaps 任意邻居 flap>2/5m 告警
DNS QPS、解析成功率、地区命中率 命中率低于 90% 告
Edge NIC rx_no_buffer, rx_dropped 连续 1min > 0 告
L4 conntrack count / max >80% 告,>90% 触发丢弃策略
L7 2xx/4xx/5xx 比例、429 比例 429 > 1% 检查策略
回源 源站 RTT/Jitter/P95 触发权重调整
Redis p99、连接数、rejected_connections p99>5ms 告
用户 登录 P95、战斗服抖动 目标 HK→Tokyo < 60ms

12. 风控与演练清单(SOP)

攻防策略:

高速口子(UDP 9000)只对合法握手流量开放,随机端口 + 首包签名校验(无签名直接丢)。

登录口按 ASN/国家 设不同 burst,对“黑产 ASN”强限。

JA3/UA 指纹异常 + 失败率拉高 → 临时降权甚至黑洞(短时)。

演练(季度至少 1 次):

单上联 BGP 断链:验证 Anycast 回收、DNS 命中率无抖。

某源站不可达:IPVS/Envoy 10 秒内权重调零;会话复用不炸。

Redis 半残:限流退化到本地 shared dict;成功率下降 <1%。

Conntrack 溢出:触发自动采样丢弃 & per-/24 限速。

日志盘打满:Loki/FluentD 退化,边缘仅保最近 2h。

整机下电:BGP 优雅下线 + 接口抢占互斥有效。

DNS 被劫持(演练环境):客户端回落到 Anycast 备用域名。

演练记录表(摘录):

演练项 触发方式 预期行为 实际结果 回归修复
上联断链 ip link set/BGP neighbor shutdown 30s 内路由收敛 28s 通过
源站失效 防火墙丢弃 Healthcheck 10s 内权重=0 9s 通过
Redis 降级 tc 注入 20ms 延迟 本地限流接管 正常 调整 p99 告警阈值

13. 线上“坑”与现场解决

ECMP 粘性被打散:某上联修改哈希算法,长连接掉线。→ 在我们侧 ipvs sh + 源地址打标固定,BGP 不再开 maximum-paths 对该邻居。

MTU 陷阱:回源路径 1500/1492 混杂,QUIC 丢包明显。→ 全链路 PMTUD + 业务端 max_udp_payload_size=1200。

iptables 锁竞争:早期脚本仍动 iptables,高 QPS 下加规则阻塞。→ 全面迁移 nftables。

Redis 热点 Key:活动开场 login:uid:* 热点,p999 突刺。→ 二级 Key + 本地预扣 + 限流 Key 分片。

MaxMind 识别错误:新 AS 走了远路。→ 手工白名单 + 每周自动对账(解析结果 vs RTT)。

14. 关键配置清单(可直接套)

sysctl:

cat >/etc/sysctl.d/99-tuning.conf <<'EOF'
fs.file-max = 10000000
net.core.somaxconn = 16384
net.core.netdev_max_backlog = 250000
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_syncookies = 1
net.ipv4.ip_local_port_range = 1024 65535
net.netfilter.nf_conntrack_max = 8000000
net.ipv4.tcp_tw_reuse = 1
EOF
sysctl --system

FRR(bgpd):见第 4 节。
PowerDNS(Lua):见第 5 节。
IPVS:见第 6.1 节。
Envoy:见第 6.2 节。
nftables/tc:见第 7 节。
NGINX + Lua:见第 8 节。

15. 成本与容量规划(经验值)

QPS/连接规模 机器数 备注
L4(UDP 9000) 300–500 万 pps 4 单台 25G NIC,irq 绑核后可稳 120–150 万 pps
L4(TCP 7001) 25–40 万并发 4 conntrack 需 8M 级
L7(登录/支付) 5–8 万 QPS 2–3 Envoy+NGINX,命中缓存比例高
Redis 限流 30–50 万 ops 3 主 3 从 p99 < 3–5ms

16.活动夜的 0 点到 3 点

大促当天 00:00 切主,会场流量像开闸。bgp_neighbors_established 一直稳在 3,Edge 的 rx_dropped 为 0;Tokyo 源站 RTT P95 42ms,Singapore 做备。凌晨 1 点开始有一波 UDP 污点流量,nft 的 per-/24 限速把高峰削平,429 的比例始终 <0.3%。
2:10 Redis p99 抬头,我们自动切回“本地预扣”模式,成功率没掉。3:05 运营在群里发了句“这次很安静”,我把凉咖啡扔了,去楼下买了碗云吞面。
技术人的成就感就是这样:把复杂的系统揉到玩家感觉不到的顺滑里。

目录结构
全文