阅读本文大约需要 10 分钟。
如何保证缓存和数据库一致性,这是一个老生常谈的话题了。
但很多人对这个问题,依旧有很多疑惑:
-
到底是更新缓存还是删缓存?
-
到底选择先更新数据库,再删除缓存,还是先删除缓存,再更新数据库?
-
为什么要引入消息队列保证一致性?
-
延迟双删会有什么问题?到底要不要用?
-
...
这篇文章,我们就来把这些问题讲清楚。
这篇文章干货很多,希望你可以耐心读完。
引入缓存提高性能
我们从最简单的场景开始讲起。
如果你的业务处于起步阶段,流量非常小,那无论是读请求还是写请求,直接操作数据库即可,这时你的架构模型是这样的:
但随着业务量的增长,你的项目请求量越来越大,这时如果每次都从数据库中读数据,那肯定会有性能问题。
这个阶段通常的做法是,引入「缓存」来提高读性能,架构模型就变成了这样:
当下优秀的缓存中间件,当属 Redis 莫属,它不仅性能非常高,还提供了很多友好的数据类型,可以很好地满足我们的业务需求。
但引入缓存之后,你就会面临一个问题:之前数据只存在数据库中,现在要放到缓存中读取,具体要怎么存呢?
最简单直接的方案是「全量数据刷到缓存中」:
-
数据库的数据,全量刷入缓存(不设置失效时间)
-
写请求只更新数据库,不更新缓存
-
启动一个定时任务,定时把数据库的数据,更新到缓存中
这个方案的优点是,所有读请求都可以直接「命中」缓存,不需要再查数据库,性能非常高。
但缺点也很明显,有 2 个问题:
-
缓存利用率低:不经常访问的数据,还一直留在缓存中
-
数据不一致:因为是「定时」刷新缓存,缓存和数据库存在不一致(取决于定时任务的执行频率)
所以,这种方案一般更适合业务「体量小」,且对数据一致性要求不高的业务场景。
那如果我们的业务体量很大,怎么解决这 2 个问题呢?
缓存利用率和一致性问题
先来看第一个问题,如何提高缓存利用率?
想要缓存利用率「最大化」,我们很容易想到的方案是,缓存中只保留最近访问的「热数据」。但具体要怎么做呢?
我们可以这样优化:
-
写请求依旧只写数据库
-
读请求先读缓存,如果缓存不存在,则从数据库读取,并重建缓存
-
同时,写入缓存中的数据,都设置失效时间
这样一来,缓存中不经常访问的数据,随着时间的推移,都会逐渐「过期」淘汰掉,最终缓存中保留的,都是经常被访问的「热数据」,缓存利用率得以最大化。
再来看数据一致性问题。
要想保证缓存和数据库「实时」一致,那就不能再用定时任务刷新缓存了。
所以,当数据发生更新时,我们不仅要操作数据库,还要一并操作缓存。具体操作就是,修改一条数据时,不仅要更新数据库,也要连带缓存一起更新。
但数据库和缓存都更新,又存在先后问题,那对应的方案就有