Redis和DB的一致性
一,方案
- 先更新数据库,更新缓存。不考虑
(1)线程A更新了数据库
(2)线程B更新了数据库
(3)线程B更新了缓存
(4)线程A更新了缓存
结论:导致线程B更新缓存的数据丢失,产生脏数据
(1)写多读少的场景:DB通常是写磁盘,Redis通常是写内存,磁盘速度比内存的写慢,在DB压根没写完,就频繁写内存导致性能浪费
(2)如果写DB的值并不是直接写入Redis的,还需要经过复杂计算,无疑是浪费性能,显然删缓存更为合适
- 先删除缓存,再更新数据库。常用
(1)请求A进行写操作,删除缓存
(2)请求B查询发现缓存不存在
(3)请求B去数据库查询得到旧值
(4)请求B将旧值写入缓存
(5)请求A将新值写入数据库 上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。
public void write(String key,Object data){
redis.delKey(key);
db.updateData(data);
Thread.sleep(1000);
redis.delKey(key);
}
(1)请求A进行写操作,删除缓存
(2)请求A将数据写入数据库了,
(3)请求B查询缓存发现,缓存没有值
(4)请求B去从库查询,这时,还没有完成主从同步,因此查询到的是旧值
(5)请求B将旧值写入缓存
(6)数据库完成主从同步,从库变为新值 上述情形,就是数据不一致的原因。
解决方案还是使用延时双删策略。只是,睡眠时间修改为在主从同步的延时时间基础上,加几百ms。
解决方案:删除缓存改成异步删除,将删除缓存操作另起一个线程,异步写入
解决方案:将删除缓存操作放入MQ队列,利用MQ队列的重试机制/此处最好是自己用代码控制MQ的ack机制和重试机制
结论:读写分离架构下,基本还是使用延时双删策略,不过延时删除缓存会导致吞吐量降低,所以延时删除改为,MQ的异步删除缓存,利用MQ的ack机制和重试机制确保代码的健壮性,最好用本地消息表去控制ack机制,重试机制和去重机制,若使用MQ出现消息积压的情况,说明生产消息速度 > 消费消息速度,可通过增加消费者数量 去解决,注意MQ需设置备份策略,以保证MQ宕机的情况减少消息的丢失
- 先更新数据库,再删除缓存,也称旁路缓存