为什么redis 需要延迟双删 caffeine不需要
这是因为Redis 和 Caffeine 所处的位置不同以及它们面对的一致性问题不同。简单来说Redis通常是分布式缓存多个应用实例共享Caffeine是本地缓存每个 JVM 自己维护延迟双删主要是为了解决Redis 数据库场景下的缓存一致性问题而 Caffeine 通常不会遇到同样的问题。Redis 为什么会出现脏数据假设采用经典 Cache Aside 模式读 缓存 - 数据库 写 数据库 - 删除缓存有两个线程Thread A读1. 查询Redis未命中 2. 查询MySQLThread B写1. 更新MySQL 2. 删除Redis缓存时序如下A: Redis miss A: 查询DB(旧值) B: 更新DB(新值) B: 删除Redis A: 把旧值重新写入Redis结果MySQL 新值 Redis 旧值缓存被污染了。延迟双删怎么解决更新流程变成更新DB 删除Redis sleep(500ms) 再次删除Redis时序A: Redis miss A: 查DB旧值 B: 更新DB新值 B: 删除Redis A: 写入旧值到Redis B: 延迟后再次删除Redis最终Redis被删掉下一次读取Redis miss ↓ 查DB新值 ↓ 写回Redis恢复一致。为什么 Caffeine 不需要延迟双删因为 Caffeine 是进程内缓存。例如应用A ├── Caffeine └── MySQL更新时updateDB(); caffeine.invalidate(key);此时cache.get(key)和cache.invalidate(key)都在同一个 JVM 内。不存在A线程查数据库 ↓ B线程更新数据库 ↓ A线程把旧值写回缓存这种跨进程竞争窗口通常很容易通过本地锁、原子加载机制解决。更关键的原因Caffeine 有原子加载能力例如LoadingCacheLong, User cache Caffeine.newBuilder() .build(this::loadUser);读取cache.get(id);对于同一个 key多个线程同时miss ↓ 只有一个线程真正回源DB ↓ 其他线程等待结果即所谓Single Flight避免了 Redis 常见的miss ↓ 多个线程同时查库 ↓ 同时回填问题。但如果是「Caffeine Redis」两级缓存呢很多系统Caffeine ↓ Redis ↓ MySQL这时候情况就变了。更新updateDB(); caffeine.invalidate(key); redis.del(key);因为应用A有本地缓存 应用B有本地缓存 应用C有本地缓存删除 A 的 CaffeineB、C 的 Caffeine 还在所以需要Redis 删除本地缓存广播失效MQ、Redis Pub/Sub 等例如更新DB ↓ 删除Redis ↓ 发送失效消息 ↓ 所有节点删除Caffeine这里讨论的已经不是延迟双删而是多级缓存一致性问题。更准确地说很多人说Redis 需要延迟双删Caffeine 不需要。本质原因不是 Redis 和 Caffeine 本身的区别而是Redis多个应用实例共享 缓存重建和写入来自不同进程 存在并发回填旧数据的问题Caffeine缓存只属于当前JVM 提供原子加载机制 容易保证读写顺序 不存在跨进程回填竞争所以在Redis DB 的 Cache Aside 模式下经常会讨论延迟双删而在纯Caffeine DB场景下通常通过invalidate()、原子加载、锁机制就能保证一致性不需要专门的“延迟双删”策略。