缓存更新军体拳

前言

在日常工作中说实话我没细想过缓存更新会有这么多的细节,一个原因是因为我本身所在平台的量级并发量被不大,二是对自己要求还是不够严谨。

会出现什么样的问题

缓存更新出现问题其实归根揭底是因为维护了 redis 和 mysql 两份数据,出现问题也是他们一致性的问题。常见的场景如下

场景一

一般DataStore更新或者删除会删掉cache而不是去更新它,但是在一些高并发场景这样做会让大量请求打到DataStore上,一种做法还是去更新这个缓存。

如图,当redis缓存过期重新从mysql取值得当前值cache=A1,同一时间运营修改了数据cache的值应该变为A2,脚本获得需要更新的值cache=A2。这样问题就来了 set cache = A1set cache = A2 的执行顺序会影响最终的结果。

场景二

先删除缓存再修改数据库,而后续的操作会把数据再装载的缓存中。这种操作本身就是有问题的。

场景三

先更新数据库,再删除缓存,而后续的操作会把数据再装载的缓存中。这种场景如图

session-1 session-2
update cache=A2 where cache=A1 缓存过期
transaction ing get cache=A1
commit
delete cache=A1
set cache=A1

依然会缓存脏数据,如果感觉例子中的update不是很好理解可以也可以想象成类似主从延时等其他情况。

常见解决方法

Cache Aside

这种方式是通用型的解决方案遵循

  • 读请求

先读chache 如果 miss 用 DataStore 数据回填chache。

  • 写请求

先删除/更新 数据库,再删除cache

主要要点是
 
 1. 读的顺序
 2. 删除cache而不是update(为了避免并发写产生脏数据

Read Through

Cache Aside 是调用方去解决缓存miss的情况, Read Through会依赖其它操作把需要的数据写入cache, 比如异步服务或者依靠运营手动更新缓存。

Write Through

有修改或者删除操作,先操作cache然后cache去更新DataStore,如果miss就直接操作DataStore

Write Behind Caching

page cache 更新策略,操作全部放入cache 按策略后台异步去写DataStore。(参考mysql rodolog 或者 redis AOF)

场景解决方案

场景一

这个场景是违反了Cache Aside 的只删不更新原则,是一个反范式的设计。目的是在高并发下不想缓存miss增加数据库的压力,毛老师在redis中的解决感觉很精巧所以特地记录下来了。

  1. 数据库同步到缓存的脚本用 setex 覆盖写,读缓存失效用 setnx 存在就不写。 setex > setnx, 用类似写优先级的方案解决了这种问题。
  2. Read Through, 把写缓存的操作交给运营手动去执行或者订阅binlog用脚本去更新,程序只用去关心读场景miss了返回空即可。

场景二

先删缓存再更新数据库这种方式尽量避免

场景三

session-1 session-2
update cache=A2 where cache=A1 缓存过期
transaction ing get cache=A1
commit
delete cache=A1
set cache=A1

延时双删,在第一次删除缓存后延时一定时间,再删一次。

session-1 session-2
update cache=A2 where cache=A1 缓存过期
transaction ing get cache=A1
commit
delete cache=A
set cache=A1
sleep(2s)
delete cache=A1

延时删除的时间根据业务场景决定是一种减少概率的规避做法

参考

  1. 极客时间毛老师第4期缓存策略分享
  2. https://coolshell.cn/articles/17416.html
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

请我喝杯咖啡吧~