Mysql/Redis缓存一致性

如何保证MySQL和Redis的缓存一致。从理论到实战。总结6种来感受一下。

理论知识

不好的方案

1.先写MySQL,再写Redis

图解说明:

这是一幅时序图,描述请求的先后调用顺序;

黄色的线是请求A,黑色的线是请求B;

黄色的文字是MySQL和Redis最终不一致的数据;

数据是从10更新为11;

后面的图为此规定

请求A、B都是先写MySQL,然后再写Redis,在高并发的情况下,如果请求A在写Redis时卡了一会,请求B已经一次完成了数据的更新,就会出现图中描述的问题。

图表述很清楚了,不过这里有个前提,就是对于读请求,先去读Redis,如果没有,再去读DB,但是读请求不会再写回Redis。就是读请求不会更新Redis。

2.先写Redis,再写MySQL

同1描述一样,秒懂。

3.先删除Redis,再写MySQL

和上面不一样的是,前面的请求A和B都是更新请求,这里的请求·A是跟新请求,但B请求是读请求,并且B的读请求会写回Redis。

请求A先删除缓存,可能因为卡顿,数据一直没有更新到MySQL,导致数据不一致。

这种情况出现的概率比较大,因为请求A更新MySQL可能会耗时比较长,而请求B的前两者都是查询,会比较快。

好的方案

4.先删除Redis,再写MySQL,再删除Redis

对于“先删除Redis,再写MySQL” ,如果要解决最后的不一致问题,其实再对Redis重新删除即可,这个就是“缓存双删”。

这个方案看看就行。

更好的方案是,异步串行化删除,即删除请求入队列

异步删除除对线上业务无影响,串行化处理保障并发情况下正确删除。

5.先写MySQL,再删除Redis

对于上面这种情况,对于第一次查询,请求B查询的数据10,但是MySQL的数据是11,只存在这一次不一致的情况,对于不是强一致的情况,对于不是强一致性要求的业务,可以容忍。对秒杀,库存就不行。

当请求B进行第二次查询时,因为没命中Redis,会重新擦汗一次DB,然后再回写到Redis。

这里需要满足两个条件:

        缓存刚好自动失效;

        请求B从数据库查10,回写缓存的消耗,比请求A写数据库,并且删除缓存的还长。

对于第二个条件,我们都知道更新DB肯定比查询耗时要长,所以出现这个情况的概率很小,同时满足上述条件情况更小。

6.先写MySQL,通过Binlog,异步更新Redis

这个方案,主要是监听MySQL的Binlog,然后通过异步的方式,将数据更新到Redis,这种方案有个前提,查询的请求,不会写回Redis。

这个方案,保证MySQL和Redis的最终一致性,但是如果中途请求B需要查询数据,如果缓存无数据,就直接查DB;如果缓存有数据,查询的数据也会存在不一致的情况。

所以这个方案,是实现最终一致性的终极方案,但是不能保证实时性。

几种方案比较

我们对比上述讨论的6种方案:‘

1.先写Redis,再写MySQL

这种方案,坑定是不会用,万一DB挂了,你把数据写到缓存,DB无数据,这个是灾难性的;

如果写DB失败,对Redis进行逆操作,那如果逆向操作失败,是不是得又搞个重试?

2.先写MySQL,再写Redis

对于并发量、一致性要求不高的项目,很多就是这么用的,我之前也经常这么搞

但是不建议这么做;

当Redis瞬间不可用的情况,需要报警出来,然后线下处理。

3.先删除Redis,再写MySQL

有懂得回答?

4.先删除Redis,再写MySQL,再删除Redis

这种方式虽然可行,但是感觉复杂,还要搞个消息队列去异步删除Redis。

5.先写MySQL,再删除Redis

比较推荐这总方案,删除Redis如果失败,可以再多重试几次,否则报警出来;

这个方案,是实时性最好的方案,在一些高并发场景种,推荐。

6.先写MySQL,通过Binlog。异步更新Redis

对于异地容灾,数据汇总,建议用这种,比如binlog+kafka,数据得一致性也可以达到秒级;

纯粹得高并发场景,不建议这种方案,入抢购,秒杀等。

个人结论:

实时性一致方案:采用“先写MySQL ,再删除Redis”的策略,这种情况下虽然也会存在两者不一致,但是需要满足的条件有点苛刻,所以是满足实时性条件下,能尽量满足一致性的最优解。

最终一致性方案:采用“先写MySQL,通过Binlog,异步更新Redis“,可以通过Binlog,结合消息队列异步更新Redis,是最终一致性的最优解。

项目实战

数据更新

因为项目对实时性要求高,所以采用方案5,先写MySQL,再删除Redis方式。

下面是一个示例,我们将文章的标签放入MySQL之后,在删除Redis,所有涉及到DB更新的操作都需要按照这种方式处理。

