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

跨境电商的双11:香港机房多活与峰值削峰,BGP Anycast + CDN 预热 + Redis 热点隔离

发布人:Minchunlin 发布时间:2025-09-24 16:03 阅读量:830


去年双11凌晨 00:03 分,我们的购物车接口被一波「秒杀+黄牛」打穿,Redis 热点键像一颗微缩的恒星,疯狂坍缩把线程都拽进去;北向链路的 BGP 会话因为健康检查迟缓,Anycast 没及时撤宣,部分用户绕了半个地球。凌晨机房的冷风从走线孔里灌进来,我拎着手电,盯着机柜门上的冷凝水珠,耳机里轮番响的是 Slack 的 P1 告警和清洗厂商的回铃。

今年我不想再赌。于是有了这篇实战稿:一套我亲手在香港机房落地的多活架构与峰值削峰方案,包含硬件配置、网络拓扑、参数、风控与演练清单,也包括那些只会写在值班手册边角的「坑与解法」。

目标与指标

  • 峰值 QPS(入口层):1.2M QPS(含静态/动静混合),目标 95% 命中 CDN。
  • 应用层(核心写接口)稳态 85k QPS,峰值 150k QPS,P99 < 120 ms。
  • 交易失败率 < 0.15%;风控拦截准确率 > 97%(人工复核样本)。
  • 弹性回退:3 分钟内切出降级策略,30 秒内完成 BGP 撤宣与灰度恢复。
  • 变更窗口:T-7 至 T 日 00:00 冻结高风险变更;T 日只允许白名单开关。

1. 拓扑鸟瞰(香港双可用区 + 广州冷备)

拓扑:
                   ┌──────────────── CDN/Edge (多厂商) ────────────────┐


公网用户 → Anycast VIP (203.0.113.0/24) →  边缘 POP  →  原站回源 (东亚最优)
                   └──────────────────────────────────────────────────┘
                               │ BGP Anycast(多运营商)
                         ┌──────┴───────────┐
                         │                  │
                    HK-A 可用区         HK-B 可用区
                  (新田/荃湾)          (葵涌/将军澳)
                 ┌──────────┐         ┌──────────┐
入口LB (Envoy/Nginx)   接入交换      入口LB (Envoy/Nginx)   接入交换
+ eBPF/XDP抗SYN        TOR 100G     + eBPF/XDP抗SYN        TOR 100G
                 └──────────┘         └──────────┘
                        │ L3/L4/L7 负载均衡 (一致性哈希/熔断/限流)
                        ▼
                 ┌───────────────────────────────────┐
                 │  应用层(无状态)K8s/裸金属混部   │
                 │  - checkout / cart / sku / auth  │
                 └───────────────────────────────────┘
                        │
                        ▼
             ┌───────────────┐        ┌──────────────────┐
             │ Redis 集群(冷/热池) │ ←→  消息队列(Kafka) │  ←→ 异步削峰
             └───────────────┘        └──────────────────┘
                        │
                        ▼
               MySQL 主从 + Binlog CDC → ClickHouse 实时分析
                        │
                        ▼
                   广州冷备站(跨域读库/对象存储)- 断路器级别切换

2. 硬件与机房落地清单(我真买了这些)

2.1 机柜与供配电

规格/参数 备注
机柜 42U,双路 32A C13/C19 两可用区各 3 柜
冷通道 封闭冷通道,地板下走线 热点机柜前置空调风墙
带宽 HKIX/多运营商混接,2×100G 上联 各区独立上联,BFD

2.2 网络设备

角色 型号/配置 用途
边界路由 Juniper MX204(2×100G) 运行 FRR/原生 BGP,Anycast 宣告
TOR 交换 Arista 7050X/Juniper QFX5120(48×25G + 6×100G) 接入与汇聚
防火墙 FortiGate 2000E(HA) 东西向 ACL,DDoS 信令协同

2.3 服务器(CentOS 7.9,统一装 ELRepo kernel-ml 5.4.x 便于 eBPF)

角色 型号 CPU/RAM/NIC 存储
入口 LB Dell R650 2×Xeon 8358,256G,2×100G 2×480G SSD (RAID1)
应用 Dell R7525 2×EPYC 7543,512G,2×25G 2×960G SSD (RAID1)
Redis 冷池 Dell R650 1×Xeon 6348,256G,2×25G 4×3.2TB NVMe
Redis 热池 Dell R750 2×Xeon 8368,768G,2×100G 6×3.84TB NVMe
MySQL Dell R750 2×EPYC 7543,512G,2×25G 8×3.84TB NVMe(RAID10)
Kafka Dell R650 1×Xeon 6338,256G,2×25G 6×3.84TB NVMe(JBOD)

