建议前面文章没看过的同窗先看下前面的文章:java
「老司机带你玩转面试(1):缓存中间件 Redis 基础知识以及数据持久化」git
「老司机带你玩转面试(2):Redis 过时策略以及缓存雪崩、击穿、穿透」github
「老司机带你玩转面试(3):Redis 高可用之主从模式」面试
「老司机带你玩转面试(4):Redis 高可用之哨兵模式」redis
「老司机带你玩转面试(5):Redis 集群模式 Redis Cluster」数据库
这个问题产生的根源是并发写,自己 Redis 是不会产生并发问题的,看过前面文章的同窗应该知道, Redis 的线程模型是单线程的,全部的操做指令都在文件事件处理器的队列中,这个绝对是按照先进先出的原则进行操做的,那么并发竞争的问题是如何产生的?缓存
例如咱们如今有三个客户端 A , B , C ,须要按序像 Redis 中写入或者更新数据 A -> B -> C
,就像下面这样:网络
可是如今忽然 A 的网络抖动了一下,致使 A 并非第一个去 Redis 中写数据的,整个流程的操做顺序变成了 B -> C -> A
,这样数据不就错了么。mybatis
这就比如你在网上买东西,加购物车,下订单,支付订单的顺序变掉了,你先下单,再支付,再加购物车,这个流程根本走不下去的好吧。这种事情发生在线上的系统上的时候,是很是恐怖的。并发
这种状况解决起来其实也很简单,加一个分布式锁就能够了,好比这样:
咱们能够基于 Zookeeper
实现一个全局分布式锁,确保同一时间,只能有一个系统实例在操做某个 key ,别人都不容许读和写。
你要写入缓存的数据,都是从 DB 里查出来的,都得写入 DB 中,写入 DB 中的时候必须保存一个时间戳,从 DB 查出来的时候,时间戳也查出来。
每次要写以前,先判断一下当前这个 Value 的时间戳是否比缓存里的 Value 的时间戳要新。若是是的话,那么能够写,不然,就不能用旧的数据覆盖新的数据。
只要使用缓存,就可能会涉及到缓存与数据库的双写,只要是双写,就必定会有数据一致性的问题。
首先先介绍下经典的 Redis + BD 的读写模式:
这里还会有另一个问题:为何是删除缓存,而不是更新缓存?
缘由很简单,不少时候,在复杂点的缓存场景,缓存不仅仅是数据库中直接取出来的值。
好比可能更新了某个表的一个字段,而后其对应的缓存,是须要查询另外两个表的数据并进行运算,才能计算出缓存最新的值的。
另外更新缓存的代价有时候是很高的。是否是说,每次修改数据库的时候,都必定要将其对应的缓存更新一份?也许有的场景是这样,可是对于比较复杂的缓存数据计算的场景,就不是这样了。若是你频繁修改一个缓存涉及的多个表,缓存也频繁更新。可是问题在于,这个缓存到底会不会被频繁访问到?
举个栗子,一个缓存涉及的表的字段,在 1 分钟内就修改了 20 次,或者是 100 次,那么缓存更新 20 次、100 次;可是这个缓存在 1 分钟内只被读取了 1 次,有大量的冷数据。实际上,若是你只是删除缓存的话,那么在 1 分钟内,这个缓存不过就从新计算一次而已,开销大幅度下降。用到缓存才去算缓存。
其实删除缓存,而不是更新缓存,就是一个 lazy 计算的思想,不要每次都从新作复杂的计算,无论它会不会用到,而是让它到须要被使用的时候再从新计算。像 mybatis,hibernate,都有懒加载思想。查询一个部门,部门带了一个员工的 list,没有必要说每次查询部门,都把里面的 1000 个员工的数据也同时查出来啊。80% 的状况,查这个部门,就只是要访问这个部门的信息就能够了。先查部门,同时要访问里面的员工,那么这个时候只有在你要访问里面的员工的时候,才会去数据库里面查询 1000 个员工。
这种模式应该就是咱们大多数人如今使用的模式,在不是严格要求 「缓存 + 数据库」 必须保持一致的时候,这样作是能够的。
那为何说上面这种方案在严格要求 「缓存 + 数据库」 的场景下不行呢?
由于这个问题只有在对一个数据在并发的进行读写的时候,才可能会出现这种问题。
就是若是说并发量没那么高的话,好比 1s 才一个对缓存的读写请求,那么大几率是不会出现缓存 「缓存 + 数据库」 不一致的状况。
可是问题是,若是天天的是上亿的流量,每秒并发读是几万,每秒只要有数据更新的请求,就可能会出现上述的 「缓存 + 数据库」 不一致的状况。
那么这种问题是否是就没有解决方案,固然有,可是若是不是严格要求 「缓存 + 数据库」 必须保持一致性的场景,最好不要使用这个方案。
咱们可让读请求和写请求串行化,把全部的读写请求都串行到一个队列里面去。
串行化能够保证必定不会出现不一致的状况,可是它也会致使系统的吞吐量大幅度下降,用比正常状况下多几倍的机器去支撑线上的一个请求。
把全部的操做都放到队列里面,顺序确定不会乱,可是并发高了,这队列很容易阻塞,反而会成为整个系统的瓶颈。