按照缓存更新的方式大致分为: 内存淘汰、过期删除、主动更新。
利用Redis
的内存淘汰策略,当内存不足时自动进行淘汰部分数据,下次查询时更新缓存,一致性差,无维护成本。
因为Redis
是基于内存的,如果内存超过限定值(Redis
配置文件的maxmemory
参数决定Redis
最大内存使用量),导致新的数据存不进去,此时Redis
会根据淘汰策略删除一些数据。
淘汰策略由Redis
配置文件的maxmemory-policy
参数决定设置,默认为noeviction
模式。
淘汰策略的执行过程:
Redis
会检查内存使用情况,内存使用超过限定值,按照淘汰策略删除key
。Redis
写入新数据。具体的淘汰策略:redis.windows-service.conf
中可以查到
key
中使用LRU
算法(最近最少使用算法)淘汰最久没有使用过的key
。key
中使用LRU
算法淘汰最久没有使用过的key
。key
中随机淘汰key
。key
中随机淘汰key
。key
中根据过期时间淘汰key
,越快过期越早淘汰。key
中使用LFU
算法(最少频率访问算法)淘汰使用频率最低的key
。key
中使用LFU
算法淘汰使用频率最低的key
。缓存添加过期时间,到期后根据过期删除策略自动进行删除缓存,下次查询时更新缓存,一致性一般,维护成本低。
key
设置了过期时间,一旦过期立即删除。
key
一旦过期就会立即删除,不会占用内存。key
较多时,删除key
会占用CPU
时间,影响服务器的响应时间,吞吐量,性能。key
不会马上被删除,而是继续保存在内存中,当key
被访问时检查key
的过期时间,若已过期则删除。
key
的过期时间,没使用的key
不会占用CPU
的时间去检查过期时间,不会影响服务器的响应时间,吞吐量,性能。key
继续保存在内存中,导致内存不会被释放,消耗内存资源。Redis
配置文件的hz
参数表示1s执行多少次定期删除策略,默认值10),随机抽取设置了过期时间的key
检查它们的过期时间,删除已过期的key
。
CPU
性能的影响,定期删除也能释放没有被访问的过期key
占用的内存。CPU
的性能,频率低过期key
占用的内存不会及时释放。应用程序中修改DB
,修改缓存,一致性好,维护成本高。
主动更新大致分为: Cache Aside Pattern
、Read/Write Through Pattern
、Write Behind Caching Pattern
。
DB
的读写。读写操作步骤:
DB
,然后把读的DB
数据存入缓存,返回。DB
,再删除缓存。DB
的读写。
DB
,然后把读的DB
数据存入缓存,返回。DB
和缓存。DB
的读写,通过定时或阈值的异步方式将数据同步到DB
,保证最终一致。该模式和Read/Write
模式相似,不同点在于Read/Write
模式更新DB
和更新缓存是同步的,而Write Behind Caching Pattern
模式更新DB
和更新缓存是异步的。
DB
的频率,读写响应非常快,吞吐量也会有明显的提升。DB
过程服务不可用,导致数据丢失。三种主动更新策略的对比:
策略 | 说明 | 优点 | 缺点 |
---|---|---|---|
Cache Aside Pattern | 应用程序负责缓存和DB 的读写 |
使用简单,直接操作缓存和DB |
需要编写对缓存和DB 读写的代码 |
Read/Write Through Pattern | 应用程序只与缓存管理组件交互,缓存管理组件负责缓存和DB 的读写 |
使代码更简洁 | 缓存管理组件需要提供对DB 和缓存读写的方法 |
Write Behind Caching Pattern | 应用程序只与缓存管理组件交互,缓存管理组件负责缓存和DB 的读写 |
性能最好,在高并发场景下可以降低数据库的压力 | 缓存管理组件,需要提供对DB 和缓存读写的方法;不能实时同步,数据同步 DB 过程DB 不可用,导致数据丢失;一致性不强,对一致性要求高的系统不适用 |
策略 | 说明 | 一致性 | 维护成本 |
---|---|---|---|
内存淘汰 | 使用Redis的内存淘汰策略,当内存不足时自动进行淘汰部分数据,下次查询时更新缓存 | 差 | 无 |
过期删除 | 缓存添加过期时间,到期后根据过期删除策略自动进行删除缓存,下次查询时进行更新缓存 | 低 | 低 |
主动更新 | 修改数据库时也修改缓存,使用硬编码方式或者硬编码+中间件方式在修改数据库时同步或异步的修改缓存 | 好 | 高 |
DB
时删除缓存,查询时再从DB
中读取数据并更新到缓存。DB
时更新缓存,频繁更新缓存开销大,且并发时可能导致请求读取的缓存数据是旧数据。1 并发写场景
所有线程都是先更新DB
再更新缓存,在某个写线程更新DB
后继续更新缓存时,可能因为网络原因出现延迟,这时其他写线程也更新了DB
和缓存,导致缓存和DB
数据不一致。
具体步骤:
DB
DB
总结:
理论上先更新DB
的线程理应也会先更新缓存,但是并发场景下线程的执行顺序无法保证:
DB
是线程2的数据,导致缓存和DB
数据不一致。2 并发读写场景
在写线程更新DB
和更新缓存之间,读线程可以获取到旧数据,但最终会一致。
具体步骤:
DB
总结:
线程2获取的缓存是旧数据,但最终都会一致。
1 并发写场景
所有线程都是先更新DB
再删除缓存,无论哪个线程先更新DB
再删除缓存,缓存都会被删除,不会导致缓存和DB
数据不一致。
具体步骤:
DB
DB
总结:
无论哪个线程先更新DB
再删除缓存,缓存都会被删除,不会导致缓存和DB
数据不一致。
2 并发读写场景
在写线程更新DB
再删除缓存之间,读线程可以获取到旧数据,但最终会一致。
具体步骤:
DB
总结:
线程2获取的缓存是旧数据,但后续最终都会一致。
1 并发写场景
所有线程都是先更新缓存再更新DB
,在某个写线程更新缓存和更新DB
之间,其他写线程也更新了缓存和DB
,导致缓存和DB
数据不一致。
具体步骤:
DB
DB
总结:理论上先更新缓存的线程也会先更新DB
,但是并发场景下线程的执行顺序无法保证:
DB
的顺序是: 线程1再线程2,则不会出现数据不一致问题。DB
的顺序是: 线程2再线程1,此时缓存是线程2的数据,DB
是线程1的数据,导致缓存和DB
数据不一致。2 并发读写场景
在写线程更新缓存和更新DB
之间,读线程也可以获取到最新的缓存,不会导致缓存和DB
数据不一致。
具体步骤:
DB
总结:
可以保证缓存和DB
数据一致,虽然线程1更新DB
的操作还没有完成,但是更新缓存的操作已经完成了,读请求可以获取到最新的缓存。
1 并发写场景
所有线程都是先删除缓存再更新DB
,无论哪个线程先删除缓存再更新DB
,缓存都会被删除,不会导致缓存和DB
数据不一致。
具体步骤:
DB
DB
总结:
无论哪个线程先删除缓存再更新DB
,缓存都会被删除,不会导致缓存和DB
数据不一致。
2 并发读写场景
在写线程删除缓存和更新DB
之间,读线程根据查询的DB
结果更新了缓存,导致缓存和DB
数据不一致。
具体步骤:
DB
DB
结果更新缓存DB
总结:
线程1删除缓存和更新DB
之间,线程2根据查询的DB
结果更新了缓存,导致缓存和DB
数据不一致。
因为3.4 先删除缓存,再更新DB,在并发读写场景会导致数据不一致。
延迟双删是基于先删除缓存再更新DB
的基础上的改进,在更新DB
后延迟一定时间,再次删除缓存。
延迟是为了保证第二次删除缓存前能完成更新DB
操作,延迟时间根据系统的查询性能而定。
第二次删除缓存是为了保证后续请求查询DB
(此时数据库中的数据已是更新后的数据),重新写入缓存,保证数据一致性。
1 并发写场景
无论哪个线程都会删除缓存,所以不会导致缓存和DB
数据不一致。
具体步骤:
DB
DB
2 并发读写场景
具体步骤:
DB
DB
结果更新缓存DB
总结:
线程1第一次删除缓存之后,线程2根据查询的DB
结果更新缓存,此时查询得到的结果是旧数据,线程1延迟第二次删除缓存之后,后续查询DB
(此时数据库中的数据已是更新后的数据),重新写入缓存,不会导致缓存和DB
数据不一致。
3 延时双删的缺点:
DB
之前,查询线程查询得到的结果是旧数据,可但可以减轻缓存和DB
数据不一致的问题。因为3.2 先更新DB,后删除缓存 在并发写场景不会导致数据不一致,但是在并发读写场景会短暂的导致数据不一致,但是由于删除缓存失败不会重试,并发写场景、并发读写场景都可能长时间导致数据不一致。
异步删除缓存是对先更新DB,后删除缓存的改进:更新DB
之后,基于消费队列异步删除缓存。
根据消费队列不同大致分为:消息队列、bin log
+消息队列。
1 并发写场景
无论哪个线程先更新DB
再删除缓存,缓存都会被删除,不会导致缓存和DB
数据不一致。
具体步骤:
DB
DB
总结:
无论哪个线程先更新DB
再删除缓存,缓存都会被删除,不会导致缓存和DB
数据不一致。
2 并发读写场景
异步删除缓存期间,读线程获取的缓存是旧数据,短暂出现数据不一致,异步删除缓存后最终会一致。
具体步骤:
DB
总结:
异步删除缓存期间,读线程获取的缓存是旧数据,短暂出现数据不一致,异步删除缓存后最终会一致。
1 并发写场景
具体步骤:
DB
DB
bin log
日志收集中间件定时收集DB
的bin log
日志bin log
日志收集中间件发送日志消息到消息队列总结:
无论哪个线程先更新DB
再删除缓存,缓存都会被删除,不会导致缓存和DB
数据不一致。
–
2 并发读写场景
具体步骤:
DB
bin log
日志收集中间件定时收集DB
的bin log
日志bin log
日志收集中间件发送日志消息到消息队列总结:
异步删除缓存期间,读线程获取的缓存是旧数据,短暂出现数据不一致,异步删除缓存后最终会一致。
优点:
缺点:
引入中间件,提升了系统的复杂度,在高并发场景可能会产生性能问题。
canal
是阿里开发的基于数据库增量日志解析,提供增量数据的订阅和消费,目前主要支持MySQL
的bin log
解析。基于canal
的实现方案完全避免了对业务代码的侵入,核心业务代码只管更新数据库,其他的不用care
。
canal
地址:https://github.com/alibaba/canal
MySQL
会将操作记录在bin log
日志中,通过canal
去监听数据库日志二进制文件,解析bin log
日志,同步到Redis
中进行增删改操作。
canal
的工作原理:canal
是模拟MySQL slave
的交互协议,伪装自己为MySQL slave
,向MySQL master
发送dump
协议;MySQL master
收到dump
请求,开始推送bin log
给slave
(即canal
);canal
解析bin log
象(原始为byte
流)。
参看:redis之缓存一致性 最后一部分