Redis缓存一致性设计
大黄 Lv4

一致性问题是如何产生的?

首先先抽象我们日常的几个缓存和数据库CRUD的方法

1
2
3
4
5
6
7
8
9
10
11
getFromDB(key)

getFromRedis(key)

putToDB(key,value)

putToRedis(key,value)

deleteFromDB(key)

deleteFromRedis(key)
  1. 利用缓存读取数据原因,一般情况下,Redis的操作速度比数据库操作数据要快上很多(因为Redis是基于内存的)
  2. 而数据最终要放到数据库中,是因为Redis并不适合作为落地存储的

那么,假如我们既要持久化数据,又要快速的读数据。那么,就必须要将Redis和数据库一起使用起来

假如我们即要使用Redis,又要使用数据库,他们分别属于两个系统,那么我们就不能保证Redis的操作和数据库的操作和在一起是原子性的,那么就会存在数据一致性问题

举个例子:

1
2
3
4
public void putValue(String key,String value){
putToRedis(key,value);
putToDB(key,value);
}

假如在往Redis放数据成功,往数据库放数据失败,造成回滚,此时无法回滚Redis的数据,就会出现数据不一致的问题

那么假如把Redis和数据库的操作反过来,能够解决这个问题吗? 同样也是不能的

1
2
3
4
public void putValue(String key,String value){
putToDB(key,value);
putToRedis(key,value);
}

假设一个并发场景

Redis缓存一致性设计 redis1.jpeg

此时,Redis数据理应为线程2操作后的数据,但是我们并不能控制这两个操作的先后执行顺序,导致Redis中的数据是线程1操作后的数据

那么,换一种思路,假如我们不更新缓存,而是删除缓存,在用户查询时,如果缓存中没有数据就去数据库中获取,这样会有问题吗

先删缓存,再更新数据库

1
2
3
4
public void putValue(String key,String value){
deleteFromRedis(key);
putToDB(key,value);
}

就跟上一张图一样,在线程1删除缓存后,数据库还没有来得及更新,此时线程2进入,查询到数据库中的值,设置到缓存中。那么缓存中的数据永远都是不一致的数据

同时这样也会存在缓存击穿的问题,在线程2将数据设置到缓存之前,所有的请求都会被打到数据库中

那么再换一种思路,先更新数据库,再删缓存可以吗(前后双删同理)

在并发不高的场景下,还是满足的

1
2
3
4
public void putValue(String key,String value){
putToDB(key,value);
deleteFromRedis(key);
}

但是在高并发的情况下,举个例子(产生条件非常苛刻,在发生并发写的同时,还要并发读,一般业务到达不了这个量级)

Redis缓存一致性设计 redis2.jpeg

高并发的不一致问题解决思路

延时双删

1
2
3
4
5
6
public void putValue(key,value) {
putToDB(key,value);
deleteFromRedis(key);

...deleteFromRedis(key,after5sec);
}

删除选择的方式也有很多

  • 放在DelayQueue中,但是有JVM宕机导致删除丢失的风险
  • 放在MQ中,增加编码的复杂性,且又加入了一个新系统,同样有三个系统的一致性问题(虽然可以通过事务消息解决)

闪电缓存

缓存时间设置的非常短,存在缓存击穿的问题

缓存击穿问题

不管是延时双删,还是闪电缓存,其实他们都会存在一个缓存击穿的问题,那么怎么解决缓存击穿呢?

  • 读操作互斥,使用锁/分布式锁控制
  • 更新集中,利用定时或者订阅binlog(例如canal)保持最终一致

读互斥的思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
get(key) {
res = getFromRedis(key);
if (null == res) {
lock.lock(...);
try {
// 再次读取缓存,即double check
res = getFromRedis(key);
if (res == null) {
res = getFromDB(key);
if (null != res) {
// 设置redis
putToRedis(key, res);
}
}
} finally {
lock.unlock();
}
}
}
  • 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.