对于缓存 + 数据库读写,有个经典的Cache Aside Pattern:
读取:先读取缓存,缓存里没有,读取数据库,然后返回响应,顺便保存缓存:
更新:先更新数据库,然后删除缓存
为什么是删除缓存而不是更新缓存?
- 并发情况下更新缓存可能会带来更多问题,直接删除缓存更加稳妥
- 缓存更新消耗更多资源,直接删除,用时再从数据库读取,写进缓存,更省性能
先更新数据库,然后删除缓存
一致性问题
假设更新数据库成功,接下来还没来得及删除缓存,或者删除缓存失败,此时其他线程进来读的就是脏数据。
既然删除缓存失败会导致脏数据,那么就得想办法能让它删除成功。一般来说有两种方法:消息队列重试机制和监听binlog异步删除
消息队列重试机制
如果删除缓存失败,向消息队列发送消息,把删除失败的key放进去,消费消息队列,获取要删除的key,然后重试删除。
这样做的话引入了消息队列,对现有的业务造成了入侵,复杂度升高
监听binlog异步删除
用一个服务去监听数据库的binlog,获取需要操作的数据。然后用另外一个服务获取订阅程序传来的信息,进行缓存删除操作。这样对于业务的入侵就小了。
先删除缓存,再更新数据库
在并发情况下,先删除缓存,再更新数据库,此时数据库还未更新成功,这时有其他线程进来,读取缓存,缓存不存在,读取数据库,读取的是就是旧值,此时缓存不一致就发生了。
解决方案便是延时双删:
就是在删除缓存,更新数据库之后,休眠一段时间后,再次删除缓存。延时删除之后,就把缓存里面缓存的旧值删除了。
再有请求进来,就是读取数据库里的新值,再把新值保存进缓存。如果第二次删除也失败,那么就再次重试。