源站与缓存策略:回源优化、缓存穿透防护、Origin Shield 与动态内容缓存

系统拆解源站(Origin)与缓存层的完整策略:回源链路优化、缓存穿透/击穿/雪崩的防护、Origin Shield 与缓存分层、动态内容的缓存变通(边缘 404/零缓存 TTL/分区缓存)、静态资源与 API 的差异化策略,给出从「源站裸奔」到「多层缓存」的演进路径。

一、引言

CDN 的本质是「把热数据推到离用户最近的地方」,但真正决定命中的,是源站(Origin)与缓存层之间如何配合。很多团队只配置了边缘 TTL,却忽略了源站的回源链路、穿透防护和动态内容怎么缓存——结果边缘命中率虚高,一遇热点源站直接被打穿。

本文拆解源站与缓存策略的核心问题:回源链路怎么优化、缓存穿透/击穿/雪崩怎么防、Origin Shield 分层怎么搭、动态内容怎么变通缓存,最后给出一份「静态 + API」差异化的缓存策略表。基础概念见 边缘缓存策略。


二、回源链路优化:让「没命中」也快

2.1 回源路径

用户 → 边缘 PoP →(未命中)→ 源站
  回源延迟 = 边缘到源站的网络 + 源站处理
  边缘离源站越远,回源越慢

2.2 回源优化三板斧

1. 回源就近:边缘回源走「最近的源站」(多源站 / 按地域回源)
2. 回源压缩:源站开 gzip/br,回源体积更小
3. 回源协议:HTTP/2 到源站,减少连接开销
优化收益
就近回源回源 RTT 从跨洲降到区域级
压缩回源带宽与延迟双降
连接复用省握手

心法:边缘命中率高≠快,回源快的系统才是真的快。冷内容、首次访问、缓存刷新都会打到源站,回源优化决定这部分的体验。


三、缓存穿透、击穿、雪崩

3.1 三类缓存故障

穿透:请求的数据「本来就不存在」(如查不存在的 id)→ 永远不命中 → 每次都打源站
击穿:某个「热点 key」过期瞬间 → 大量请求同时打源站
雪崩:大量 key「同时过期」→ 源站瞬间被打爆

3.2 穿透防护

缓存穿透:把「不存在」也缓存起来
  - 空值缓存:存一个「空标记」,TTL 短(如 5min)
  - 布隆过滤器:请求前先判断「大概率不存在」→ 直接返回
// 空值缓存:查不到也写缓存
const v = await cache.get(`order:${id}`)
if (v === undefined) {
  const data = await db.query(id)
  await cache.set(`order:${id}`, data ?? EMPTY, data ? 3600 : 300)
}

3.3 击穿防护

热点 key 过期 → 单飞(mutext):
  只有一个请求去回源重建缓存,其余等待
// 单飞重建:防止热点 key 过期瞬间打爆源站
const lock = await cache.setnx(`lock:order:${id}`, '1', { ttl: 10 })
if (lock) {
  const data = await db.query(id)
  await cache.set(`order:${id}`, data, 3600)
  await cache.delete(`lock:order:${id}`)
} else {
  await sleep(50); return cache.get(`order:${id}`)  // 等重建
}

3.4 雪崩防护

大规模同时过期 → 打爆源站
  对策:过期时间加随机抖动(3600 + rand(0,300))
       错峰过期,避免「齐刷刷失效」

心法:三类故障本质都是「缓存失效瞬间的流量洪峰」。穿透靠空值缓存、击穿靠单飞、雪崩靠错峰——三招都是从「把源站当盾」变成「把缓存当盾」。


四、Origin Shield:给源站再加一道缓冲

4.1 什么是 Origin Shield

边缘 PoP(数百个)──→ Origin Shield(区域聚合)──→ 源站
   └── 所有边缘回源先打 Shield,Shield 再回源
好处:源站只被 Shield 打,回源次数从「PoP 数 × 命中失败」降为「Shield 数」

4.2 两层缓存

