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

香港、新加坡、广州三地多活实战:Anycast+BGP、智能DNS与负载均衡优化跨境网络延迟

发布人:Minchunlin 发布时间:2025-08-17 10:55 阅读量:1888


那天夜里,机房的空调送风和交换机风扇像合唱团。我盯着广州用户的 p95 时延——186ms,抖得像心电图。白天业务量一上来,偶发超时、重试成风暴,订单回写延迟飙升。我们已经把单点优化做满了(内核、Netplan、队列、BBR),但跨境距离和路径选择还在拖后腿。

我决定上“三板斧”:Anycast 兜底、智能 DNS 精确导流、L4/L7 负载均衡细粒度调度,把跨境路径和会话稳定性一起拿下。

1. 目标与基线

SLO(以华南用户为主)

  • p95 时延:≤120ms(静态内容 ≤80ms,动态 API ≤120ms)
  • 丢包率:≤0.2%
  • 故障切走:≤30s(优先 Anycast / BFD / BGP 收敛;DNS 作为兜底)

基线(优化前)——合成探测(广州、深圳、东莞、福州节点)、真实用户监控(RUM)与黑盒探针(blackbox_exporter)混合:

区域来路 去向机房 p50 p95 丢包 备注
华南(电信) 香港 98ms 186ms 0.9% 路由绕转、拥塞时跳到非 CN2
华南(联通) 新加坡 162ms 228ms 1.4% 晚高峰波动明显
华东(移动) 香港 132ms 190ms 0.7% 途经多跳,回程不一致
华南(混合) 广州 38ms 72ms 0.2% 本地直达好,但容量有限,故障回切慢

2. 拓扑、硬件与角色分工

数据中心与角色

  • 香港(HK):主外服流量入口之一;对接国际出口+CN2/精品网,向北回程相对更稳。
  • 新加坡(SG):东南亚与备份北向入口;承担海外加速与容灾。
  • 广州(GZ):面向华南的计算/缓存与内陆直达节点;对外发布谨慎(合规与路径稳定考虑),主要承接近源读写与边缘逻辑。

硬件(每地节选)

角色 规格
边界路由(2 台) x86_64(Intel Xeon Silver)、128G 内存、NVMe 1.92T、双口 25GbE(Mellanox CX4-Lx),跑 FRR 8.x
L4/L7 网关(2 台) 32C/64G、NVMe、2×25GbE,跑 HAProxy 2.8+ / NginxKeepalived
应用/缓存集群 k8s/裸机混部,节点 10~30 台/点位
ToR & 汇聚 25G 接入、100G 上联(ECMP),BFD 开启
时钟 PTP(机柜内)、NTP(外部 2 源+内部 1 源)

3. Anycast:用 BGP 把“最近”送到用户手边

3.1 策略

申请到一段专用公网前缀(示例用保留网段演示):203.0.113.0/24 作为Anycast 服务段。

香港与新加坡向上游发布 /24;广州不对公网发布 /24(合规/稳定性考虑),但参与东西向互联与容灾。

健康驱动撤布:当本地 L7 网关异常,撤销本地 BGP 宣告,让流量自然飘向其它点位。

路径意图:让华南电信/联通更倾向走香港,海外/东南亚更多落在新加坡。通过 AS-Path Prepend、MED、社区标记 做软引导,不做“硬钉死”。

3.2 FRR(bgpd)核心配置示例(香港边界路由)

说明:下方配置以两家上游 ISP为例,使用 BFD 加速失连感知;通过条件广告与健康脚本控制发布。

# /etc/frr/daemons
bgpd=yes
bfdd=yes

