本文已收录至 Github,举荐浏览 👉 Java 随想录
微信公众号:Java 随想录
我的项目中有遇到这个问题,跟 MySQL 中的数据不统一,钻研一番发现这外面细节并不简略,特此记录一下。
写在后面
严格意义上任何非原子操作都不可能保障一致性,除非用阻塞读写实现强一致性,所以缓存架构咱们谋求的指标是最终一致性。
缓存就是通过就义强一致性来进步性能的。
这是由 CAP 实践决定的。缓存零碎实用的场景就是非强一致性的场景,它属于 CAP 中的 AP。
以下 3 种缓存读写策略各有优劣,不存在最佳。
三种读写缓存策略
Cache-Aside Pattern(旁路缓存模式)
Cache-Aside Pattern,即旁路缓存模式,它的提出是为了尽可能地解决缓存与数据库的数据不统一问题。
读 :从缓存读取数据,读到间接返回。如果读取不到的话,从数据库加载,写入缓存后,再返回响应。
写:更新的时候,先 更新数据库,而后再删除缓存。
Read-Through/Write-Through(读写穿透)
Read/Write Through Pattern 中服务端把 cache 视为次要数据存储,从中读取数据并将数据写入其中。cache 服务负责将此数据读取和写入 DB,从而加重了应用程序的职责。
因为咱们常常应用的分布式缓存 Redis 并没有提供 cache 将数据写入 DB 的性能,所以应用并不多。
写 :先查 cache,cache 中不存在,间接更新 DB。cache 中存在,则先更新 cache,而后 cache 服务本人更新 DB( 同步更新 cache 和 DB)。
读:从 cache 中读取数据,读取到就间接返回。读取不到的话,先从 DB 加载,写入到 cache 后返回响应。
Write Behind Pattern(异步缓存写入)
Write Behind Pattern 和 Read/Write Through Pattern 很类似,两者都是由 cache 服务来负责 cache 和 DB 的读写。
然而,两个又有很大的不同:Read/Write Through 是同步更新 cache 和 DB,而 Write Behind Caching 则是只更新缓存,不间接更新 DB,而是改为异步批量的形式来更新 DB。
<font color=OrangeRed> 很显著,这种形式对数据一致性带来了更大的挑战,比方 cache 数据可能还没异步更新 DB 的话,cache 服务可能就挂掉了,反而会带来更大的劫难。</font>
这种策略在咱们平时开发过程中也十分十分少见,然而不代表它的利用场景少,比方音讯队列中音讯的异步写入磁盘、MySQL 的 InnoDB Buffer Pool 机制都用到了这种策略。
Write Behind Pattern 下 DB 的写性能十分高,非常适合一些数据常常变动又对数据一致性要求没那么高的场景,比方浏览量、点赞量。
旁路缓存模式解析
Cache Aside Pattern 的一些疑难
旁路缓存模式是咱们平时中应用最多的。上面依据下面介绍的旁路缓存模式,咱们能够有以下几个疑难。
为什么写操作是删除缓存,而不是更新缓存
答:线程 A 先发动一个写操作,第一步先更新数据库。线程 B 再发动一个写操作,第二步更新了数据库,因为网络等起因,线程 B 先更新了缓存,线程 A 更新缓存。
这时候,缓存保留的是 A 的数据(老数据),数据库保留的是 B 的数据(新数据),数据 不统一 了,脏数据呈现啦。如果是 删除缓存取代更新缓存 则不会呈现这个脏数据问题。
实际上要写操作的时候更新缓存也是能够的,不过咱们须要加一个锁 / 分布式锁来保障更新 cache 的时候不存在线程平安问题。
在写数据的过程中,为什么要先更新 DB 在删除缓存
答:比如说申请 1 是写操作,要是先删除缓存 A,申请 2 是读操作,先读缓存 A,发现缓存被删除了(被申请 1 删除了),而后去读数据库,然而此时申请 1 还没来得及把数据及时更新,那么申请 2 读的就是旧数据,并且申请 2 还会把读到的旧数据放到缓存中,造成了数据的不统一。
其实要先删缓存,再更新数据库也是能够,如采纳 延时双删策略
休眠 1 秒,再次淘汰缓存 这么做,能够将 1 秒内所造成的缓存脏数据,再次删除。不肯定是 1 秒,看你业务决定的,不过不举荐这种做法,因为在这 1 秒内可能产生因素很多,它的不确定性太大。
在写数据的过程中,先更新 DB,后删除 cache 就没有问题了么?
答: 实践上来说还是可能会呈现数据不一致性的问题,不过概率十分小。
假如这会有两个申请,一个申请 A 做查问操作,一个申请 B 做更新操作,那么会有如下情景产生
(1)缓存刚好生效
(2)申请 A 查询数据库,得一个旧值
(3)申请 B 将新值写入数据库
(4)申请 B 删除缓存
(5)申请 A 将查到的旧值写入缓存 ok,如果产生上述情况,的确是会产生脏数据。
然而,产生这种状况的概率并不高
产生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。
可是,认真想想,数据库的读操作的速度远快于写操作的(不然做读写拆散干嘛,做读写拆散的意义就是因为读操作比拟快,耗资源少),因而步骤(3)耗时比步骤(2)更短,这一情景很难呈现。
还有其余造成不统一的起因么?
答: 如果删除缓存过程中失败了就会造成不统一问题
如何解决?
应用 Canal 去订阅数据库的 binlog,取得须要操作的数据。另起一个程序,取得这个订阅程序传来的信息,进行删除缓存操作。
Cache Aside Pattern 的缺点
缺点 1:首次申请数据肯定不在 cache 的问题
解决办法:能够将热点数据提前放入 cache 中。
缺点 2:写操作比拟频繁的话导致 cache 中的数据会被频繁被删除,这样会影响缓存命中率。
- 数据库和缓存数据强统一场景:更新 DB 的时候同样更新 cache,不过咱们须要加一个锁 / 分布式锁来保障更新 cache 的时候不存在线程平安问题。
- 能够短暂地容许数据库和缓存数据不统一的场景:更新 DB 的时候同样更新 cache,然而给缓存加一个比拟短的过期工夫,这样的话就能够保障即便数据不统一的话影响也比拟小。
本篇文章就到这里,感激浏览,如果本篇博客有任何谬误和倡议,欢送给我留言斧正。文章继续更新,能够关注公众号第一工夫浏览。