3. BGP Anycast:宣告、检测与撤宣

3.1 地址与策略

  • Anycast 前缀:203.0.113.0/24(仅示例),各区宣告同一 /24,入口 VIP:203.0.113.10(HTTP/HTTPS)。
  • 每区两家运营商(如 HKT、PCCW),MED/LocalPref 控制就近。
  • BFD 300ms × 3,health-check + ip route replace 降级撤宣。

3.2 FRR bgpd.conf 片段(边界路由)

router bgp 65001
 bgp router-id 203.0.113.2
 neighbor 10.10.10.1 remote-as 100  # HKT
 neighbor 10.10.20.1 remote-as 200  # PCCW
 neighbor 10.10.10.1 timers 5 15
 neighbor 10.10.20.1 timers 5 15
 neighbor 10.10.10.1 bfd
 neighbor 10.10.20.1 bfd

 address-family ipv4 unicast
  network 203.0.113.0/24
  neighbor 10.10.10.1 route-map OUT out
  neighbor 10.10.20.1 route-map OUT out
 exit-address-family

route-map OUT permit 10
 set community 65001:100 additive
 set local-preference 200

3.3 健康检查联动撤宣(systemd + 脚本)

逻辑:当入口 LB 健康探测失败 **≥3 次/**30s 或上游清洗厂商回调 attack=severe,执行 vtysh -c 'conf t' -c 'router bgp 65001' -c 'no network 203.0.113.0/24',并在对侧 AZ 提高 LocalPref。

#!/bin/bash
FAIL=$(curl -s -m2 http://127.0.0.1:9000/health | jq -r .pass)
DDoS=$(curl -s -m2 http://127.0.0.1:9100/ddos_state | jq -r .level)
if [[ "$FAIL" != "true" || "$DDoS" == "severe" ]]; then
  logger -t anycast "withdrawing /24 due to health:$FAIL ddos:$DDoS"
  vtysh -c 'conf t' -c 'router bgp 65001' -c 'no network 203.0.113.0/24'
fi

4. CDN 预热与源站策略(命中率就是生命线)

4.1 预热清单(T-3 小时启动,T-30 分钟复核)

类目 路径/Pattern TTL 备注
静态资源 /assets/*.{js,css} 7d 强缓存 + 版本号
商品图 /img/sku/** 3d 大图+缩略图变体
活动页 /promo/2025/1111/* 1h 多语言变体
API GET /api/v1/sku?id=* 60s 可缓存+签名忽略

4.2 预热脚本(多厂商 API 抽象)

# urls.txt 预生成(TopN SKU、活动页、静态清单等)
xargs -I{} -P16 bash -c '
  curl -s -XPOST "https://edgeA.example.com/prefetch" -d "url={}" >/dev/null
  curl -s -XPOST "https://edgeB.example.com/prefetch" -d "url={}" >/dev/null
' < urls.txt

4.3 源站 Cache 与签名回源

Nginx 原站:

proxy_cache_path /var/cache/nginx keys_zone=STATIC:10g max_size=100g inactive=1h;
map $request_uri $cache_key { default $request_uri; }
server {
  listen 443 ssl http2;
  location /assets/ {
    proxy_cache STATIC;
    proxy_ignore_headers Set-Cookie;
    add_header X-Cache $upstream_cache_status;
    proxy_pass http://app_pool;
  }
  # 对 GET 接口允许缓存(鉴权签名从 Vary 中剥离)
  location /api/v1/sku {
    proxy_cache STATIC;
    proxy_cache_valid 200 60s;
    proxy_hide_header Set-Cookie;
    proxy_ignore_headers Cache-Control Expires;
    proxy_pass http://app_pool;
  }
}

5. 入口层削峰(令牌桶 + 排队 + 断路器)

Envoy 限流(关键接口如 POST /checkout):

rate_limit:
  domain: "checkout"
  descriptors:
  - entries:
    - key: remote_address
    timeout: 0.02s
    token_bucket:
      max_tokens: 2000
      tokens_per_fill: 2000
      fill_interval: 1s

同时在 Nginx 层为长尾接口开 漏斗队列(超时丢弃):

limit_req_zone $binary_remote_addr zone=one:10m rate=50r/s;
location /cart/update {
  limit_req zone=one burst=200 nodelay;
  proxy_connect_timeout 1s;
  proxy_read_timeout 2s;
}

6. Redis:热点识别与隔离(冷池/热池双轨)

6.1 集群拓扑

  • 冷池(主业务缓存):Redis 7 Cluster,6 主 6 从,每主 64G Maxmemory,volatile-lru。
  • 热池(热点隔离):独立 Redis 7 实例 4 台主(无从),maxmemory 256G/台,allkeys-lfu,100G NIC,CPU 绑核。
  • 访问层:应用侧用 一致性哈希 + 本地 sidecar LRU(1G)。

6.2 热点识别(TopK + 采样)

  • 采样器在网关侧以 1‰ 比例上报 <key, hit> 到 Kafka hotkey-topic。
  • 消费器基于 Count-Min Sketch + TopK(redis-bloom 或内存实现),滚动窗口 30s。
  • 阈值:同一 key 30s 命中 > 100k 且 miss 率 < 1% → 加入热池白名单。

简化版 Go 伪代码:

if cm.Estimate(key) > 100000 && missRate(key) < 0.01 {
    hotPool.Add(key)
}

6.3 热点旁路(Lua 原子搬运 + TTL 重写)

当 key 进入热池:应用先读热池,不命中则回冷池,命中后把 TTL 重写为短 TTL(例如 30~60s),防止雪崩。

Lua(在应用侧 eval):

-- KEYS[1]: key, ARGV[1]: ttlSeconds
local v = redis.call('GET', KEYS[1])
if v then
  redis.call('PEXPIRE', KEYS[1], ARGV[1]*1000)
  return {1, v}
else
  return {0, nil}
end

6.4 击穿/穿透/雪崩策略

击穿:互斥锁(setnx 锁 300ms)+ 后台刷新。

穿透:布隆过滤(skuId 不存在直接 404,本地布隆 1e7,fpp=0.01)。

雪崩:TTL 抖动(±20%),多层 cache(sidecar→热池→冷池)。

6.5 Redis 关键参数(热/冷池)

参数 冷池 热池
maxmemory-policy volatile-lru allkeys-lfu
hz 10 50
tcp-keepalive 60 30
cluster-node-timeout 15000 N/A
activedefrag yes yes
latency-monitor-threshold 10 1

7. 异步削峰(Kafka + 事务外化)

  • 交易链路仅做必要校验和预占库存,重任务(发券、积分、消息)走 Kafka。
  • Kafka 主题:checkout-log(3×集群副本;分区数 48),xfs + noatime,num.network.threads=8,num.io.threads=16。
  • 消费端开启批量确认与幂等写。
  • 超时策略:入口层超过 100ms 排队直接 429 + 降级提示(可配置开关)。

8. 数据库层(只写必须的东西)

  • MySQL 8.0 主从,Semi-sync 打开,binlog_row_image=FULL(配合 CDC)。
  • 核心表:order(主键雪花+时间分区)、stock(行级乐观锁 + version 字段)。
  • 典型 SQL 限流:在 DAO 层为 SELECT ... FOR UPDATE 增加热点分片键,避免整段锁。

9. 系统与内核调优(CentOS 7.9 + kernel 5.4)

/etc/sysctl.d/99-tuning.conf:

net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_fin_timeout = 10
net.core.netdev_max_backlog = 250000
fs.file-max = 10485760
vm.swappiness = 1
vm.max_map_count = 262144

绑核与 IRQ:

# 入口LB绑核:接收队列与工作线程分 NUMA 节点
for q in /sys/class/net/eth0/queues/rx-*; do
  i=${q##*-}; echo $((i%16)) > $q/rps_cpus
done

10. 监控与红线(告警要「少而准」)

层级 指标 红线/动作
Anycast BFD flap > 3/5min 自动降 Pref + Pager P1
CDN 命中率 < 90%(静态)、<70%(API) 自动触发二次预热
入口LB SYN 队列利用率 > 80% 开启 XDP 抛弃+提升 backlog
应用 P99 > 200ms 或 5xx > 0.5% 打开降级开关(读缓存/禁推荐)
Redis 热池 GET avg > 1ms 或 hit < 95% 扩容热池/扩大白名单
MySQL InnoDB row lock time > 50ms 部分接口转异步/限流

11. 风控(把「羊毛」挡在门口)

  • 设备画像:UA + IP 段 + TLS JA3 + 指纹(Canvas/字体/传感器),落盘至风控网关。
  • 信誉分:基于近期行为滑窗(5min/24h),对低分用户限频/验证码。
  • 清洗联动:WAF 规则自动同步至清洗厂商,命中阈值自动切高级防护。
  • 关键接口:/login、/checkout 开强校验;灰名单仅走冷池缓存,拒绝热池提速。

Nginx 简化规则:

map $ja3_hash $rbl {
  default 0;
  "7721d..." 1; # 黑样本
}
server {
  if ($rbl) { return 403; }
  location /checkout {
    limit_req zone=vip burst=100 nodelay;
    # 验证签名/时间戳/nonce...
  }
}

12. 演练清单(实战前的实战)

T-7 天(全链路压测 + 灰度)

  •  合成流量 60% 峰值,逐层开缓存与限流。
  •  Anycast 人为降优;验证撤宣与回宣路径稳定。
  •  Redis 热点注入(Top100 SKU),验证热池命中。
  •  MySQL 主从延迟注入 200ms,验证事务外化。

T-1 天(静态预热 + 规则冻结)

  •  CDN 全量预热校验(抽样 2% 命中)。
  •  WAF/风控策略冻结并导出快照。
  •  回滚点创建(镜像 + 配置版本)。
  •  值班席位表、联络矩阵更新。

T 日(战术动作)

  •  00:00 前 30 分钟降低探测阈值,开启更激进限流。
  •  监控面板只保留 9 块关键大盘(其他静音)。
  •  每 10 分钟节奏报(CDN 命中、P99、交易失败率)。
  •  发生拥塞先开降级,再排查原因(纪律!)。

13. 坑与现场修复(血泪账本)

  • Anycast 黑洞:健康检查只测 200 OK,未覆盖上游 TLS 握手超时 → 改为 合成交易探测(下单沙箱),并将失败计入撤宣阈值。
  • Redis 热池抖动:TopK 窗口太短,热点在 1 分钟内频繁进出 → 改 30s 滑窗 + min_stay=120s。
  • CDN 预热误伤:API 带签名参数被当不同 URL,缓存碎片化 → Vary 精简 + 参数白名单。
  • Kafka 背压:磁盘写入放大导致 IO 等待高 → 改为 直接 IO + 批量大小调小,log.flush.interval.messages 调整配合。
  • 内核队列满:netdev_max_backlog 不足导致丢包 → 提升至 250k,并配合 RSS 队列绑核。
  • 数据库行锁:stock 表热行锁冲突 → 将 sku_id 做热分片(hash 路由),把锁扩散为多行。

14. 复盘模板(我怎么判断赢了)

维度 目标 实际 结论/改进
CDN 命中 静态≥90%,API≥70%    
入口 P99 ≤80ms    
交易失败率 ≤0.15%    
风控准确率 ≥97%    
Redis 热池命中 ≥95%    
Anycast 撤宣 ≤30s    

15. 附:落地指令与示例

15.1 一键调整系统参数(Ansible 片段)

- hosts: all
  tasks:
  - copy: src=99-tuning.conf dest=/etc/sysctl.d/99-tuning.conf
  - command: sysctl --system
  - lineinfile:
      path: /etc/security/limits.conf
      line: "* soft nofile 1048576"
  - service: name=irqbalance state=stopped enabled=no

15.2 Redis 热池白名单推送

# hotlist.txt: 每行一个 key
xargs -I{} -P32 redis-cli -h HOTPOOL1 -a "$PASS" SET {} "$(date +%s)" EX 60 < hotlist.txt

15.3 eBPF/XDP 快速丢 SYN(cloudflare/katran 思路,示意)

# bpftool 加载 XDP 程序(示意,不展开)
ip link set dev eth0 xdp obj xdp_syn.o sec xdp

今年的 00:00,我会照例站在机柜前。监控面板的曲线像精心修剪的花园,Anycast 的路由几乎没有抖,Redis 热池的命中率稳得像块大理石。
00:07,风控提示有一波羊毛党加速入场,我们的限流轻轻合上闸门;00:15,物流回传开始涌入,Kafka 的水位像练过台阶舞,一步不差。
我摘下耳机,听见冷通道风机的低鸣——这声音去年听来像风暴,今年更像稳稳的呼吸。
走出机房的时候,天刚要亮。

真实的系统不是完美的,而是「在最差情况下还能优雅地坏」。
这就是我在香港机房做多活与削峰的全部:不追求神话,追求可控、可预期、可复原。
明年我们还会继续把这套打法磨下去,把每一次实践经验铆在架构上。

目录结构
全文