# /etc/frr/bgpd.conf (HK)
router bgp 65010
 bgp router-id 10.0.0.11
 no bgp default ipv4-unicast
 neighbor uplink1 peer-group
 neighbor uplink2 peer-group
 neighbor 203.0.113.254 remote-as 64512
 neighbor 203.0.113.254 peer-group uplink1
 neighbor 198.51.100.254 remote-as 64513
 neighbor 198.51.100.254 peer-group uplink2

 ! BFD 绑定
 neighbor uplink1 bfd
 neighbor uplink2 bfd

 ! 出口策略(软引导北向更偏好HK)
 route-map OUT-HK permit 10
  set community 65010:100 additive
  set metric 50

 address-family ipv4 unicast
  network 203.0.113.0/24 route-map ANNOUNCE-WHEN-HEALTHY
  neighbor uplink1 activate
  neighbor uplink1 route-map OUT-HK out
  neighbor uplink2 activate
  neighbor uplink2 route-map OUT-HK out
 exit-address-family
!
ip prefix-list ANYCAST-SIGNAL seq 5 permit 10.255.0.1/32
route-map ANNOUNCE-WHEN-HEALTHY permit 10
 match ip address prefix-list ANYCAST-SIGNAL

健康脚本(当本地 L7 正常时,保持信号路由;异常时删除,从而停止宣告):

# /usr/local/bin/anycast-health.sh
#!/usr/bin/env bash
VIP_CHECK_URL="http://127.0.0.1:8404/ready"   # HAProxy / Nginx 本地健康端点
SIGNAL_PREFIX="10.255.0.1/32"

if curl -s --max-time 1 "$VIP_CHECK_URL" | grep -q OK; then
  ip route show $SIGNAL_PREFIX >/dev/null 2>&1 || ip route add $SIGNAL_PREFIX dev lo
else
  ip route del $SIGNAL_PREFIX >/dev/null 2>&1
fi

Crontab/系统定时器每秒探测一次:

* * * * * root /usr/local/bin/anycast-health.sh >/dev/null 2>&1
* * * * * root sleep 1; /usr/local/bin/anycast-health.sh >/dev/null 2>&1
* * * * * root sleep 2; /usr/local/bin/anycast-health.sh >/dev/null 2>&1
...

新加坡侧可做AS-Path Prepend增加 2~3 次,以便华南更偏向香港:

# /etc/frr/bgpd.conf (SG) 片段
route-map OUT-SG permit 10
 set as-path prepend 65020 65020 65020
 set community 65020:200 additive
 set metric 150

实战体会:不要频繁撤布。有一次香港 L7 切滚动升级把健康脚本设置为每 100ms 执行,导致 BGP 一直抖,上游触发了flap dampening,恢复慢得想哭。后来我们把探测颗粒度放到 1s,并引入抖动抑制窗口(3 连续失败才撤布)。

4. 智能 DNS(GSLB):给长连接和非 Anycast 业务兜底

为什么还要 DNS?

Anycast对短连接/幂等非常友好,但对长连接(如 WebSocket、部分 gRPC 流)在单点故障的“连接寿命”里难以立即迁移。

DNS 可以按地理/网络/实时延迟下发更适合的Unicast VIP,并配合短 TTL 做分钟级收敛。

我用了一套自管 CoreDNS(边缘也可以上云厂商 GSLB),做了:GeoIP 分流 → 延迟探测打分 → 健康检查 → 权重回收。

CoreDNS 配置(简化示例)

# /etc/coredns/Corefile
example.com:53 {
  log
  errors
  health
  cache 30

  # 来自探针的权重文件(外部生成)
  rewrite name weight.hk.example.com hk-lb.example.com
  rewrite name weight.sg.example.com sg-lb.example.com
  rewrite name weight.gz.example.com gz-lb.example.com

  # GeoIP 分流(按大洲/国家/AS 号粗分)
  geoip /etc/coredns/GeoLite2-City.mmdb {
    # 中国南方 → 广州优先,其次香港
    1.0.0.0/8 gz-lb.example.com
    CN hk-lb.example.com
    SG sg-lb.example.com
    fallback hk-lb.example.com
  }

  forward . 8.8.8.8 1.1.1.1
}

权重/健康来源:

我跑了黑盒探针(广州/深圳/东莞/福州/厦门/汕头)每 10s 打点 tcp_connect 与 http_2xx,把移动/联通/电信分开统计,然后写到一个权重文件,被 CoreDNS 的 rewrite 读。

TTL 控制在 20~30s。一次教训:TTL 设 5s 看起来很敏捷,但上游递归解析器会合并/重试,实际未必更快,反而抖动大。

