3. 缓存基础:本地缓存、分布式缓存(Redis/Memcached)的原理、适用场景及常见失效模式
在领域驱动设计的落地过程中,缓存是连接聚合边界与高性能查询的关键桥梁。理解不同缓存类型的原理与失效模式,是设计缓存一致性策略的前提。本章从本地缓存与分布式缓存两个维度展开,重点剖析其核心机制、适用场景及典型失效问题。
3.1 本地缓存:进程内的快速存取
本地缓存(如 Caffeine、Guava Cache、Ehcache)将数据存储在应用程序的堆内存中,与业务进程共享同一地址空间。其核心优势在于零网络开销,访问延迟通常在微秒级。
3.1.1 工作原理
- 数据存储:基于 ConcurrentHashMap 或类似结构,以键值对形式驻留 JVM 堆内。
- 淘汰策略:常见策略包括 LRU(最近最少使用)、LFU(最不经常使用)、FIFO(先进先出)及基于时间的 TTL/TTI。
- 线程安全:通过读写锁或分段锁保证并发安全,避免数据竞争。
3.1.2 适用场景
| 场景 | 说明 | 典型示例 |
|---|---|---|
| 读多写少的热点数据 | 数据变化频率低,访问量极高 | 领域枚举、配置字典、用户权限快照 |
| 延迟敏感型查询 | 要求微秒级响应,无法容忍网络抖动 | 实时风控规则匹配、高频交易参数 |
| 单体应用或小规模集群 | 节点数少,无需跨进程共享缓存 | 单机版后台管理系统、嵌入式设备 |
3.1.3 常见失效模式
- 内存溢出:未设置最大容量或淘汰策略不当,导致堆内存持续增长,触发 Full GC 甚至 OOM。
- 数据不一致:多实例部署时,每个节点持有独立缓存副本,更新操作无法广播,造成节点间数据不一致。
- 缓存穿透:查询不存在的数据,每次请求都穿透缓存直达数据库,导致数据库压力骤增。
- 缓存雪崩:大量缓存同时过期,请求瞬间涌向后端,引发数据库负载尖峰。
领域设计警示:在 DDD 的聚合边界内使用本地缓存时,务必确保缓存数据属于同一聚合实例。跨聚合的本地缓存极易引发一致性问题,建议优先考虑分布式缓存。
3.2 分布式缓存:跨进程的共享存储
分布式缓存(以 Redis 和 Memcached 为代表)将数据存储在独立的缓存服务进程中,通过网络协议与业务应用交互。它解决了本地缓存的“单机孤岛”问题,但引入了网络延迟和序列化开销。
3.2.1 Redis 与 Memcached 核心对比
| 维度 | Redis | Memcached |
|---|---|---|
| 数据结构 | 支持 String、Hash、List、Set、Sorted Set、Bitmap、HyperLogLog 等 | 仅支持简单的 Key-Value 结构 |
| 持久化 | 支持 RDB 快照和 AOF 日志,可做数据恢复 | 不支持持久化,重启后数据全部丢失 |
| 淘汰策略 | 8 种策略,包括 LRU、LFU、TTL、随机淘汰等 | 仅 LRU 淘汰 |
| 集群模式 | 原生 Cluster、Sentinel、Proxy 方案 | 客户端一致性哈希,无原生集群 |
| 典型延迟 | 毫秒级(P99 通常 < 5ms) | 毫秒级(P99 通常 < 3ms) |
| 适用场景 | 复杂数据结构、排行榜、分布式锁、消息队列 | 简单 KV 缓存、Session 共享、页面片段缓存 |
3.2.2 工作原理
- 数据分片:通过一致性哈希或槽位分配(Redis Cluster 的 16384 个槽),将数据均匀分布到多个节点。
- 主从复制:主节点负责写操作,从节点负责读操作,实现读写分离与高可用。
- 哨兵/集群:Sentinel 提供故障自动切换;Cluster 提供无中心化的数据分片与自动故障转移。
3.2.3 常见失效模式
- 缓存击穿:某个热点 key 在失效瞬间,大量并发请求同时穿透到数据库。解决方案:互斥锁(Mutex Key)或逻辑过期时间。
- 缓存穿透:查询一个根本不存在的数据,缓存层和数据库层都未命中。解决方案:布隆过滤器(Bloom Filter)或缓存空值(Null Object)。
- 缓存雪崩:大量 key 在同一时间过期,或缓存节点宕机,导致请求全部落到数据库。解决方案:过期时间加随机偏移、多级缓存、限流降级。
- 数据不一致:数据库更新后,缓存未及时失效或更新,导致脏数据。解决方案:Cache-Aside 模式下的延迟双删、CDC(Change Data Capture)监听 binlog。
- 大 Key 与热 Key:单个 key 存储超大 value(如 > 10MB)或单个 key 被超高并发访问,导致节点负载不均或网络带宽打满。
实践建议:在领域服务中,优先使用 Cache-Aside 模式(先更新数据库,再删除缓存)。对于写密集型聚合,可考虑 Write-Behind 模式,但需配合消息队列保证最终一致性。
3.3 缓存架构对比图
下图展示了本地缓存与分布式缓存在典型微服务架构中的位置关系,以及数据流动路径。
3.4 失效模式总结与应对策略
| 失效模式 | 触发条件 | 影响范围 | 推荐应对策略 |
|---|---|---|---|
| 缓存穿透 | 查询不存在的数据 | 数据库压力激增 | 布隆过滤器、缓存空值 |
| 缓存击穿 | 热点 key 失效瞬间高并发 | 数据库单点压力 | 互斥锁、逻辑过期 |
| 缓存雪崩 | 大量 key 同时过期或节点宕机 | 数据库整体压力 | 过期时间随机化、多级缓存、限流 |
| 数据不一致 | 数据库更新后缓存未同步 | 业务数据脏读 | 延迟双删、CDC 监听、版本号 |
| 大 Key / 热 Key | 单 key 数据量过大或访问过于集中 | 节点负载不均、带宽瓶颈 | 拆分大 Key、本地缓存+分布式缓存分层 |
领域驱动视角:在聚合根内部,优先使用本地缓存加速领域查询;在跨聚合的查询场景(如报表、搜索),使用分布式缓存并配合领域事件的发布/订阅机制,确保缓存失效的及时性。缓存不是银弹,每一次缓存引入都应明确其边界与失效补偿策略。