关于 content-delivery
面向系统设计与云架构工作的技能,指导何时采用 CDN,以及如何选择缓存、路由和源站保护策略。内容涵盖设计权衡、故障应对、监控指标与实施步骤。
主要功能
- 01选择拉取或推送分发
- 02规划缓存键、TTL 与失效
- 03配置边缘路由和源站保护
- 04评估故障模式与监控指标
适用场景
- 01为全球用户加速静态资源交付
- 02在流量高峰期间减少源站负载
- 03确定缓存策略并估算流量成本
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ proyecto26/ system-design-skills --skill content-delivery安装后在 Claude Code 中输入/content-delivery确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/deep-dive.mdreferences/providers/aws.mdazure.mdgcp.mdgeneric.md详细介绍
内容分发
将字节推送到网络边缘,让请求在靠近用户的位置终止,永远不会到达源站。CDN 是系统最外层的缓存层:配置得当,大多数静态内容和媒体流量以及一部分延迟都会在到达服务器前消失;配置不当,则会提供过期资源、增加源站负载,或重复支付出站流量费用。
适用场景
同一批文件(图片、视频、JS/CSS 包、下载文件、字体)会被地理位置分散的用户反复读取;源站或其带宽成为静态读取的瓶颈;或者跨区域延迟导致首字节时间变差(跨洲往返约为 100 ms,参见 back-of-the-envelope)。CDN 可以同时降低延迟并减轻源站负载。
不适用场景
高度个性化、每个请求都动态生成且无法缓存的响应(CDN 会多增加一跳,却什么也缓存不了)。用户集中在单一区域且数量很少,源站已经能轻松处理读取请求(YAGNI——CDN 会带来另一个供应商、另一笔账单和另一个失效处理问题)。必须保持严格实时、无法容忍任何过期窗口的数据——这类数据应放在源站,或置于 consistency-coordination 之后,而不是放在基于 TTL 的边缘缓存中。在没有数据表明静态读取或地域分布才是问题之前就决定使用 CDN,是个危险信号。
先明确需求
- 内容构成——可缓存的静态内容/媒体与不可缓存的动态内容/个性化内容分别占多少?(只有可缓存的部分能从中受益。)
- 更新频率与过期容忍度——资源多久变更一次,边缘副本可以过期多久?(这会影响 TTL 和失效策略。)
- 地域分布——用户在哪里,分布有多集中?(这决定边缘 PoP 和地域路由是否有意义。)
- 对象大小与出站流量——平均资源大小 × 请求数 = 出站流量;这决定账单和卸载规模(→
back-of-the-envelope)。 - 源站形态——对象存储(S3/GCS/blob)还是动态应用服务器?边缘缓存清空后,能否承受冷缓存惊群?
选项
分发模式——内容如何到达边缘
- **拉取(源站拉取):**边缘在首次未命中时获取内容,按 TTL 缓存,并为后续请求提供服务。适用于流量大且内容体积大或变动频繁的情况——边缘只保存实际有人请求的内容。大多数系统的默认选择。
- **推送:**提前将资源上传到 CDN,并重写 URL。适用于目录规模小且内容静态,或上线流量高峰无法承受首次未命中时(可预热缓存);代价是需要自行管理存储和上传。
缓存键与 TTL——边缘依据什么建立缓存键,以及缓存多久
- 较长 TTL + 指纹 URL(
app.4f9a.js、image.png?v=2):不可变资源缓存数月;内容变更时使用新的 URL,而不是执行失效操作。适用于可以控制资源 URL 的情况——这是最简洁的模式。 - **较短 TTL /
stale-while-revalidate:**为按计划更新的内容限定过期时间。适用于URL 固定但内容会更新的情况。
边缘邻近性与路由——用户如何连接到最近的 PoP
- **Anycast / DNS 地域路由:**将每位用户路由到最近的健康边缘节点。适用于用户分布在多个区域的情况(使用 CDN 时几乎总是如此)。与
load-balancing共享相关机制;路由机制详见该部分。
源站保护——缩小源站的暴露面
- **源站屏蔽 / 中间层缓存:**在源站前设置一个区域级缓存层,所有边缘节点都通过该层拉取内容,将 N 个边缘未命中合并为一次源站请求。适用于源站负载卸载或惊群保护比冷缓存未命中时略微增加的延迟更重要的情况。
权衡
| 选项 | 解决的问题 | 会加剧的问题 | 何时更改 |
|---|---|---|---|
| 拉取式 CDN | 边缘节点只存储请求过的内容;无需上传流水线 | 每个对象的首次请求都会因缓存未命中而变慢;内容未变但 TTL 到期时会重复拉取 | 冷缓存未命中的延迟或发布高峰造成影响 → 改用推送式 / 预热 |
| 推送式 CDN | 没有冷缓存未命中;可完全控制缓存内容和缓存时机 | 上传、存储和 URL 重写都由你负责;还要为很少读取的资源付费 | 目录规模扩大或内容频繁变动 → 改用拉取式 |
| 长 TTL + 指纹化 URL | 几乎永久缓存;更新会使用新 URL(不存在失效竞争) | 需要控制构建和 URL;旧版本会留在边缘节点,直到过期 | URL 不受你控制 → 改用短 TTL |
| 短 TTL / stale-while-revalidate | 在稳定 URL 上限制内容陈旧时间 | 源站重新验证流量增加;同步过期可能引发请求风暴 | 内容确实不可变 → 使用指纹化 URL + 长 TTL |
| 地理路由 / 任播 | 用户访问最近的边缘节点;延迟更低 | 需要考虑更多 PoP;路由可能将用户发送到性能下降的 PoP | 受众只在单一区域 → 不采用 |
| 源站盾 | 将边缘缓存未命中合并为一次源站请求;保护源站 | 冷路径上多一次跳转;如果仅部署在单一区域,盾层会成为新的瓶颈 / 单点故障 | 源站足够健壮,且现有卸载已足够 → 移除 |
压力下的行为
CDN 通常会吸收负载峰值——这正是它的职责——但它也有自身的故障模式, 而这些故障往往会直接将流量推向源站。
- 冷缓存 / 大规模驱逐: 清除缓存、推送配置或 TTL 同步过期后,边缘缓存命中率会骤降,每个 PoP 都会同时从源站拉取内容。这会在全球范围内引发
caching惊群效应。缓解措施: 使用源站盾合并缓存未命中请求、为 TTL 添加随机抖动、通过stale-while-revalidate在重新获取内容时由边缘节点提供过期内容,以及分阶段清除缓存。 - 缓存穿透 / 命中率低: 无限制的查询字符串变化或将 Cookie 纳入缓存键会使键空间膨胀,导致内容无法留在缓存中——源站仍需处理全部流量,你却还要支付 CDN 费用。缓解措施: 规范化缓存键参数或设置白名单;在静态路径上剥除 Cookie。
- 热点对象: 一个病毒式传播的文件可能超出单个 PoP 的容量,但 CDN 扩展处理能力远胜源站——真正的风险是热点不可缓存路径穿透到源站。
- CDN 故障 / PoP 部分故障: 边缘层现在成为所有服务前面的依赖项。规划回退到源站的方案(客户端或 DNS 故障转移到源站),并接受源站必须短暂承受全部负载;也可以使用第二个 CDN(多 CDN)。
- 出口流量意外激增: 配置错误的
no-cache或热点不可缓存资源可能会让源站出口流量费用悄然增加到原来的 10 倍。
监控: 边缘命中率(缓存命中率)、源站卸载率、源站请求速率(边缘故障时会飙升的指标)、按区域统计的边缘 p95 延迟、出口字节数,以及边缘与源站的 4xx/5xx。
如何应用
- 明确输入条件——内容构成(可缓存比例)、陈旧度预算、地理分布、对象大小 × 数量,以及源站特征(参见
Clarify first)。如果可缓存比例接近零,或受众仅分布在单一区域,请到此为止。 - 根据权衡表选择分发模式和缓存键:默认选择 拉取;只有冷缓存未命中延迟或发布峰值造成影响时,才切换到 推送/预热。如果你能控制 URL,优先选择 较长 TTL + 指纹化 URL;否则选择 较短 TTL /
stale-while-revalidate。 - 设置参数——配置
Cache-Control(max-age、immutable、stale-while-revalidate)、缓存键(URL 路径 + 白名单参数;静态路径剥离 Cookie)、仅在确有差异时设置Vary,并在需要减轻源站负载或防止惊群时添加源站盾牌。 - 对设计进行压力测试——演练一次全局清除、一次配置推送,以及一次 CDN/PoP 故障。确认 TTL 抖动 +
stale-while-revalidate+ 盾牌能让源站承受得住,并且客户端/DNS 有回退到源站的方案。 - 用数字估算规模——通过
back-of-the-envelope估算命中率(目标为 90% 以上)、源站卸载比例和出口流量(请求数 × 平均对象大小)。如果出口流量或源站请求率令人担忧,请重新审视缓存键和 TTL。 - 选择提供商——默认采用通用方案;如果指定了云服务,请阅读对应的提供商文件,了解服务映射和限制(参见
Choosing a provider)。 - 在回答中展示权衡——提供一个
Decision | Solves | Worsens | Change it when…表格,每项选择各占一行(例如所选方案、其存储方式、故障策略);最后一列填写具体触发条件(数字或可观测信号)。然后指出最先会出问题的地方。
应做与不应做
应做
- 为不可变资源添加指纹,并缓存数月——将更新转化为新的 URL,而非使缓存失效。
- 将缓存键参数加入白名单,并在静态路径剥离 Cookie,以保持较高的命中率。
- 添加
stale-while-revalidate和 TTL 抖动,避免同步过期导致源站遭遇惊群。 - 如果大量边缘节点可能同时未命中并回源,请添加源站盾牌。
- 为 CDN 或 PoP 故障规划回退到源站/DNS 的方案(或使用多 CDN)。
- 监控边缘命中率和源站请求率——边缘故障时会飙升的那个数值。
不应做
- 除非有数据表明静态读取量大或地理位置是瓶颈,否则不要急着使用 CDN。
- 不要让无限制的查询字符串或
Vary: Cookie导致键空间膨胀、缓存形同虚设。 - 不要把单区域源站盾牌当作免费的方案——它会成为新的瓶颈/单点故障。
- 如果要求零陈旧度,就不要通过 TTL 缓存必须始终为最新的数据。
- 不要在热门资源上随意设置
no-cache——这可能会在不知不觉中让源站出口流量增加 10 倍。
重要指标
决定性的指标包括命中率(目标为 90% 以上;低于约 80% 时,应质疑内容是否适合缓存)、源站卸载比例(1 − 源站请求数/总请求数),以及出口流量(请求数 × 平均对象大小)。边缘与源站之间的延迟差异体现了收益:边缘命中可在同一区域完成往返(约数毫秒至数十毫秒),而无需跨洲往返(约 100 ms)。使用 back-of-the-envelope 计算出口流量和卸载比例——此处不要重复其表格;出口流量通常是 CDN 账单中占比最高的项目。
接口示意
约定主要是由源站设置、边缘节点遵循的 HTTP 缓存头:
- 指纹化静态内容使用
Cache-Control: public, max-age=31536000, immutable。 - 会定期更新的稳定 URL 使用
Cache-Control: public, max-age=60, stale-while-revalidate=600。 - 使用
ETag/Last-Modified启用低成本重新验证(304 Not Modified)。 - 只有在确实会据此提供不同内容的请求头上使用
Vary(不慎使用Vary: Cookie会大幅降低命中率)。 - 缓存键:URL 路径 + 明确列出的查询参数白名单;决定哪些 Cookie/请求头(如果有)应纳入缓存键。
失效可以通过调用
PURGE/失效 API 实现,或者(最好)通过更新 URL 版本实现。版本化 URL 可以完全避开清除传播竞态。
选择提供商
默认采用上面的通用方案。如果用户指定了云服务商,请阅读 references/providers/<provider>.md,了解托管服务映射、配额/限制和提供商特定的权衡。如果该提供商没有对应文件,则采用通用方案。
图示
如需展示边缘 → Shield → 源站的拉取路径(以及虚线表示的冷缓存未命中箭头,还有从客户端到最近 PoP 的地理路由),请使用插件内的 architecture-diagram 技能。用缓存颜色绘制边缘节点,用存储颜色绘制源站;此处不要嵌入 Mermaid。
相关构建模块
caching— 归属概念位于此处:失效、淘汰、TTL 和惊群理论均在此处;CDN 是位于应用/分布式缓存之上的边缘层,也是静态内容/媒体内容读取源站的替代方案。load-balancing— 归属概念位于此处:地理/任播路由和源站健康检查会将用户导向最近的边缘节点。back-of-the-envelope— 为此提供依据:提供出口流量、卸载百分比和延迟收益数据,以论证使用 CDN 的合理性。data-storage— 依赖于:对象存储通常是 CDN 的源站。consistency-coordination— 对于无法容忍任何陈旧窗口的数据,这是此方案的替代方案(从源站提供内容,而不是使用基于 TTL 的边缘缓存)。system-design— 与之配合(反向链接):当设计需要在地理范围内提供静态内容/媒体内容时,由编排器将请求路由至此处。
参考资料
references/deep-dive.md— 缓存键规范化、Cache-Control指令语义、推送与拉取机制、源站 Shield/分层拓扑、失效与版本化之间的竞态、多 CDN,以及媒体专用交付(分段 HLS/DASH、范围请求、签名 URL)。在详细设计边缘层时阅读。references/providers/{generic,aws,azure,gcp}.md— 各环境的服务映射、限制和注意事项。