5. L4/L7 负载均衡:连接亲和与退避逻辑

5.1 Keepalived(同城 2 节点 VRRP)

# /etc/keepalived/keepalived.conf (HK)
vrrp_instance VI_1 {
  state MASTER
  interface eno1
  virtual_router_id 51
  priority 110
  advert_int 1
  virtual_ipaddress {
    203.0.113.10/24 dev eno1 label eno1:vip
  }
  track_script {
    chk_haproxy
  }
}

vrrp_script chk_haproxy {
  script "/usr/local/bin/check_haproxy.sh"
  interval 1
  fall 3
  rise 2
}

check_haproxy.sh

#!/usr/bin/env bash
curl -s --max-time 1 http://127.0.0.1:8404/ready | grep -q OK

5.2 HAProxy(L4/L7 混部关键片段)

连接亲和:对会话/用户保持源地址一致性,降低跨数据中心“来回漂移”。

重试与退避:对长尾超时做指数回退,避免雪崩。

# /etc/haproxy/haproxy.cfg
global
  nbthread 8
  tune.ssl.default-dh-param 2048
  log 127.0.0.1 local0

defaults
  mode http
  option httplog
  option http-keep-alive
  timeout connect 2s
  timeout client  60s
  timeout server  60s
  retries 2

frontend fe_web
  bind 203.0.113.10:80
  bind 203.0.113.10:443 ssl crt /etc/ssl/acme/example.com/fullchain.pem
  http-response set-header X-DC hk
  default_backend be_api

backend be_api
  balance hdr(X-User-Id) # 也可 sticky on src,一致性更强用 consistent-hash
  hash-type consistent
  option httpchk GET /health
  http-check expect status 200
  server gz-1 10.10.20.11:8080 check inter 1000 rise 2 fall 3
  server gz-2 10.10.20.12:8080 check inter 1000 rise 2 fall 3
  server hk-1 10.10.10.11:8080 check backup
  server sg-1 10.10.30.11:8080 check backup

backend be_static
  balance uri
  server hk-cdn 10.10.10.21:80 check
  server sg-cdn 10.10.30.21:80 check backup

经验:consistent-hash 对“社交/交易类”会话非常关键;而backup 节点能在单城抖动时少量兜底,不至于全城黑暗。

6. 东西向专线/隧道:把三地粘成一块

WireGuard 做控制面与数据面的加密隧道(跨公网/专线混合),MTU 1420 起步,逐步探测升至 1420~1440。

ECMP 两条上行(例:运营商 A/B),BFD 300ms 间隔快速探活。

广州→香港/新加坡走私网回源;香港/新加坡回写广州的强一致操作走消息队列+双写日志,避免直写抖动。

WireGuard(HK 到 GZ)

# /etc/wireguard/wg0.conf (HK)
[Interface]
Address = 172.16.0.1/30
ListenPort = 51820
PrivateKey = <HK_PRIVATE>
MTU = 1420

[Peer]
PublicKey = <GZ_PUBLIC>
AllowedIPs = 172.16.0.2/32, 10.10.20.0/24
Endpoint = gz-edge.example.com:51820
PersistentKeepalive = 15

策略路由(对回源/数据复制打 Tag)

ip rule add from 10.10.10.0/24 table 100
ip route add default dev wg0 table 100

7. 观测与验收:把“感觉快”变成数字

PromQL(p95 延迟)

histogram_quantile(0.95, sum by (le,region,isp) (rate(http_request_duration_seconds_bucket{job="edge",status=~"2.."}[5m])))

收敛时间:

  • BFD 失连探测:~0.3s
  • BGP 邻居撤布到上游收敛:5~12s(视供应商)
  • DNS TTL:20~30s(递归差异会拖长到 40~60s)

上线后一周汇总(节选):

来路 Anycast 命中 GSLB 命中 p50 p95 丢包
华南(电信)→HK 78% 22% 62ms 108ms 0.18%
华南(联通)→HK 71% 29% 68ms 115ms 0.21%
华南(混合)→GZ 12% 88% 34ms 66ms 0.12%
东南亚→SG 92% 8% 38ms 72ms 0.09%

