redis-缓存数据库双写一致性方案解析

从理论上来说,设置过期时间是保证缓存数据库最终一致性的解决方案。在这种方案下,我们可以对存入缓存的数据设置过期时间,所有写操作以数据库为准,对缓存操作知识尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,后面的请求自然会从数据库中读取新值然后填回缓存。因此,接下来讨论的思路不依赖于给缓存设置过期时间这个方案。

本文讨论三种更新策略:

  • 先更新数据库,再更新缓存
  • 先删除缓存,再更新数据库
  • 先更新数据库,再删除缓存

没有先更新缓存再更新数据库的方案,因为所有的写操作要以数据库为准,这种情况下若更新数据库失败,缓存失效后再次读数据库将取得旧值。

1、先更新数据库,再更新缓存

该方案从线程安全角度

假设同时有请求A和请求B进行更新操作,如下图所示的情况下最终数据库中的数据是B请求的数据,缓存中的数据数A请求的数据,最终出现了不一致的情况。这种情况因为网络情况等原因是可能出现的

更更

该方案从业务场景角度

  1. 如果是一个写多读少的场景,使用这种方案会导致数据压根没读到,缓存就被频繁的更新,浪费性能
  2. 如果写入db的值需要经过一系列复杂的计算再写入缓存,那么每次写入缓存前都需要计算缓存值,无疑是在浪费性能

所以,更新缓存不可取,删除缓存更合适。

2、先删除缓存,再更新数据库

首先看该方案会导致不一致的情况:

  • A 删除缓存,还没及时更新db
  • B 读db,并将旧值写入缓存
  • A 更新新值到db

这种情况就会导致缓存与db数据不一致的情形出现,而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。

那么,如何解决呢?

延时双删策略

1
2
3
4
5
6
public void write(String key,Object data){
redis.delKey(key);
db.updateData(data);
Thread.sleep(1000);
redis.delKey(key);
}

说明:
(1)先淘汰缓存
(2)再写数据库
(3)休眠1秒,再次淘汰缓存
这么做,可以将1秒内所造成的缓存脏数据,再次删除。
那么,这个1秒怎么确定的,具体该休眠多久呢?
针对上面的情形,应该自行评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

3、先更新数据库,再删除缓存

首先,先说一下。老外提出了一个缓存更新套路,名为《Cache-Aside pattern》。其中就指出

  • 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
  • 命中:应用程序从cache中取数据,取到后返回。
  • 更新:先把数据存到数据库中,成功后,再让缓存失效。

另外,知名社交网站facebook也在论文《Scaling Memcache at Facebook》中提出,他们用的也是先更新数据库,再删缓存的策略。

这种情况不存在并发问题么?
不是的。假设这会有两个请求,一个请求A做更新操作,一个请求B做查询操作,那么会有如下情形产生
更删
如果发生上述情况,确实是会发生脏数据。

然而,发生这种情况的必要条件是
1、B读db时A还没有完成写db,这样B才能读到旧数据

2、A写db比B读db先完成,这样A才会在B更新缓存之前删缓存

因此只有在B请求读db成功但还没有更新缓存之前,A请求更新db结束并执行了删缓存操作,才有可能发生以上的情况。