提出问题:在使用美团Squirrel 作为分布式缓存 ,那么缓存与数据库的一致性如何保证?如何尽可能拿到最新数据?(以单节点为例)
方案1:先更新数据库后更新缓存不推荐
分析缺点:
(1)AB两个线程写。A更新数据库->B更新数据库->B更新缓存->A更新缓存 显然出现了数据覆盖,此时数据不一致,是旧数据。
(2)另外在更新数据库的时候还需要更新缓存,如果更新逻辑复杂,导致整个更新周期很长。
解决方案:
对于(1)可以增加版本号,版本号比目前大的才更新。
对于(2)可以考虑任务异步化。
方案2:先删除缓存 再更新数据库
分析缺点:
(1) A 先写B后读。那可能存在这样的序列:A删除缓存->B未命中读数据库后,将其加入缓存->A更新数据库 此时数据发生不一致。
解决办法:
“延迟双删”可以解决,在A线程写入完成之后 ,过M时间再去删除缓存,M时间的大小需要根据B线程将数据库数据放入缓存的时间N 来定,比其稍大即可。
方案3:先更新数据库再删除缓存推荐
分析缺点:
(1)A 先写B后读。假设存在 A先更新数据库->B读->A删除缓存。这样B读到的是过期数据 但是这是可以忍受的,因为这只是短暂的不一致后续的读都是一致数据。
(2)假设某时刻缓存恰好失效,B线程先读,A线程后写。可能存在这样的操作序列B 读数据库->A更新数据库->A 加入缓存->B加入缓存 此时旧值就会覆盖最新值。其实这种情况发生的比较少,因为读操作应该比数据库更新操作快,所以B加入缓存的这一步很大可能是在A 更新数据库之前的操作。
关于删除缓存失败的解决办法?
我们知道不管是延时双删还是一次删除,那么删除失败那么意味着可能发生数据不一致,所以应该循环重试直到成功。
方案1:使用消息队列,在业务代码中应该在更新数据库之后,在将删除任务发到消息队列中,然后正确消费就可以。
方案2:同样适用消息队列,但是利用binlog,我们知道binlog记录改变数据库的操作,所以写操作会被记录,当其变化时就去产生任务放到队列中,减少了业务代码的侵入。比较推荐。