关键结果:华南主业务 p95 从 186ms → 108~115ms,稳定在 SLO 之内;广州本地读取/缓存命中场景 p95 66ms。

8. 故障演练回放:拔掉新加坡一根“网线”

场景:SG 边界路由 uplink2 人为 down。

  • t+0.3s:BFD 探测失败
  • t+1.2s:BGP 邻接撤销,/24 从 uplink2 不再发布
  • t+7s:外部路径收敛完成,Anycast 流量回到 uplink1
  • t+9s:长连接少量超时,自动重连
  • t+30s:DNS 自愈覆盖尾巴群体

用户可感:个别地区 1020s 内出现一次性 200600ms 的尾延迟峰值,无持续报错

9. 我踩过的坑与解法

BGP 抖动被上游拉黑

症状:频繁撤布/发布,路由被“抑制”。

解决:健康脚本增加抖动抑制窗口(3 连续失败才撤)、最小持续时间 30s,且灰度发布(分上游逐个恢复)。

Anycast 与长连接的“背刺”

症状:单点 L7 掉线,存量长连接在超时前不会迁移。

解决:重要长连接走 GSLB Unicast,并在客户端层做心跳+快速重连;服务器侧 idle_timeout 控制在 60s 内。

ECN/DSCP 被清洗

跨公网常被中间路由器清理,无法依赖端到端 QoS。

解决:不把 SLO 押宝在 QoS 标记,更依赖路径选择与容量。

WireGuard MTU 黑洞

一开始贪心设到 1440,部分链路 ICMP 不通。

解决:从 1420 起步,配合 tcp_mtu_probing=1,逐步放大到稳定阈值。

DNS TTL 过短引发缓存行为不可预期

5s TTL 并未带来线性收敛提升,反而使递归合并与突发抖动更频繁。

解决:回到 20~30s,并对关键客户提供备用域名手工回切方案。

监控“看起来都绿”但用户还卡

症状:探针都走 CN2,用户却绕大圈。

解决:多 AS/多省份的探针,RUM 采样扩大;同时对 回程路径做被动采样(tcpinfo RTT/RTTVar)与 mtr 周期巡检。

10. 上线清单(我真的一条条勾过)

  1.  前缀路由策略与上游对齐(社区、MED、AS Path 预置)
  2.  BFD/BGP 计时调优(抖动抑制阈值)
  3.  Anycast 健康脚本幂等与灰度撤布
  4.  GSLB 探针覆盖(按省/AS/出口)与 TTL 设定
  5.  L4/L7 亲和策略(consistent-hash / sticky-table)
  6.  东西向链路 MTU & ECMP 验证
  7.  RUM 采样比例 & 黑盒探针上报去重
  8.  演练:单上游 Down、单 DC L7 Down、DNS 只读、证书过期
  9.  回滚:单键禁用 Anycast、强制权重到香港/广州/新加坡
  10.  预案通讯录:上游 NOC、机房值班、内部群

11. 结语:把复杂性“关在机房里”

做完这一切,我最满意的不是 p95 从 186ms 拉到 108ms,而是用户不再需要关心我们在背后做了什么。
Anycast、GSLB、L4/L7、ECMP、BFD、WireGuard……这些名词最后都只剩下一个结果:打开就快、错了能挪、宕了能活。
凌晨三点的演练结束时,指示灯还是那么多,但我知道,复杂性被我们关在了机房里,而不是摊在用户面前。

附:关键系统参数(示例)

# 网络缓冲(边界/LB 节点)
sysctl -w net.core.rmem_max=134217728
sysctl -w net.core.wmem_max=134217728
sysctl -w net.core.netdev_max_backlog=250000
sysctl -w net.ipv4.tcp_rmem="4096 87380 134217728"
sysctl -w net.ipv4.tcp_wmem="4096 65536 134217728"
sysctl -w net.ipv4.tcp_congestion_control=bbr
sysctl -w net.ipv4.tcp_mtu_probing=1
目录结构
全文