这里加了一个事务,如果Redis删除失败,MySQL的更新操作也要回滚,避免查询读取到脏数据。

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveTag(TagReq tagReq) {
        TagDO tagDO = ArticleConverter.toDO(tagReq);
        //先写MySQL
        if (NumUtil.nullOrZero(tagReq.getTagId())) {
            tagDao.save(tagDO);
        } else {
            tagDO.setId(tagReq.getTagId());
            tagDao.updateById(tagDO);
        }
        //再删除Redis
        String redisKey = CACHE_TAG_PRE + tagDO.getId();
        RedisClient.del(redisKey);
    }
    
    @Override
    @Transactional(rollbackFor = Excetion.class)
    public void deleteTag(Integer tagId) {
        TagDO tagDO = tagDao.getById(tagId);
        if (tagDO != null){
            //先写MySQL
            tagDao.removeById(tagId);
            //再删除Redis
            String redisKey = CACHE_TAG_PRE + tagDO.getId();
            RedisClien.del(redisKey);
        }
    }

    @Override
    public void operateTag(Integer tagId, Integer pushStatus) {
        TagDO tagDO = tagDao.getById(tagId);
        if (tagDO != null){
            //先写MySQL
            tagDO.setStatus(pushStatus);
            tagDao.updateById(tagDO);
            //再删除Redis
            String redisKey = CACHE_TAG_PRE + tagDO.getId();
            RedisClient.del(redisKey);
        }
    }

获取数据

也比较简单,先查缓存,如果有就直接返回;如果未查询到,需要先查询DB,再写入缓存。

我们放入缓存时,加了一个过期时间,用于兜底,万一两者不一致,缓存过期后,数据会重新更新到缓存。

    @Override
    public TagDTO getTagById(Long tagId) {

        String redisKey = CACHE_TAG_PRE + tagId;

        // 先查询缓存,如果有就直接返回
        String tagInfoStr = RedisClient.getStr(redisKey);
        if (tagInfoStr != null && !tagInfoStr.isEmpty()) {
            return JsonUtil.toObj(tagInfoStr, TagDTO.class);
        }

        // 如果未查询到,需要先查询 DB ,再写入缓存
        TagDTO tagDTO = tagDao.selectById(tagId);
        tagInfoStr = JsonUtil.toStr(tagDTO);
        RedisClient.setStrWithExpire(redisKey, tagInfoStr, CACHE_TAG_EXPRIE_TIME);

        return tagDTO;
    }

测试用例

@Slf4j
public class MysqlRedisService extends BasicTest {

    @Autowired
    private TagSettingService tagSettingService;

    @Test
    public void save() {
        TagReq tagReq = new TagReq();
        tagReq.setTag("Java");
        tagReq.setTagId(1L);
        tagSettingService.saveTag(tagReq);
        log.info("save success:{}", tagReq);
    }

    @Test
    public void query() {
        TagDTO tagDTO = tagSettingService.getTagById(1L);
        log.info("query tagInfo:{}", tagDTO);
    }
}

我们看一下Redis:

结果输出:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/454157.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[Linux][CentOs][Mysql]基于Linux-CentOs7.9系统安装并配置开机自启Mysql-8.0.28数据库

目录 一、准备工作:获取安装包和相应工具 (一)所需安装包 (二)安装包下载链接 (三)在服务器上创建文件夹并上传安装包 二、安装MySql (一)删除系统自带的mariadb …

到底什么是中台?

1.背景 最近老是听见或看见”中台“的字眼,例如数据中台、业务中台,根本搞不懂是什么,就感觉挺高大尚的。但同时,作为技术人,对于这种可能用到又一无所知的东西,心里是发慌的,因此有了这篇文章。…

unity中实现场景跳转

