1.如何保证缓存一致性?
使用缓存代表不需要强一致性,只需要最终一致性
1.1数据库和缓存数据强一致场景:
同步双写:更新 DB 时同样更新 cache,保证在一个事务中,通过加锁来保证更新 cache 时不存在线程安全问题
延迟双删:先淘汰缓存再写数据库,休眠 1 秒再次淘汰缓存,可以将 1 秒内造成的缓存脏数据再次删除
异步通知:
- 基于 MQ 的异步通知:对数据的修改后,代码需要发送一条消息到 MQ 中,缓存服务监听 MQ 消息
- Canal 订阅 MySQL binlog 的变更上报给 Kafka,系统监听 Kafka 消息触发缓存失效,或者直接将变更发送到处理服务,没有任何代码侵入
低耦合,可以同时通知多个缓存服务,但是时效性一般,可能存在中间不一致状态
1.2低一致性场景:
- 更新 DB 的时候同样更新 cache,但是给缓存加一个比较短的过期时间,这样就可以保证即使数据不一致影响也比较小
- 使用 Redis 自带的内存淘汰机制
2.先操作数据库还是先操作redis
在保证Redis缓存一致性时,优先选择先更新数据库再删除缓存(Cache-Aside Pattern),这是业界主流方案。
2.1为什么先更新数据库再删除缓存?
- 逻辑合理性:数据库是数据的唯一权威来源,缓存应作为数据库的从属。先更新数据库可确保数据源头的正确性.
- 并发安全:若先删除缓存再更新数据库,在并发场景下可能导致旧数据回填缓存,例如:请求A把缓存删了,还没来得及更新数据库,请求B读取了数据库旧值并写入缓存(这里可以想象缓存是永不过期的),而先更新数据库再删除缓存能降低此类风险。
3.延迟双删
public void write(String key, Object data) {
redis.del(key); // 第一次删除
db.update(data); // 更新数据库
Thread.sleep(1000); // 等待读请求完成
redis.del(key); // 第二次异步删除
}