缓存用于缓解后端db的压力,策略指的是更新缓存以及db的方式。
主要可以分为两个大类:
调用方主动更新缓存以及db:
这种是最最常见也是最最容易想到的方式。即调用端需要同时维护db和缓存的调用,调用端逻辑比较复杂。
读取:先读缓存,缓存未命中读取db然后回写缓存;
写入:同时写入,有很多种。分类标准:db和缓存的先后;缓存失效还是更新。
其实,对写入而言,不论使用什么方式,都会有一些问题存在,因为无法保证db和缓存的更新是原子性的。
但是通常,使用先更新db再让缓存失效可以最大程度得降低问题出现的概率。
为什么?
首先看第二个维度,是失效还是更新?
如果用更新会有什么问题?
两个并发的更新操作会出现写入覆盖的问题,其实与mysql的事务比较相似。
如果不用锁加同步,这个问题无法避免。
如下:
update1: db cache
update2: db cache
类似上面这种并发更新,不论是先缓存还是先db,总会有db与缓存数据不一致的情况。
所以应该使用失效而非更新,只不过失效会增加一次miss而已,并无大碍。
那么是先让缓存失效还是先更新db?
应该先更新db,因为假如先失效缓存,如下序列:
update: delete cache update db
select: select db, set cache
这样,读操作就会把一个之前的脏值放入缓存中,导致缓存db不一致。
如果先更新db:
update: update db delete cache
select:cache miss select db set cache
这种情况也会导致缓存不一致,但是这种情况的条件要苛刻一些,因为update db和delete cache必须发生在select db 和 set cache之间才行。而update db执行一般比select db久,那么select db先发生,但是update db和delete cache已经结束时select db还没有结束导致无法set cache的概率就比较低。
当然,无论如何还是有脏数据的可能。
所以,为缓存设置一个超时时间是必须的,这样可以防止脏数据一直存在而无法被更新。
调用方只更新其中一个:
这种方式将会大大降低调用方的复杂度,读取和上面的没有区别,只是写入有区别,调用方仅仅需要更新一处,即可。
更新缓存:写入操作只更新缓存,然后由缓存更新到db。
更新db:写入操作只更新db,然后监听db的变更,比如说mysql的binlog,监听以后再更新缓存。
参考:https://juejin.im/post/5af5b2c36fb9a07ac65318bd
下面记录下监听mysql的binlog来更新或者删除缓存的考虑:
缺点:相比较于直接更新缓存,监听binlog从设计上来说更加复杂,另外可能会有延迟;
优点:解放业务代码,我业务代码涉及到更新的地方只需要改db即可,缓存更新不需要考虑。当然有人会说,你这样只是不在业务代码里写了而已,但是更新缓存的逻辑你还是需要在监听binlog的代码里写啊,该写的代码还是没少啊。对,是没少,但是,区别在于,你思考或者写一个更新逻辑时,完完全全可以把更新缓存与db分开,这是很重要的。否则,你需要一直考虑,我的db写入成功没?我写缓存是异步还是同步?等等这样的问题。而监听binlog的方式就不需要这样想,我只管写db就行,能监听到binlog就说明一定写入成功。这其实是一种解耦。
另外还有一个优点就是,如果要做数据迁移,不用binlog的方式可能需要同时迁移缓存数据,但是有了binlog,只迁移db数据就行。
再比如删除脏数据等。
总之,监听binlog方式最大的优点就是解耦,只关注db即可,缓存自动跟着db更新。