Redis(六)-缓存方案-一致性
概述
本节学习下“缓存 + 数据库”模型读写的一致性问题,比如,缓存中是否有可能被写入脏数据,策略的读写性能如何,是否存在缓存命中率下降的情况等等;
1. Cache Aside(旁路缓存)策略
来考虑一种最简单的业务场景,同时更新数据库和缓存中数据,要如何做呢?
1.1 思路一
写策略
:先更新数据库,再更新缓存;
读策略
:读缓存,缓存缺失时查数据库并回写缓存;
这个思路在并发更新时会造成缓存和数据库中的数据不一致,如下面的流程:
并发更新.png
为什么产生这个问题呢?因为变更数据库和变更缓存是两个独立的操作,而我们并没有对操作做任何的并发控制。那么当两个线程并发更新它们的时候,就会因为写入顺序的不同造成数据的不一致。
1.2 思路二
写策略
:先删除缓存,再更新数据库;
读策略
:读缓存,缓存缺失时查数据库并回写缓存;
这种方案如上面流程所示,在并发情况下仍然会出现不一致的情况;
可以通过
延迟双删
策略(更新后再次删除缓存)解决,但是这种策略对写性能影响较大,通常很少使用;
--延迟双删
redis.delKey(X)
db.update(X)
Thread.sleep(N)
redis.delKey(X)
1.3 思路三
写策略
:先更新数据库,再删除缓存;
读策略
:读缓存,缓存缺失时查数据库并回写缓存;
这个策略就是使用缓存最常见的策略,Cache Aside 策略(也叫旁路缓存策略);
Cache Aside
存在的最大的问题是当写入比较频繁时,缓存中的数据会被频繁地清理,这样会对缓存的命中率有一些影响。如果业务对缓存命中率有严格的要求,那么可以考虑两种解决方案:1.
在更新数据时也更新缓存,只是在更新缓存前先加一个分布式锁,因为这样在同一时间只允许一个线程更新缓存,就不会产生并发问题了。当然这么做对于写入的性能会有一些影响;2.
另一种做法同样也是在更新数据时更新缓存,只是给缓存加一个较短的过期时间,这样即使出现缓存不一致的情况,缓存的数据也会很快地过期,对业务的影响也是可以接受。
1.4 小结
以上三种思路中写数据库和写缓存都是分离的,那就有可能存在先写数据库成功后写缓存失败或先写缓存成功后写数据库失败的情况,这种情况同样会造成数据库和缓存不一致,这时可以把写操作通过消息中间件进行重试。
如下图流程:
旁路缓存-删缓存失败.png
2. Read/Write Through(同步直写)策略
这个策略的核心原则是用户只与缓存模块打交道,由缓存模块跟具体的缓存实现及数据库通信,写入或者读取数据。
2.1 实现
Write Through
策略:写请求直接发到缓存模块,缓存模块先查询要写入的数据在缓存中是否已经存在:
如果已经存在:则使用事务机制保证写缓存和写数据库具有原子性;
如果不存在:则直接写入数据库;
Read Through
策略:跟Cache Aside类似,缓存模块先查询缓存中数据是否存在:
如果存在:则直接返回
如果不存在:则从数据库中同步回写数据到缓存中,然后返回;
优点
:可以保证数据一致性,读性能较高;缺点
:同步写对写性能有比较大的影响,不适合写多的场景;
2.2 小结
这种策略涉及到缓存模块的开发,针对只用Redis+DB场景也可以由Client实现缓存模块的功能,通过事务来保证Redis和DB的一致性;
3. Write Behind(异步写回)策略
Write-Behind和Write-Through在“程序只和缓存模块交互这一点上很相似。不同点在于Write-Through会把数据立即写入数据库中,而Write-Behind只写入缓存并会把缓存标记为脏的,在一段时间之后(或是被其他方式触发)再把数据一起写入数据库;
Write Behind.png
数据库写操作可以用不同的方式完成,其中一个方式就是收集所有的写操作并在某一时间点(比如数据库负载低的时候)批量写入。另一种方式就是合并几个写操作成为一个小批次操作,接着缓存收集写操作(比如5个)一起批量写入;
优点:
异步写操作极大的降低了请求延迟并减轻了数据库的负担,大大提升写性能;
缺点:
因为缓存一般使用内存,而内存是非持久化的,所以一旦缓存机器掉电,就会造成原本缓存中的脏块儿数据丢失,造成数据不一致;
发现了吗?其实这种策略不仅被应用到我们常用的数据库(例如mysql)和缓存的场景中,它是计算机体系结构中的设计,比如我们在向磁盘中写数据时采用的就是这种策略。无论是操作系统层面的 Page Cache,还是日志的异步刷盘,亦或是消息队列中消息的异步写入磁盘,大多采用了这种策略。因为这个策略在性能上的优势毋庸置疑,它避免了直接写磁盘造成的随机写问题,毕竟写内存和写磁盘的随机 I/O 的延迟相差了几个数量级;
总结
1.Cache Aside 是在使用分布式缓存时最常用的策略,可以在实际工作中直接拿来使用;
2.Read/Write Through 和 Write Back 策略需要缓存组件的支持,所以比较适合在实现本地缓存组件的时候使用;
3.Write Back 策略是计算机体系结构中的策略,不过写入策略中的只写缓存,异步写入后端存储的策略倒是有很多的应用场景。
真实的系统中需求都不太一样,我们应该根据自己的需要来选择一个或组合几个模式来完成实现
--------over---------