系统使用缓存的方式是复制数据库中的部分数据到缓存中,然后就可以从缓存中更快地获取数据。但是数据不仅仅有读操作,有时候也需要进行写操作。完成数据写之后,再次使用缓存的时候,需要保证从缓存中读取到的数据是最新的,即保证缓存一致性。
为了保证数据最新,必然要对数据库和缓存都进行更新,这就涉及到两个问题:
- 更新缓存的时候,是修改缓存数据还是直接删除缓存?
- 数据库和缓存先更新哪一个?
先简单分析一下
淘汰缓存:
优点:操作简单,没有复杂的逻辑;
缺点:下一次读取,会带来额外的miss
更新缓存:
优点:下一次直接hit
缺点:可能的额外开销
以上方案排列组合一下共有四种方式:
- 先删除缓存,再更新数据库
- 先更新缓存,再更新数据库
- 先更新数据库,再删除缓存
- 先更新数据库,再更新缓存
理想情况下,四种方式都是可行的,并且删除缓存会带来一次可能的cache miss,从这个角度来看24更优。
但是一个鲁棒的系统,需要关注到那些“不理想”的异常情况:
异常一:更新缓存带来的数据不一致
当更新数据库之后,更新缓存之前,有一个并发的读操作,就会从缓存中读到脏数据。从这个角度来看删除缓存也会有这个问题,所以先对缓存操作会更好一点。
但是如果先删除缓存,仍然可能出现:在删除缓存之后,更新数据库之前,执行读操作。由于更新需要一读一写,会更慢,导致新的缓存仍然是旧数据。
异常二:并发操作带来的数据不一致
两个线程同时更新数据,隐含两个问题:
- ABBA或BAAB带来缓存不一致,删除缓存能解决这一问题
- 最终结果并非是后执行的更新的数据。和更新顺序无关,需要保证串行执行指令。
异常三:后执行的更新出错被强行终止
先更新缓存能保证后续读取到正确的数据,但是这样数据库中会保留错误数据。
先删除缓存的代价是一次miss+错误数据
后更新缓存/删除缓存会一直读取错误数据(由于操作失败)
上述问题没有完美的解决方案,最好设置一个异常检测来确保正常更新。
总结
根据上面的分析,删除缓存以较小的代价解决了数据不一致的问题,但是先执行还是后执行则各有优劣:
- 先淘汰缓存
删除缓存后,肯定后续还要更新缓存
同步更新:读写数据的时候再更新缓存
异步更新:更新数据库后立即异步更新缓存
如果是同步更新缓存,在完成数据库更新前,会有一大段时间读取到脏数据
如果是异步更新缓存,可以保证一致性,但是在数据库更新完成之前,不存在缓存,每次都需要从数据库中读取,读的效率不高。
适合需要确保数据一致性的需求下使用
- 后淘汰缓存
更新数据库期间,会有一段时间数据不一致,但是读的效率很好。
适合追求效率,但是对数据一致性要求没那么高的需求下使用