大家好 我是积极向上的湘锅锅💪💪💪
希望小伙伴们秋招一起上岸👫👫👫
Cache Aside Pattern(旁路缓存模式)
Cache Aside Pattern(旁路缓存模式)是目前使用的最多的缓存模式,通常使用的场景是读多写少,比如用户信息,都是一旦写入缓存,几乎不会修改,但会出现数据不一致的情况
读:
- 从缓存中读取数据,如果有,就直接返回
- 如果没有读取到数据,就从数据库读取
- 并写入到缓存中
写:
- 先更新数据库
- 然后直接删除缓存
那问题来了:
1. 为什么是删除缓存而不是修改缓存?
- 更新缓存:每次更新都要更新缓存,无效写操作较多
- 删除缓存:更新数据库的时候让缓存失效,只有再查询的时候再更新缓存
场景: 比如我要修改100次数据库
如果按照更新缓存的步骤,那我缓存就要更新100次,那之前的99次都是无效数据
如果我按照删除缓存,那只会删除一次,且查询的数据一定是最新的(数据一致性的情况下)
2. 那该如何保证缓存和数据库操作同时成功或者失败?
- 单体系统:将缓存和数据库操作放在一个事务
- 分布式系统:利用TCC等分布式方案
3. 先操作缓存还是先操作数据库?
- 先删除缓存,再操作数据库
线程1删除的时候,线程2读取数据库,此时数据还没更新
可能会造成数据库(DB)和缓存(Cache)数据不一致的问题
比如:
- 先操作数据库再删除缓存
恰好缓存失效,线程1开始更新,此时线程2因为找不到缓存,开始读取数据库,此时数据还未更新,所以也是一个旧值
理论上来说还是可能会出现数据不一致性的问题,不过概率非常小,因为缓存的写入速度是比数据库的写入速度快很多,一般是几ms,所以造成数据不一致需要几个条件: - 缓存刚好失效
- 在写缓存的几ms内,其他线程读取数据库
所以综合来看,先操作数据库再删除缓存出现的数据不一致性可能性较小
最后再看下Cache Aside Pattern的缺陷:
- 首次请求数据一定不在 cache 的问题:
解决: 对于热点数据,我们可以提前将数据放入缓存中,也就是缓存预热
- 如果写操作比较多造成删除操作频繁的问题:
解决: 如果删除比较多,那就不执行删除
- 如果要求缓存数据一致性较强:更新DB的时候同样更新cache,不过需要加一个锁/分布式锁来保证更新cache的时候不存在线程安全问题
- 如果要求缓存数据一致性较弱:更新DB的时候同样更新cache,但是给缓存加一个比较短的过期时间,这样的话就可以保证即使数据不一致的话影响也比较小
Read/Write Through Pattern(读写穿透)
服务端将cache做为主要数据存储,也就是说所以对数据的增删查改操作都是在cache上操作,然后由cache负责读取和写入DB
写:
- 先查cache,如果cache没有,直接更新DB
- cache中存在,则先更新cache,然后由cache服务自己更新DB
读:
- 从cache中读取数据,读取到就直接返回
- 读取不到先从DB加载,写入到cache后返回
优点:因为程序只和缓存交互,编码会变得更加简单和整洁,当需要在多处复用相同逻辑时这点就变得格外明显,Write-Through的潜在使用场景是银行系统
缺点:因为这样可以理解为后端是单一存储,但一方面DB占据的是磁盘资源而cache是内存资源,所以要注意缓存有效性的管理,否则会导致大量的缓存占用内存资源
Write-Behind(异步缓存写入)
Write-Behind和Write-Through程序只和缓存打交道很相似,不过区别是Write-Through会把数据立即写入数据库中,而Write-Behind是通过异步的方式将数据持久化到数据库中
其中可以实现的效果就是可以批量或者定时的写入数据,极大的降低了请求延迟并减轻了数据库的负担
优点:
Write Behind Pattern 下 DB 的写性能非常高,非常适合一些数据经常变化又对数据一致性要求没那么高的场景,比如浏览量、点赞量
缺点:
当然这肯定会导致数据一致性问题,比如cache数据可能还没异步更新DB的话,cache服务可能就就挂掉了,所以会用到我们的消息队列来保证消息的可靠性