本文已录入至Github,推荐阅览 Java随想录

微信大众号:Java随想录

项目中有遇到这个问题,跟MySQL中的数据不共同,研讨一番发现这里边细节并不简单,特此记录一下。

写在前面

严格意义上任何非原子操作都不或许确保共同性,除非用阻塞读写实现强共同性,所以缓存架构咱们追求的方针是终究共同性。 缓存便是经过献身强共同性来提高性能的

这是由CAP理论决议的。缓存系统适用的场景便是非强共同性的场景,它属于CAP中的AP。

以下3 种缓存读写战略各有好坏,不存在最佳。

三种读写缓存战略

Cache-Aside Pattern(旁路缓存形式)

Cache-Aside Pattern,即旁路缓存形式,它的提出是为了尽或许地处理缓存与数据库的数据不共同问题。

Redis跟MySQL的双写问题

:从缓存读取数据,读到直接回来。假如读取不到的话,从数据库加载,写入缓存后,再回来呼应。 :更新的时分,先更新数据库,然后再删去缓存

Redis跟MySQL的双写问题

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。

很明显,这种方法对数据共同性带来了更大的应战,比方cache数据或许还没异步更新DB的话,cache服务或许就挂掉了,反而会带来更大的灾难。

这种战略在咱们平常开发过程中也十分十分少见,可是不代表它的应用场景少,比方音讯队列中音讯的异步写入磁盘、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,可是给缓存加一个比较短的过期时间,这样的话就能够确保即使数据不共同的话影响也比较小。

本篇文章就到这里,感谢阅览,假如本篇博客有任何错误和主张,欢迎给我留言纠正。文章持续更新,能够关注大众号第一时间阅览。