3. 缓存基础与常见模式:缓存穿透、缓存击穿、缓存雪崩、旁路缓存、读写穿透
在领域驱动设计中,缓存是保障聚合(Aggregate)查询性能与数据库一致性的关键战术工具。本章聚焦缓存的基础故障模式与标准读写模式,为后续定义“一致性边界”奠定技术基础。
3.1 三大缓存故障模式
缓存穿透、缓存击穿、缓存雪崩是分布式缓存最常见的三类故障,直接影响数据库负载与系统可用性。下表从触发原因、影响范围、典型场景三个维度进行对比:
| 故障模式 | 触发原因 | 影响范围 | 典型场景 |
|---|---|---|---|
| 缓存穿透 | 查询一个一定不存在的数据(如无效ID) | 单个请求穿透至数据库,但可被恶意利用放大 | 攻击者遍历不存在的用户ID |
| 缓存击穿 | 热点 key 在缓存过期瞬间,大量并发请求同时穿透 | 单个热点 key 失效导致数据库瞬时高负载 | 秒杀商品详情页的缓存失效 |
| 缓存雪崩 | 大量 key 在同一时间批量过期,或缓存节点宕机 | 全局性数据库压力激增,可能导致级联故障 | 缓存服务重启后大量 key 同时过期 |
3.1.1 缓存穿透
当请求查询一个缓存和数据库中都不存在的数据时,每次请求都会绕过缓存直达数据库。若攻击者利用此漏洞大量请求无效 key,数据库可能直接被压垮。
解决方案:
- 缓存空值:将 null 结果也缓存,设置较短 TTL(如 30~60 秒)。
- 布隆过滤器:在缓存前置一层 Bloom Filter,快速判断 key 是否可能存在。
- 参数校验:在应用层拦截明显不合法的请求(如 ID ≤ 0)。
3.1.2 缓存击穿
与穿透不同,击穿针对的是热点 key。当该 key 恰好过期时,大量并发请求同时发现缓存缺失,全部涌向数据库重建缓存。
关键指标:热点 key 的 QPS 通常占整体请求的 30% 以上,失效瞬间数据库连接数可能飙升 10 倍。
常用解决方案包括:
- 互斥锁(Mutex):只允许一个线程去数据库加载数据,其他线程等待或降级。
- 逻辑过期:缓存永不过期,但数据内嵌过期时间字段,异步线程负责刷新。
- 热点 key 预热:在流量高峰前主动加载热点数据到缓存。
3.1.3 缓存雪崩
雪崩是击穿的放大版。当大量 key 在同一时刻过期,或缓存节点整体宕机时,数据库会收到远超其处理能力的请求。
避坑提示:不要将所有 key 的 TTL 设置为相同的固定值。即使业务上要求统一过期,也应加入随机偏移量(如基础 TTL + 随机 0~300 秒)。
核心防御策略:
- TTL 随机化:在基础过期时间上增加 ±20% 的随机偏移。
- 缓存高可用:使用 Redis Sentinel 或 Cluster 避免单点故障。
- 限流降级:在数据库访问层前置熔断器(如 Hystrix、Sentinel)。
- 本地缓存兜底:在应用 JVM 内缓存热点数据,作为分布式缓存失效的第二道防线。
3.2 缓存读写标准模式
在领域驱动设计的应用层与基础设施层之间,缓存模式决定了数据流的走向与一致性保证强度。以下两种模式是业界最通用的实践。
3.2.1 旁路缓存(Cache-Aside)
旁路缓存是应用最广泛的模式,应用代码同时管理缓存和数据库的读写。其核心流程如下:
- 读请求:先查缓存,命中则直接返回;未命中则查数据库,将结果写入缓存后返回。
- 写请求:先更新数据库,然后删除缓存(而非更新缓存)。
为什么写操作要删除缓存而不是更新? 因为并发环境下,更新缓存可能引入“脏数据”。删除缓存让下一次读请求自然重建,能有效避免写写冲突与读写冲突。
旁路缓存的优缺点:
- 优点:实现简单,缓存与数据库解耦,适合读多写少的场景。
- 缺点:存在“删除缓存后、重建缓存前”的短暂不一致窗口;应用层需处理缓存删除失败的重试逻辑。
3.2.2 读写穿透(Read/Write-Through)
读写穿透模式将缓存抽象为数据存储的主访问层,应用只与缓存交互,由缓存组件负责与数据库同步。
- 读穿透:缓存未命中时,缓存组件自动从数据库加载数据并返回。
- 写穿透:应用写入缓存,缓存组件同步或异步将数据写入数据库。
适用场景:当领域层希望屏蔽基础设施细节,或使用 Redis 企业版、GemFire 等支持 Write-Through 的缓存中间件时,读写穿透能简化应用代码。
两种模式的核心差异对比:
| 对比维度 | 旁路缓存(Cache-Aside) | 读写穿透(Read/Write-Through) |
|---|---|---|
| 控制权 | 应用代码显式管理缓存 | 缓存组件代理数据库操作 |
| 一致性窗口 | 存在短暂不一致(删除缓存后) | 同步模式下一致性更强 |
| 实现复杂度 | 低,只需缓存客户端 | 中高,需要缓存中间件支持 |
| 领域层侵入 | 基础设施层需感知缓存逻辑 | 领域层完全无感知 |
| 典型选型 | Redis + 应用层代码 | Redis 企业版 / Hazelcast |
3.3 模式选型与一致性边界初探
在领域驱动设计中,缓存模式的选择直接影响聚合一致性边界的定义:
- 旁路缓存适合最终一致性要求较高的查询场景(如商品列表、用户资料),领域层通过 Repository 接口控制缓存失效时机。
- 读写穿透适合强一致性或读写对称的场景(如库存扣减、账户余额),但需要缓存中间件支持事务性写入。
- 故障模式防御(穿透/击穿/雪崩)应作为基础设施层的横切关注点,通过 AOP 或中间件配置统一治理,不侵入领域逻辑。
实践建议:在 DDD 分层架构中,将缓存故障防御逻辑封装在基础设施层的
CacheRepository 装饰器内,领域层只依赖 Repository 接口,不感知缓存策略。这样当缓存模式切换时,领域模型无需改动。