先说明一下 Mysql 和 Redis 的关系:Mysql 是数据库,用来长久化数据,肯定水平上保证数据的可靠性;Redis 是用来当缓存,用来晋升数据拜访的性能。
对于如何保障 Mysql 和 Redis 中的数据统一(即缓存一致性问题),这是一个十分经典的问题。
应用过缓存的人都应该晓得,在理论利用场景中,要想实时刻保障缓存和数据库中的数据一样,很难做到。
基本上都是尽可能让他们的数据在绝大部分工夫内保持一致,并保障最终是统一的。
缓存不统一是如何产生的
如果数据始终没有变更,那么就不会呈现缓存不统一的问题。
通常缓存不统一是产生在数据有变更的时候。因为每次数据变更你须要同时操作数据库和缓存,而他们又属于不同的零碎,无奈做到同时操作胜利或失败,总会有一个时间差。在并发读写的时候可能就会呈现缓存不统一的问题(实践上通过分布式事务能够保障这一点,不过实际上基本上很少有人这么做)。
尽管没方法在数据有变更时,保障缓存和数据库强统一,但对缓存的更新还是有肯定设计办法的,遵循这些设计办法,可能让这个不统一的影响工夫和影响范畴最小化。
缓存更新的几种设计
缓存更新的设计办法大略有以下四种:
- 先删除缓存,再更新数据库(这种办法在并发下最容易呈现长时间的脏数据,不可取)
- 先更新数据库,删除缓存(Cache Aside Pattern)
- 只更新缓存,由缓存本人同步更新数据库(Read/Write Through Pattern)
- 只更新缓存,由缓存本人异步更新数据库(Write Behind Cache Pattern)
接下来具体介绍一些这四种设计办法
先删除缓存,再更新数据库
这种办法在并发读写的状况下容易呈现缓存不统一的问题
如上图所示,其可能的执行流程程序为:
客户端 1 触发更新数据 A 的逻辑
客户端 2 触发查问数据 A 的逻辑
客户端 1 删除缓存中数据 A
客户端 2 查问缓存中数据 A,未命中
客户端 2 从数据库查问数据 A,并更新到缓存中
客户端 1 更新数据库中数据 A
可见,最初缓存中的数据 A 跟数据库中的数据 A 是不统一的,缓存中的数据 A 是旧的脏数据。
因而个别不倡议应用这种形式。
先更新数据库,再让缓存生效
这种办法在并发读写的状况下,也可能会呈现短暂缓存不统一的问题
如上图所示,其可能执行的流程程序为:
客户端 1 触发更新数据 A 的逻辑
客户端 2 触发查问数据 A 的逻辑
客户端 3 触发查问数据 A 的逻辑
客户端 1 更新数据库中数据 A
客户端 2 查问缓存中数据 A,命中返回(旧数据)
客户端 1 让缓存中数据 A 生效
客户端 3 查问缓存中数据 A,未命中
客户端 3 查询数据库中数据 A,并更新到缓存中
可见,最初缓存中的数据 A 和数据库中的数据 A 是统一的,实践上可能会呈现一小段时间数据不统一,不过这种概率也比拟低,大部分的业务也不会有太大的问题。
只更新缓存,由缓存本人同步更新数据库(Read/Write Through Pattern)
这种办法相当于是业务只更新缓存,再由缓存去同步更新数据库。一个 Write Through 的 例子如下:
如上图所示,其可能执行的流程程序为:
客户端 1 触发更新数据 A 的逻辑
客户端 2 触发查问数据 A 的逻辑
客户端 1 更新缓存中数据 A,缓存同步更新数据库中数据 A,再返回后果
客户端 2 查问缓存中数据 A,命中返回
Read Through 和 WriteThrough 的流程相似,只是在客户端查问数据 A 时,如果缓存中数据 A 生效了(过期或被驱赶淘汰),则缓存会同步去数据库中查问数据 A,并缓存起来,再返回给客户端
这种形式缓存不统一的概率极低,只不过须要对缓存进行专门的革新。
只更新缓存,由缓存本人异步更新数据库(Write Behind Cache Pattern)
这种形式性详单于是业务只操作更新缓存,再由缓存异步去更新数据库,例如:
如上图所示,其可能的执行流程程序为:
客户端 1 触发更新数据 A 的逻辑
客户端 2 触发查问数据 A 的逻辑
客户端 1 更新缓存中的数据 A,返回
客户端 2 查问缓存中的数据 A,命中返回
缓存异步更新数据 A 到数据库中
这种形式的劣势是读写的性能都十分好,基本上只有操作完内存后就返回给客户端了,然而其是非强一致性,存在失落数据的状况。如果在缓存异步将数据更新到数据库中时,缓存服务挂了,此时未更新到数据库中的数据就失落了。
总结
下面讲到的几种缓存更新的设计形式,都是前人总结进去的教训,这些形式或多或少都有一些弊病,并不完满,实际上也很难有完满的设计。大家在做零碎设计的时候,也不要去谋求完满,要有一些取舍,找到一种最适宜本人业务场景的形式就行
作者:追光者