您现在的位置是:首页 >技术教程 >redis高级篇 缓存双写一致性之更新策略网站首页技术教程

redis高级篇 缓存双写一致性之更新策略

看着蓝天抽支烟 2024-06-27 00:01:02
简介redis高级篇 缓存双写一致性之更新策略

闲聊

缓存通用查询3部曲

  1. redis 中数据,返回redis 中的数据
  2. redis 中没有,查询数据库并返回
  3. 完成第二部的同时,将数据库查询结果写到redis,redis和数据库数据一致.

谈谈双写一致性的理解

1.如果redis 中有数据:需要和数据库中的相同
2.如果redis 中无数据: 数据库中的值如果是最新的,则要写入到redis

redis 缓存种类

  1. 只读(通过命令的方式写入,不是由我们的java程序,不常用)
  2. 读写(常用)

redis 读写缓存的策略

  1. 同步直写策略
    写数据后,也同步写到redis,缓存和数据库中的数据一致。
    对于读写缓存来说,要想保证缓存和数据库中的数据一致,就要采用同步直写策略
  2. 异步缓存策略
    正常业务运行中,一定时间后才将数据写到redis(比如下单后的快递信息)
    异常情况出现后,需要将失败的动作进行修补,有可能需要借助mq等消息队列。

问题

1. 如果使用redis,那就回涉及到redis 缓存与数据库双写问题。是先动redis呢,还是先动mysql?

首先我们的目的是明确的:就是保证redis和mysql的一致性。

给缓存设置过期时间,定期清理缓存并会写,是最终保证一致性的解决方案。
我们可以对缓存数据设置过期时间,所有的写以数据库为准,对缓存操作尽最大
努力即可。也就是说数据库写入成功,而缓存写入失败,那么只要达到过期时间,
则后面的请求会从数据库中读取新值回写缓存。从而达到最终一致性。

在了解一下4中更新策略

  1. 先更新数据库,在更新缓存
    如果更新mqsql 成功,redis 失败,则数据不一致
    缓存中使错误的数据,数据不一致,且高并发下问题更是严重
    在这里插入图片描述

  2. 线程缓存,在更新数据库
    如果redis 成功,mqsql失败,则数据不一致

在这里插入图片描述
3. 先删除缓存,在更新数据库
如果redis 删除成功, 数据库删除失败,则数据不一致
下面有问题的代码
在这里插入图片描述

A线程执行下面的代码,删除缓存后,正在更新数据库,
线程B要获取这个缓存 ,没有查到,线程B就把数据回写到缓存,
然后线程A更新完数据库后,发现缓存还在
导致缓存中一直都是错的数据。

处理办法1 延迟双删除
这个方案在第一次删除之后,延迟一段时间再次进行删除,所以我们把它叫做“延迟双栓”
在这里插入图片描述

  1. 先更新数据库,在删除缓存 (可用,也有问题,但是比上面的3种更好)
    如果mqsql成功,redis 失败,只要是mysql 是正确的,下次通过回写到reids,保证一致性
    ali的cannel也是这个思想, myssql有个中间件canel,可以完成biglog日志订阅功能。 先更新数据库,在更新缓存。
    在这里插入图片描述
如果上述的问题也不能容忍(在通过在get一次,以取得正确的数据),那我们只能借助mq与mysql的binlog日志了
![在这里插入图片描述](https://img-blog.csdnimg.cn/355cb7ab3eab4bc6a9122eea84ab8d82.png)

1.我们可以监听 binlog 日志, 但我们知道那些key 和那些表的关系的时。我们便可以筛选出我们要监听的sql语句了。
2.当监听到 执行删除缓存关联的sql语句时,便把这个key放到消息队列。
3.通过mq 中消息执行删除操作。删除成功则已出,删除失败重试。如果重试次数较多,加入死信队列,后续处理,排查故障。

但是这样做有一定的延迟性。比如充值话费,高峰期;1分钟后到账。

2.了解延迟双删吗

延迟双删,
是指在第一次删除之后,延迟一段时间再次进行删除,所以我们把它叫做“延迟双栓”。 目的是 在第一次删除和第二次删除之前,防止有别的线程将值回写到reids.

线程A sleep的时间,需要大于线程B读取数据在写入缓存的时间。
这个时间该怎么确定呢?
第一种方法:
在业务运行的时候统计下线程读取数据与写回数据的时间 T1。结合自己业务,只要保证双删之间的时间 大于T1百毫秒即可。

第二种方法:
新启动后台监控程序,后面将会提到 watchDog监控程序。

这种同步删除降低吞吐量怎么办?
因为要删除2次,所以吞吐量会降低,另起一个线程,异步删除。
在这里插入图片描述

3. 微服务查询redis无,mysql有。为了保证双写一致性,会写redis 时需要注意什么?,了解双检加锁吗,如何避免缓存击穿?

一般情况下,使用上面的缓存三部曲即可,但如果是高并发就不行了。想想看,
统一时刻有1000个请求过来,代码都执行 判断redis中有无数据,也就是要
1000次,接着因为redis中没有,又是1000次访问数据库,再接着又是1000次
的会写redis. 这个在高并发下,明显是不可取的。
所有出现 双检加锁。

那什么是双检加锁呢?

 public String doubleCheckCache() {
        // 先从redis 中查询
        String key = "user:1000";
        String s = stringRedisTemplate.opsForValue().get(key);
        if (!StringUtils.hasLength(s)) {
            // 加锁,防止qps高时,同时查到redis中无数据的情况下,继续访问数据库。防止缓存击穿。
            synchronized (this) {
                // 再次检查redis,第一个进入同步的线程,已经将数据写回了redis
                s = stringRedisTemplate.opsForValue().get(key);
                if (!StringUtils.hasLength(s)) {
                    //模拟查询数据库
                    s = "我是小明";
                    // 会写redis
                    stringRedisTemplate.opsForValue().setIfAbsent(key,s);
                    // 这里要考虑数据库如果为空的时候,要怀疑这ke能时恶意的,续作我们将其加入黑明单。
                }
            }
        }
        return s;
    }

5. redis 和mysql 100%会出纰漏,做不到强一致性,如何保证最终一致性?

消息队列,有延迟,
延迟双删除,线程A 时间不好估算
等待删除后,执行查询命令,不支持高并发

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。