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 核心对比

维度RedisMemcached
数据结构支持 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 缓存架构对比图

下图展示了本地缓存与分布式缓存在典型微服务架构中的位置关系,以及数据流动路径。

本地缓存与分布式缓存架构对比 本地缓存 vs 分布式缓存 架构对比 本地缓存(进程内) 应用实例 A 本地缓存 (Caffeine) 应用实例 B 本地缓存 (Caffeine) 应用实例 C 本地缓存 (Caffeine) 共享数据库 分布式缓存(跨进程) 应用实例 A 缓存客户端 应用实例 B 缓存客户端 应用实例 C 缓存客户端 Redis 集群 本地缓存延迟微秒级,但数据孤岛;分布式缓存延迟毫秒级,但数据共享,适合跨实例一致性场景

3.4 失效模式总结与应对策略

失效模式触发条件影响范围推荐应对策略
缓存穿透查询不存在的数据数据库压力激增布隆过滤器、缓存空值
缓存击穿热点 key 失效瞬间高并发数据库单点压力互斥锁、逻辑过期
缓存雪崩大量 key 同时过期或节点宕机数据库整体压力过期时间随机化、多级缓存、限流
数据不一致数据库更新后缓存未同步业务数据脏读延迟双删、CDC 监听、版本号
大 Key / 热 Key单 key 数据量过大或访问过于集中节点负载不均、带宽瓶颈拆分大 Key、本地缓存+分布式缓存分层
领域驱动视角:在聚合根内部,优先使用本地缓存加速领域查询;在跨聚合的查询场景(如报表、搜索),使用分布式缓存并配合领域事件的发布/订阅机制,确保缓存失效的及时性。缓存不是银弹,每一次缓存引入都应明确其边界与失效补偿策略。