缓存:读路径的副本治理与泄压阀¶
不要把缓存简单理解为“变快”,它在架构中更本质的角色是读路径的副本层与数据库的泄压阀。
1. 读路径的工程语义¶
当一个热点请求(如“热门制度详情”)进入系统时,查库本身没错,错在高频重复的无意义回源。
- Key 语义即真相定义:Key 不是字符串,是副本的索引。一个合格的 Key 必须包含
业务前缀:租户ID:对象ID:版本号(e.g.,policy:t42:doc108:v7)。漏掉租户会酿成串数据事故,漏掉版本会命中过期副本。 - 空值缓存(Anti-Entropy):对于不存在的 ID,必须缓存一个空值副本(带短 TTL),防止非法请求直穿数据库。
- Singleflight(并发坍缩):在热点 Key 失效瞬间,不应允许 100 个 goroutine 同时扑向数据库。第一个请求去回源,其余 99 个在闸门处等待结果,这就是并发回源合并。
2. Redis:内存执行模型与故障语义¶
Redis 之所以成为标准选型,是因为其单线程原子性与毫秒级内存响应。
- 执行模型:Redis 的性能瓶颈通常不在网络,而在大 Key或慢命令(如
KEYS *或大 Hash 全量读取)。这些操作会阻塞事件循环,导致 P99 飙升。 - 持久化权衡:
- RDB (Snapshot):恢复快,但丢数据多。适合对一致性要求不高的副本。
- AOF (Append Only):数据更安全,但 IO 压力大。适合承载业务状态。
- 复制与拓扑:主从保证可用性,Sentinel 负责故障自动切换,Cluster 解决横向扩展。关键直觉:分片不均会导致“热点分片”,单点容量上限仍是系统天花板。
3. 缓存模式:Cache-Aside 是默认选择¶
不要过度设计,先看 Cache-Aside 的受力点。
| 模式 | 逻辑 | 适用场景 | 架构代价 |
|---|---|---|---|
| Cache-Aside | 读时回填,写后失效 | 90% 业务场景 | 需自行处理并发 miss 与回填逻辑 |
| Write-Through | 同步写库与缓存 | 强一致读需求 | 写链路变长,延迟增加 |
| Write-Behind | 先写缓存,异步落库 | 高吞吐/日志流 | 一致性窗口大,存在丢数据风险 |
4. 故障演进:从击穿到雪崩¶
这三个词不是绕口令,而是读路径失守的三个阶梯:
- 击穿 (Breakdown):单个热点 Key 过期,瞬间流量直击 DB。解药:Singleflight / 互斥锁。
- 穿透 (Penetration):大量不存在的 Key 请求直达 DB。解药:布隆过滤器 / 空值缓存。
- 雪崩 (Avalanche):大批 Key 集中过期或 Redis 宕机,全量请求压垮 DB。解药:TTL 随机抖动(Jitter)/ 熔断降级。
5. 排查顺序:第一反应¶
当系统变慢且疑似缓存问题时:
1. 观测命中率:是否出现断崖式下跌?
2. 检查 Redis 监控:是否存在 CPU 飙升(慢命令)或内存打满(驱逐策略触发)?
3. 分析回源压力:DB 的 WaitCount 或 ActiveConn 是否因为缓存失效而暴涨?
4. 校验 Key 语义:是否存在 Key 冲突、租户遗漏或序列化版本不一致?
结论: 好的缓存设计不是为了“快”,而是为了让系统在面对热点流量时,依然拥有可预测的行为和确定的回源压力。