Redis缓存一致性设计
一致性问题是如何产生的?
首先先抽象我们日常的几个缓存和数据库CRUD的方法
1 | getFromDB(key) |
- 利用缓存读取数据原因,一般情况下,Redis的操作速度比数据库操作数据要快上很多(因为Redis是基于内存的)
- 而数据最终要放到数据库中,是因为Redis并不适合作为落地存储的
那么,假如我们既要持久化数据,又要快速的读数据。那么,就必须要将Redis和数据库一起使用起来
假如我们即要使用Redis,又要使用数据库,他们分别属于两个系统,那么我们就不能保证Redis的操作和数据库的操作和在一起是原子性的,那么就会存在数据一致性问题
举个例子:
1 | public void putValue(String key,String value){ |
假如在往Redis放数据成功,往数据库放数据失败,造成回滚,此时无法回滚Redis的数据,就会出现数据不一致的问题
那么假如把Redis和数据库的操作反过来,能够解决这个问题吗? 同样也是不能的
1 | public void putValue(String key,String value){ |
假设一个并发场景
此时,Redis数据理应为线程2操作后的数据,但是我们并不能控制这两个操作的先后执行顺序,导致Redis中的数据是线程1操作后的数据
那么,换一种思路,假如我们不更新缓存,而是删除缓存,在用户查询时,如果缓存中没有数据就去数据库中获取,这样会有问题吗
先删缓存,再更新数据库
1 | public void putValue(String key,String value){ |
就跟上一张图一样,在线程1删除缓存后,数据库还没有来得及更新,此时线程2进入,查询到数据库中的值,设置到缓存中。那么缓存中的数据永远都是不一致的数据
同时这样也会存在缓存击穿的问题,在线程2将数据设置到缓存之前,所有的请求都会被打到数据库中
那么再换一种思路,先更新数据库,再删缓存可以吗(前后双删同理)
在并发不高的场景下,还是满足的
1 | public void putValue(String key,String value){ |
但是在高并发的情况下,举个例子(产生条件非常苛刻,在发生并发写的同时,还要并发读,一般业务到达不了这个量级)
高并发的不一致问题解决思路
延时双删
1 | public void putValue(key,value) { |
删除选择的方式也有很多
- 放在DelayQueue中,但是有JVM宕机导致删除丢失的风险
- 放在MQ中,增加编码的复杂性,且又加入了一个新系统,同样有三个系统的一致性问题(虽然可以通过事务消息解决)
闪电缓存
缓存时间设置的非常短,存在缓存击穿的问题
缓存击穿问题
不管是延时双删,还是闪电缓存,其实他们都会存在一个缓存击穿的问题,那么怎么解决缓存击穿呢?
- 读操作互斥,使用锁/分布式锁控制
- 更新集中,利用定时或者订阅binlog(例如canal)保持最终一致
读互斥的思路
1 | get(key) { |
- Post title:Redis缓存一致性设计
- Post author:大黄
- Create time:2021-12-30 21:53:54
- Post link:https://huangbangjing.cn/2021/12/30/Redis缓存一致性设计/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.