关于 caching
用于为读密集型系统设计应用层或分布式缓存的架构技能。指导策略选择、数据新鲜度与淘汰决策、容量估算,以及缓存故障防护。
主要功能
- 01缓存位置与读取策略
- 02写入和淘汰策略选择
- 03数据新鲜度与失效规划
- 04缓存击穿与热点键缓解
- 05缓存容量估算与监控
适用场景
- 01减少对数据存储的重复读取
- 02按新鲜度要求选择缓存行为
- 03缓存重启时保护源数据服务
- 04为热点数据集规划缓存容量
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ proyecto26/ system-design-skills --skill caching安装后在 Claude Code 中输入/caching确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/deep-dive.mdreferences/providers/aws.mdazure.mdgcp.mdgeneric.md详细介绍
缓存
将热门数据的副本放到更靠近读取者的位置,让大多数请求都能跳过缓慢路径。 对于读密集型系统,缓存是收益最大的优化手段,也是最容易在细节上出错的手段,因为缓存会增加第二个数据源,可能提供过时或错误的数据;如果缓存出现问题,还可能放大故障。
何时采用
读取占主导(通过 back-of-the-envelope 得出的读写比很高);相同数据会被反复读取;数据存储是读取瓶颈;或者重新计算成本很高。缓存可以降低读取延迟,并减轻源站负载。
何时不采用
写密集型或只读一次的数据(命中率低,只会增加纯粹的开销)。必须绝对保持最新、不能有任何过时的数据(缓存本质上是过时副本;若要求严格的新鲜度,请直接访问源站,或使用 consistency-coordination)。在没有数据表明读取是问题之前,不要添加缓存(YAGNI)——它会引入新的故障模式,也会增加一个需要运维的对象。
先明确
- 读写比和命中率 — 工作集是否适合缓存?(→
back-of-the-envelope,二八法则。) - 可容忍的过时程度 — 几秒?几分钟?读取必须能看到自己的写入吗?
- 工作集大小 — 热门数据集能否装入所有缓存节点的 RAM?
- 写入时的一致性 — 缓存能否短暂地与存储不一致?
- 驱逐触发条件 — 访问模式是什么(最近访问?访问频率?按时间?)。
选项
缓存位置(通常分层设置):客户端/浏览器 → CDN 边缘节点(→ content-delivery)
→ 应用程序/进程内 → 分布式缓存(Redis/Memcached)→ 数据库缓冲池。本技能重点介绍应用程序层和分布式缓存层。
读取策略
- Cache-aside(惰性加载): 应用检查缓存,未命中时读取存储并填充缓存。适用于读取模式不可预测的情况;是大多数系统的默认选择。
- Read-through: 缓存在未命中时从存储中获取数据。适用于简化应用代码并集中管理缓存策略的情况。
写入策略
- Write-through: 同步写入缓存和存储。适用于写入后立即读取必须获取最新数据,且可以接受较慢写入的情况。
- Write-back(写后回写): 立即写入缓存,之后异步刷新到存储。适用于可容忍短暂数据丢失窗口的写密集型/突发流量路径。
- Write-around: 只写入存储;让缓存按需在读取时填充。适用于写入的数据近期很少会再次读取的情况(避免缓存频繁换入换出)。
驱逐策略
- LRU 适用于近期访问偏斜的情况(最常见);LFU 适用于热门程度稳定的情况;TTL 用于限制数据过时的时间;FIFO 较少使用。应根据访问模式选择策略。
权衡
| 选项 | 解决的问题 | 带来的问题 | 何时更换 |
|---|---|---|---|
| Cache-aside | 简单、可靠(缓存故障 ⇒ 只是变慢) | 每个键的首次读取都会未命中;写入后可能读取到过时数据 | 未命中成本太高 → 改用 read-through + 预热 |
| Read-through | 集中管理,应用代码简洁 | 应用与缓存库耦合;冷启动时会未命中 | 需要针对每个键定制加载逻辑 |
| Write-through | 写入后读取可获取最新数据 | 写入较慢;可能缓存永远不会再读取的数据 | 写入占主导且不会再次读取 → 改用 write-around/back |
| Write-back | 速度快,可吸收写入突发流量 | 崩溃时存在数据丢失窗口;实现复杂 | 需要保证近期写入的持久性 |
| Write-around | 不会因只写入的数据导致缓存频繁换入换出 | 最近写入的键在首次读取时会未命中 | 这些数据确实会在写入后立即读取 → 改用 write-through |
| TTL eviction | 自动限制数据过时的时间 | 集中过期可能导致源站遭遇惊群效应 | 增加抖动 / 使用软 TTL 刷新 |
压力下的行为
缓存出现问题时,不只是无法发挥作用,还可能拖垮源站。
- 惊群 / 缓存击穿: 热点键过期(或缓存重启),数千个并发未命中请求同时打到存储。缓解措施: 按键加锁 / 请求合并(single-flight)、提前刷新 / 概率刷新、TTL 抖动。
- 缓存穿透: 对不存在的键发起的请求每次都会绕过缓存(通常带有恶意)。缓解措施: 缓存否定结果(设置较短的 TTL),或在前面加布隆过滤器。
- 热点键: 单个键(名人、热门内容)超出单个节点的吞吐能力。缓解措施: 在多个节点间复制该键、增加本地 / L1 层,或对值分片。
- 驱逐风暴 / 冷缓存: 刷新或部署后,命中率骤降,源站承受全部负载。缓解措施: 预热关键键;逐步增加流量。
- 写入后数据陈旧: 存储已更改,但缓存未更新。缓解措施: 写入时失效,或采用写穿透,或设置较短的 TTL——根据陈旧度预算选择。
监控: 命中率、p99 延迟、驱逐率、键分布(热点),以及缓存重启期间源站的 QPS。
如何应用
- 明确输入条件——确认读写比例、陈旧度预算和热点集大小(参见 先明确条件)。如果尚无数据表明读取是瓶颈,就到此为止——暂时不需要缓存(→
back-of-the-envelope)。 - 从权衡表中选择策略——选择读取策略(默认使用 cache-aside)、根据陈旧度预算确定写入策略,并选择与访问模式匹配的驱逐策略。
- 设置关键参数——定义键命名方案、TTL(带抖动)以及每类键对应的失效事件。提前决定是否采用否定缓存和 single-flight,不要等到首次事故发生后再决定。
- 对方案进行压力测试——逐一检查 压力下的行为 中的各类故障(惊群、穿透、热点键、冷缓存、写入后数据陈旧),并确认流量特征可能触发的故障都有相应的缓解措施。
- 用数字确定容量——确保所有节点的 RAM 足以容纳热点集,检查目标命中率(90%+)是否合理,并确认节点数足以覆盖峰值 QPS(→ 重要指标)。
- 选择提供方——默认采用通用方案;只有用户指定云厂商时才打开对应的提供方文件(参见 选择提供方)。
- 在回答中展示权衡——给出
Decision | Solves | Worsens | Change it when…表格,每个此处做出的选择对应一行(例如所选方案、存储、故障策略);最后一列填写具体触发条件(数字或可观察信号)。然后说明最先出现故障的环节。
应做与不应做
应做
- 默认使用 cache-aside;缓存不可用时,它仍能保证正确性(只是速度更慢)。
- 为每个条目设置 TTL,并添加抖动,避免键同时过期。
- 在上线前为热点键添加 single-flight / 请求合并。
- 按明确的陈旧度预算,在写入时失效(或采用写穿透,或设置较短的 TTL)。
- 根据热点集确定缓存容量,并对命中率、驱逐情况和源站 QPS 设置告警。
不应做
- 不要在没有数据证明读取是瓶颈之前添加缓存(YAGNI)。
- 不要缓存必须始终保持最新的数据——应直接访问数据源。
- 不要让批量过期或冷启动把全部负载压到源站(应预热、逐步增加流量)。
- 不要忽略针对不存在键的请求洪泛——应缓存否定结果,或在前面加布隆过滤器。
- 不要在不同架构版本间复用同一个键——应提升版本号(
...:v2)。
重要指标
一个缓存节点可处理约 100k–1M QPS,远高于 RDBMS(约 1k)。内存访问耗时约为 100 ns,而磁盘访问耗时为毫秒级——这正是缓存能发挥作用的原因。根据热点集确定缓存容量(约 20% 的数据 ≈ 80% 的读取)。目标命中率通常为 90%+;低于此值时,应考虑数据是否适合缓存。→ back-of-the-envelope。
接口草图
缓存条目是一份契约:键(稳定且有命名空间,例如
user:123:profile)、值(序列化;注意大小)和 TTL。为每类键确定
失效事件(写入时?TTL 到期时?版本号递增时?)。带版本号的键(...:v2)可通过写入新键而非删除,避免删除竞态。
选择提供商
默认采用上述通用方案(Redis 或 Memcached,可自托管或使用托管服务)。
如果用户指定了云平台,请阅读 references/providers/<provider>.md,了解托管服务映射、限制和提供商特定的权衡。如果该提供商没有对应文件,则采用通用方案。
图示
如需展示缓存旁路读取路径(应用 → 缓存 → 未命中 → 源站 → 填充缓存)或惊群/回退流程,请使用插件内的 architecture-diagram 技能——缓存节点使用缓存颜色,源站使用其存储颜色,未命中路径使用虚线箭头。
相关构建模块
content-delivery— 与此配合,作为其上层的边缘层;用于静态内容和媒体的 CDN/边缘缓存(该概念归属该模块)。data-storage— 依赖此模块,将其作为缓存所保护的源站;其只读副本是缓存读取的替代方案,缓存节点分片方式则与其分区方式相对应。(一致性哈希理论归属consistency-coordination。)consistency-coordination— 当无法接受数据过时,且需要读己之写或更强保证时,与此配合。back-of-the-envelope— 为此技能提供依据:它提供读请求比例和热点集大小,用于判断缓存是否合理。resilience-failure— 与此配合;速率限制和熔断器(归属该模块)有助于控制行为异常的缓存可能引发的重试风暴。system-design— 由编排器负责的概念包括:推理循环、权衡方法和十种故障模式。
参考资料
references/deep-dive.md— 失效模式、单航班/请求合并、缓存分片的一致性哈希、Redis 与 Memcached 内部机制、本地(L1)+分布式(L2)层级。设计缓存层的详细方案时请阅读。references/providers/{generic,aws,azure,gcp}.md— 各环境的服务映射、限制和注意事项。