Redis如何与数据库保持双写一致性
Redis如何与数据库保持双写一致性
双写一致性 指的是,当我们同时使用了数据库(持久化存储)和 Redis(缓存)时,如何保证对一个数据进行更新后,两者存储的数据是相同的。
1.并发问题
-
更新缓存和数据库,如果并发两个写,会因为并发导致数据库和缓存数据不一致。
-
这里可以用旁路缓存策略,也就是说先更新数据库中的数据,再删除缓存中的数据。这个顺序为什么是这样的呢,因为存在一个读写并发,如果先删除缓存,这时候另一个线程来读的话,发现缓存没有,它去数据库读到旧数据,这时候就把旧数据缓存到缓存中,这时候去更新数据库。最终就会发现redis中的数据还是旧的数据,但是数据库更新了,不一致了。
那我们怎么改善这种问题,可以用延迟双删,我们等另一个线程读完旧的数据库的数据,并且写完缓存再删除一次不就解决了吗 -
延迟双删,顾名思义,先删除缓存中的数据,再更新数据库中的数据,然后延迟(睡眠一会),再删除一次。→解决了先删除缓存后更新数据库的问题。
-
当然其实先更新数据库中的数据,再删除缓存中的数据。也存在读写并发,如果我们在缓存中没有读到,就去数据库获取一个旧数据,这时候另一个线程来写数据库,然后删除缓存,我们再把原来从数据库获取到的旧数据缓存在redis中,但是这样有一个问题,就是必须等另一个线程来写数据库,然后删除缓存,都做完之后,再去写缓存,本来写redis就比写sql要快很多,所以这种概率并不高。
-
但是删了缓存的话,命中率变低,如果要求很高,那我们可以更新缓存+数据库(更新缓存+数据库或者更新数据库+缓存,都存在写写并发问题),但是要解决数据库和缓存数据不一致,我们可以采取
-
分布式锁,避免并发问题,写操作时加锁,确保同一数据串行更新。也就是请求执行"更新数据库 → 更新缓存"(先更新数据库再更新缓存,先保证数据权威的来源(数据库),失败处理也合理(缓存失败了,也能后面会过期))的完整操作序列。
-
给缓存加上较短的过期时间,接受出现短暂的缓存不一致,但是缓存的数据反正会很快过期。
2.操作失败问题
Redis和数据库是两个独立系统,可能一个成功一个失败。
更新数据库中的数据,再删除缓存中的数据,但是删除缓存中的数据失败了,其他请求打过来不还是不一致吗。
- 消息队列重试机制。要删除的缓存中的数据加入消息队列,消费者来操作,如果失败,会从消息队列重新读取。
- 订阅 MySQL binlog,再操作缓存。更新数据库成功会产生一条日志,记录在 binlog 里。订阅这个日志,并且通过ACK机制确认处理这条更新log(也是通过消息队列,但是代码侵入性没有那么强)
