跨境业务最典型的浪费是:
- 大促前 10 分钟,边缘还在冷启动
- 新版本发布,全球节点逐个回源穿透
- 热点 SKU 第一次被访问,TTFB 800ms,后面 20ms
- 缓存失效同一秒雪崩,DB 被打穿
问题不是“没加缓存”,是缓存是被动等用户撞出来的。
香港节点的新定位:
在用户请求到达前,把热数据先调度到边缘。
它做三件事:
- 收中心“预热清单”→ 主动推边缘
- 管“版本化缓存键”→ 发版即切版,不脏读
- 做区域同步总线 → 港/新/日/美共享失效事件,不各回各的源
本文给一套可落地的“缓存预热+同步总线”工程架构,偏电商/内容/出海 SaaS 高频读场景。
一、先纠正:缓存不是“存起来”,是“提前放对位置”
被动缓存链路:
text
用户请求 → 边缘未命中 → 回源 → 源站查 DB → 写缓存 → 下次命中
首访永远慢,热点越高越容易击穿。
主动预热链路:
text
运营/CI 触发“预热事件” ▼ 香港同步总线 ├─ 拆 URL 清单(首页/活动页/SKU 详情/API 快照) ├─ 带版本号(v=20261101_02) ├─ 并发控速推边缘(不压源站) ▼ 各区域边缘节点 提前写入缓存 → 用户来即命中
核心认知:
缓存命中率 = 业务设计结果,不是运气。
二、整体架构:香港当“预热调度中枢”
text
[中心域] 发版系统 / 运营后台 / 大促剧本 │ 产出“预热清单 + 版本号” ▼ 香港同步总线(2C4G 轻量机集群) ├─ 消息层(NATS / Kafka 轻量 / Redis Stream) ├─ 预热控制器(并发、限速、重试、去重) ├─ 边缘 Agent 注册表(区域/节点/健康) └─ 失效事件总线(del 事件全局广播) ▼ 区域边缘(港/新/日/美西/法兰) ├─ CDN 边缘(静态/预渲染) ├─ Nginx/Varish 反代缓存 └─ 本地 Redis(API 快照/灰度开关)
原则:
- 香港不存业务主数据,只存“该预热什么”的指令
- 源站只收香港总线回源,不收公网边缘直回
- 预热失败可重试,不阻塞发布
三、版本化缓存键:发版即切层,不互斥覆盖
缓存脏读根源是“同 key 覆盖”。
用内容寻址 + 版本维度:
text
静态资源: /assets/{git_sha}/app.{hash}.js # 文件指纹,永久不变 页面/API 快照: cache_key = "page:home:v20261101_02" cache_key = "sku:12345:v20261101_02:region=hk" 灰度: cache_key = "sku:12345:v20261101_02:ab=checkout_v2"
边缘配置:
nginx
# Nginx 反代层 proxy_cache_key "$host$uri$http_x_env$v1_version"; proxy_cache_valid 200 30s; add_header X-Cache-Version $upstream_cache_version;
发新版本:
- 旧 key 保留 1 个周期(回滚可秒切)
- 新 key 预热完再切流量
- 不删旧 key,避免回滚瞬间穿透
四、预热控制器:别一把梭,把源站打挂
1. 预热清单来源
yaml
preheat_job: trigger: release_tag / manual / cron(大促前30min) targets: - url: https://shop.example.com/ weight: 100 - url: /api/sku/hot/12345 weight: 50 - url: /api/category/phone weight: 30 regions: [hk, sg, jp, us-west] concurrency: 8 rate: 50/s timeout: 2s
2. 控速拉取(不压源站)
python
import asyncio, aiohttp async def warm(url, region): async with session.get(url, headers={"X-Preheat":"1","X-Region":region}) as r: # 边缘写缓存,源站只处理一次 return r.status # 信号量限流 sem = asyncio.Semaphore(8) async def worker(url, region): async with sem: await warm(url, region)
关键点:
- 同 URL 同区域只预热 1 次(去重)
- 失败指数退避(1s/2s/4s)
- 大促前分波:先首页→再品类→再 SKU 详情
3. 边缘 Agent 接收
bash
# 边缘节点本地 curl -H "X-Preheat:1" https://hk-bus.example.com/push?region=hk&key=home:v2 # Agent 本地触发一次回源填充,后续真实用户命中
五、失效总线:别让全球边缘“各过各的”
传统:清缓存靠 CDN 控制台批量 purge,区域不同步。
正确做法:失效事件走消息总线。
text
源站数据变更(库存/价格/上下架) ▼ 发 del 事件: { "key":"sku:12345", "version":"v20261101_02", "reason":"stock_change" } ▼ 香港总线广播 → 各区域 Agent 本地删对应 key
Redis Stream 最小实现:
bash
XADD invalidation * key sku:12345 version v20261101_02
边缘消费:
python
for msg in xread(): redis.delete(f"cache:{msg.key}:{msg.version}") # 不删“下一版”预热中的 key
防雪崩补充:
- TTL 加 jitter(±10%)
- 单 key 失效用 singleflight(只放一个请求回源)
- 关键读用“旧值兜底 + 后台刷新”
六、预热效果怎么验证:别只看命中率
指标分层:
指标 |
说明 |
预热覆盖率 |
清单里多少 URL 在流量来前已写入边缘 |
首访 TTFB |
大促开门前 1 分钟采样 |
边缘命中率 |
按区域/按资源类型拆 |
回源 QPS 降幅 |
预热后源站读请求是否断崖下降 |
失效延迟 |
数据变更到边缘失效的 p95 |
长尾 p99 |
预热没覆盖到的长尾 URL 是否可控 |
验证脚本(香港机跑):
bash
# 大促前 5min 探边缘 for u in $(cat preheat_list.txt); do curl -w "$u dns=%{time_namelookup} ttfb=%{time_starttransfer}\n" -o /dev/null -s $u done
多节点测速视角(浏览器侧/拨测侧)可用来交叉验证:用户真实地理看到的命中情况,和香港总线预期是否一致。
七、大促/发版 SOP
text
1. 发版前 30min:生成预热清单(按热度排序) 2. 香港总线下发,边缘分波预热 3. 预热完自检:命中率>95%、TTFB 达标 4. 切流量(DNS/权重/边缘路由) 5. 旧版本 key 保留 5min 兜底 6. 数据变更走失效总线,不批量 purge 7. 回滚:流量切回旧 key,边缘秒级生效
灰度场景:
- 新版本预热到 5% 边缘节点
- 对比错误率/延迟
- 再全量预热
八、安全与合规
- 预热请求带内部头,公网不认
- 香港机不存 PII、不落业务库
- 失效事件只传 key 哈希,不传用户数据
- 传输 mTLS,Agent 短期证书
- 审计:谁触发预热、清了什么 key、影响哪些区域
九、最小落地清单
- [ ] 香港轻量集群跑同步总线(NATS/Redis Stream)
- [ ] 缓存键带版本号,不发版覆盖旧 key
- [ ] 预热控制器:清单+限流+去重+退避
- [ ] 边缘 Agent 注册健康,失败区域跳过不阻塞
- [ ] 失效总线全局广播,按 key+version 删
- [ ] singleflight + TTL jitter 防雪崩
- [ ] 预热前/中/后指标看板(命中率/TTFB/回源 QPS)
- [ ] 多节点测速交叉验证真实用户命中
- [ ] 回滚保留旧版本 key 5min
十、什么别用预热层干
- 把用户请求当预热(被动等撞)
- 全量 purge 不区分版本(雪崩+回滚难)
- 预热不限速打爆源站
- 香港机存敏感数据
- 只测中心命中率不测边缘长尾
- 失效事件传原文(合规雷)
十一、总结
香港服务器在缓存体系里最被低估的价值,不是“离用户近”,而是:
让热数据在用户来之前,就已经在用户家门口。
CDN 管边缘存储,
香港管“该存什么、先存哪里、什么时候作废”。
发版不是“上线等命中”,
是大促前把首页、SKU、接口快照,
按版本号安静铺满亚太边缘。
用户感知不到缓存存在,
只觉得“每次点开都秒出”。
缓存的高手,
不是命中率高,
是首访也像第 100 次访问。
一台香港小机,
不扛主库、不存订单,
却让大促开门那 1 秒,
没有一个人看到 loading。