只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题。我们需要保证redis跟数据库的中的数据保持一致,返回正确的数据。
删除缓存,而不是更新缓存
如果更新缓存,在并发写时,可能出现数据不一致。
假设现在同时有请求A和请求B进行更新操作,那么会出现
这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。
如果因为每次数据发生变更,都「无脑」更新缓存,但是缓存中的数据不一定会被「马上读取」,这就会导致缓存中可能存放了很多不常访问的数据,浪费缓存资源。 而且很多情况下,写到缓存中的值,并不是与数据库中的值一一对应的,很有可能是先查询数据库,再经过一系列「计算」得出一个值,才把这个值才写到缓存中。
A线程先成功删除了redis里面的数据,然后去更新mysql,此时mysql正在更新中,还没有结束。(比如网络延时)B突然出现要来读取缓存数据。此时redis里面的数据是空的,B线程来读取,先去读redis里数据(已经被A线程delete掉了)。
上述场景出现的问题:
最后:A线程更新完mysql,发现redis里面的缓存是脏数据
时间 | 线程A | 线程B | 出现的问题 |
---|---|---|---|
t1 | 请求A进行写操作,删除缓存后,工作正在进行中...... | A还更新完mysql,致B读到了旧值 线程B遵守回写机制,把旧值写回redis,导致其它请求读取的还是旧值,A白干了。 | |
t2 | 缓存中读取不到,立刻读mysal,由于A还没有对mysal更新完,读到的是旧值。 还把从mysgl读取的旧值,写回了redis | ||
t3 | 更新mysql数据库的值,over | redis是被B写回的旧值 mysql是被A更新的新值 出现了,数据不一致问题。 |
可以先对缓存的数据先进行删除一次,再处理好数据库的业务以后睡眠一段时间后再进行一次删除。这就是延迟双删。
为什么要sleep一段时间?
加上sleep的这段时间,就是为了让线程B能够先从数据库读取数据,再把缺失的数据写入缓存,然后,线程A再进行删除。所以,线程A Sleep的时间,就需要大于现场B读取数据再写入缓存的时间。这样一来,其他线程读取数据时,会发生缓存缺失,所以会从数据库中读取最新值。因为这个方案会在第一次删除缓存值后,延迟一段时间再去进行删除,所以我们也把它叫做"延迟双删"
如果直接删掉的话,线程B可能还没写进去redis中,线程A写了,然后线程B再写,覆盖掉了。
休眠多久呢?这个时间怎么确定呢?
时间 | 线程A | 线程B | 出现的问题 |
---|---|---|---|
t1 | 删除数据库中的值 | ||
t2 | 缓存中立刻命中,此时B读取的是缓存旧值。 | A还没有来得及删除缓存的值导致B缓存命中读到旧值。 | |
t3 | 更新缓存的数据,over |
异常原因:假如缓存删除失败或者来不及,导致请求再次访问redis时缓存命中,读取到的是缓存旧值