阅读了小林哥的文章后收获很大,做个总结用于复习。
目录
前言
造成缓存和数据库的数据不一致的现象,是因为并发问题!当有多个请求同时进行操作时,由于缓存、数据库操作的顺序和时机不同,可能造成不一致的情况。
一、旁路缓存策略
该策略又细分为读策略和写策略
写策略步骤:
- 更新数据库中的数据
- 删除缓存中的数据
读策略步骤:
- 如果读取的数据命中了缓存,则直接返回数据;
- 如果读取的数据没有命中缓存,则从数据库中读取数据,然后将数据写入到缓存中,并且返回给用户。
为了保证写策略的两个步骤成功,我们可以引入消息队列,将第二个操作(删除缓存)要操作的数据加入到消息队列中,由消费者来操作数据。
- 如果应用删除缓存失败,可以从消息队列中重新读取数据,然后再次删除缓存,这个就是重试机制。重试超过一定次数,需要向业务层发送报错信息。
- 如果删除缓存成功,就要把数据从消息队列中移除,避免重复操作。
或者可以通过订阅 MySQL binlog,再操作缓存
二、更新数据库+更新缓存
如果业务对缓存命中率有很高的要求,可以采用更新数据库+更新缓存的方案,因为更新缓存并不会出现缓存未命中的情况。但是因为更新数据库和更新缓存这两个操作是独立的,而我们又没有对操作做任何并发控制,那么当两个线程并发更新他们的话,就会因为写入顺序的不同造成数据的不一致性。
- 更新缓存前先加个分布式锁,保证同一时间只运行一个请求更新缓存,就不会产生并发问题了,不过会对写入的性能带来影响。
- 在更新完缓存时,给缓存加上较短的过期时间,这样即时出现缓存不一致的情况,缓存的数据也会很快过期。
三、删除缓存+更新数据库
删除缓存+更新数据库在并发请求下造成缓存不一致的解决办法是引入延迟双删
#删除缓存
redis.delKey(X)
#更新数据库
db.update(X)
#睡眠
Thread.sleep(N)
#再删除缓存
redis.delKey(X)
加了个睡眠时间,主要是为了确保请求 A 在睡眠的时候,请求 B 能够在这这一段时间完成「从数据库读取数据,再把缺失的缓存写入缓存」的操作,然后请求 A 睡眠完,再删除缓存。
所以,请求 A 的睡眠时间就需要大于请求 B 「从数据库读取数据 + 写入缓存」的时间。