边缘缓存(离用户近,TTL 短,命中率靠用户流量)
Shield 缓存(离源站近,TTL 长,命中率靠聚合回源)
   └── 冷内容:边缘 miss → Shield 命中 → 源站几乎不被打
缓存层位置作用TTL
边缘各地 PoP就近命中短
Shield区域中心聚合回源长

心法:Shield 是「缓存里的缓存」。它不直接服务用户,而是服务边缘——把「打源站」变成「打 Shield」,源站才能从「被打穿」变成「几乎不被直接打」。


五、动态内容缓存:边缘 404 / 零 TTL / 分区缓存

5.1 动态内容能不能缓存

完全动态(个性化 feed、实时状态)→ 缓存会出错
但很多「动态」其实有变通空间

5.2 三种变通

变通做法适用
边缘 404命中直接回 404,不把请求打到源站确定性不存在的路径
零 TTL 但有缓存Cache-Control: max-age=0, s-maxage=60内容准实时,秒级刷新
分区缓存按用户群/地域分区缓存半个性化(按区不按人)
动态接口的缓存分层:
  ├── 全局共有部分(配置、版本)→ 长缓存
  ├── 分区部分(地域/语言)→ 分区缓存
  └── 个人部分(我的数据)→ 不缓存 / 短 TTL

5.3 动态内容的 Cache-Control

// 动态但可复用:让 CDN 缓存 60s,浏览器不缓存
Cache-Control: public, max-age=0, s-maxage=60

心法:动态内容不是「不能缓存」,而是「分层缓存」。把响应拆成「共有 + 分区 + 个人」三层,共有层和分区层都能用 CDN,只有个人层走源站。


六、静态资源与 API 的差异化策略

6.1 静态资源

静态资源(js/css/img):
  - 文件名带 hash → 长期缓存(一年)
  - Cache-Control: public, max-age=31536000, immutable
  - 版本变化靠「文件名变化」而非「过期」

6.2 API

API 响应:
  - 不变化的(配置/字典)→ 长缓存
  - 准实时 → s-maxage 短缓存
  - 强动态 → 不缓存 + 回源优化
内容TTL策略
静态资源1 年immutable
配置/字典1 天s-maxage
列表数据60ss-maxage
个性化接口0回源优化

铁律:静态靠长缓存,动态靠回源优化。对「不能缓存的内容」,把力气花在回源链路(就近、压缩、并发)而不是硬缓存。


七、缓存刷新与一致性

7.1 主动刷新

内容更新后需要「让旧缓存失效」:
  - 刷新单 URL(CDN 提供 purge API)
  - 批量刷新(目录 / 前缀)
  - 服务端标记版本 + 客户端带版本请求

7.2 刷新风暴

全局刷新瞬间 → 所有边缘同时回源 → 源站被打爆
  对策:分批刷新、错峰、先预热后刷新

心法:缓存的难点不在「怎么缓存」,在「怎么让该失效的失效」。发布系统要带「预热 + 定向刷新」能力,而不是全站 purge。


八、总结

源站与缓存策略的核心是「给源站减负」:

  1. 回源优化:就近、压缩、连接复用,让没命中也快。
  2. 三类防护:穿透靠空值缓存、击穿靠单飞、雪崩靠错峰。
  3. Origin Shield:边缘打 Shield、Shield 打源站,源站几乎不被直接打。
  4. 动态分层:共有/分区/个人三层,能缓的分层缓、不能的走回源优化。
  5. 刷新可控:定向刷新 + 预热,别全站 purge。

把源站从「被边缘直接打的靶子」变成「被 Shield 保护的稳定服务」,配合 Webhook 集成 的主动失效与 可观测性 的命中率监控,缓存体系就能既快又不脆。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「tools」更多文章

  1. 域名与 DNS 接入实战:NS/解析记录、SSL 签发、CDN 接管与多级域名策略
  2. API 网关与 BFF 层:边缘聚合、统一鉴权与接口编排实战
  3. Serverless 定时任务实战:Vercel Cron、Cloudflare Cron Triggers 与调度编排