1,第一步创建2个场景(右键资源窗口,名字这里我取的1111和2222) 2.添加跳转按钮(双击其中一个场景并添加按钮) 3.编辑按钮的文字(将原本的按钮打开点击里面的text,就可以在右边编辑文…

改进沙猫群优化的BP神经网络ISCSO-BP(时序预测)的Matlab实现

改进沙猫群优化的BP神经网络(ISCSO-BP)是一种结合了改进的沙猫群优化算法(Improved Sand Cat Swarm Optimization, ISCSO)和反向传播(Back Propagation, BP)神经网络的模型,旨在提高时序预测的准…

【大模型API调用初尝试一】智谱AI 通义千问

大模型API调用初尝试一 调用大模型API能干什么智谱AI大模型API调用的过程获取API_KEYGLM_4同步调用GLM_4异步调用文生图大模型API调用 阿里云通义千问API调用过程单轮会话多轮会话 调用大模型API能干什么 大模型的参数非常庞大,功能非常强大,但是训练成…

水肥一体机远程监控解决方案

水肥一体机远程监控解决方案 水肥一体机物联网解决方案,作为现代农业技术的尖端代表,深度融合了物联网、大数据分析以及智能控制等前沿科技手段,为农田灌溉与施肥管理带来了革命性的精准化与智能化升级。该方案的核心理念在于通过实时监测土…

python爬虫实战——小红书

目录 1、博主页面分析 2、在控制台预先获取所有作品页的URL 3、在 Python 中读入该文件并做准备工作 4、处理图文类型作品 5、处理视频类型作品 6、异常访问而被中断的现象 7、完整参考代码 任务:在 win 环境下,利用 Python、webdriver、JavaS…

C/C++链接mysql

下载对应文件 https://dev.mysql.com/downloads/ 选择自己对应的普通进行下载 将下载好的文件上传到机器并解压 tar -zxvf 文件名.tar.gz其中 include 包含所有的方法声明, lib64包含所有的方法实现(打包成库) 将include文件夹放到/usr/…

zabbix 7.0编译部署教程

zabbix 7.0编译部署教程 2024-03-08 16:50乐维社区 zabbix7.0 alpha版本、beta版本已经陆续发布,Zabbix7.0 LTS版本发布时间也越来越近。据了解,新的版本在性能提升、架构优化等新功能方面有非常亮眼的表现,不少小伙伴对此也已经跃跃欲试。心…

OpenCASCADE开发指南<五>:OCC 内存管理器和异常类

一个软件首先要规定能处理的数据类型, 其次要实现三项最基本的功能——引用管理、内存管理和异常管理。在 OCC 中,这三项功能分别对应基础类中的句柄、内存管理器和异常类。 1 异常类 1. 1 异常类的定义 异常处理机制实现了正常程序逻辑与错误处理的分离…

2024春秋蓝桥杯reverse——crackme01

尝试了下输入没有任何反应 查看——32位——IDA打开 我之前没怎么写过win32,所以我开始在string里面找flag,wrong,right什么的字符,都不行 然后我又在函数里面找main,也什么收获的没有,OK废话完了 在win32里面 关于弹窗的函数:…

移动机器人设计与实践课程进度安排-2023-2024-2

进度安排由人工智能审核制定。 人工智能设计的机器人模型如下,一组三个: 轮式物流小车机器人智慧工厂绘图描述 背景: 绘制一个工厂的大致轮廓,包括工厂大门、围墙和主要的建筑结构。在背景中描绘一些工业设备、生产线和堆放的物料&#xff…

elasticsearch篇

1.初识elasticsearch 1.1.了解ES 1.1.1.elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容 例如: 在电商网站搜索商品 在百度搜索答案 在打车软件搜索附近…

两个笔记本如何将一个笔记本作为另一个笔记本的拓展屏

需求是有两个笔记本,一个笔记本闲置,另一个笔记本是主力本。想将另一个闲置的笔记本连接到主力本上作为拓展屏使用。网上搜了好久,有一些人提到了,也有一些视频但是文章比较少。简单总结一下吧 上述需求有两种方式 第一种&#x…

Purple Pi OH鸿蒙开发板7天入门OpenHarmony开源鸿蒙教程【六】

今天我们来从OpenHarmony简介、环境搭建、创建第一个OpenHarmony项目等方面开始OpenHarmony应用开发的第一步。 一. OpenHarmony简介 OpenHarmony 是由开放原子开源基金会(OpenAtom Foundation)孵化及运营的开源项目,目标是面向全场景、全连接、全智能…

BUGKU-WEB No one knows regex better than me

题目描述 题目截图如下&#xff1a; 进入场景看看&#xff1a; 解题思路 看到此类题目&#xff0c;直接代码审计 相关工具 base64 在线加密https://www.mklab.cn/utils/regex 解题步骤 代码审计 <?php error_reporting(0); # 从请求中获取了两个参数&#xff1…

String 底层是如何实现的?

1、典型回答 String 底层是基于数组实现的&#xff0c;并且数组使用了 final 修饰&#xff0c;不同版本中的数组类型也是不同的&#xff1a; JDK9 之前&#xff08;不含JDK9&#xff09; String 类是使用 char[ ]&#xff08;字符数组&#xff09;实现的但 JDK9 之后&#xf…

短视频解析接口分发系统

宝塔面板&#xff1a;Nginx系统 php7.2 Mysql 5.6-5.7 伪静态Thinkphp 上传文件直接访问域名安装即可 可以自备 听说后边要出saas去水印小程序 下载地址&#xff1a;https://pan.xunlei.com/s/VNskSEelfRVIzoSm5P5Rcw34A1?pwdqzhh# 接口演示&#xff1a; 前端演示…

安装PyTorch详细过程

安装anaconda 登录anaconda的官网下载&#xff0c;anaconda是一个集成的工具软件不需要我们再次下载。anaconda官网 跳转到这个页面如果你的Python版本正好是3.8版&#xff0c;那便可以直接根据系统去选择自己相应的下载版本就可以了。 但是如果你的Python版本号不是当前页面…

Spring 面试题及答案整理,最新面试题

Spring框架中的Bean生命周期是什么&#xff1f; Spring框架中的Bean生命周期包含以下关键步骤&#xff1a; 1、实例化Bean&#xff1a; 首先创建Bean的实例。 2、设置属性值&#xff1a; Spring框架通过反射机制注入属性。 3、调用BeanNameAware的setBeanName()&#xff1a…