Mysql 、Redis 数据双写一致性 更新策略与应用

2024-04-23 07:52

本文主要是介绍Mysql 、Redis 数据双写一致性 更新策略与应用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

零、important point

1. 缓存双写一致性问题

2. java实现逻辑(对于  QPS <= 1000  可以使用)

public class UserService {public static final String CACHE_KEY_USER = "user:";@Resourceprivate UserMapper userMapper;@Resourceprivate RedisTemplate redisTemplate;/*** 业务逻辑没有写错,对于小厂中厂(QPS《=1000)可以使用,但是大厂不行* @param id* @return*/public User findUserById(Integer id){User user = null;String key = CACHE_KEY_USER+id;//1 先从redis里面查询,如果有直接返回结果,如果没有再去查询mysqluser = (User) redisTemplate.opsForValue().get(key);if(user == null){//2 redis里面无,继续查询mysqluser = userMapper.selectByPrimaryKey(id);if(user == null){//3.1 redis+mysql 都无数据//你具体细化,防止多次穿透,我们业务规定,记录下导致穿透的这个key回写redisreturn user;}else{//3.2 mysql有,需要将数据写回redis,保证下一次的缓存命中率redisTemplate.opsForValue().set(key,user);}}return user;}

其中存在的问题是:在高并发的场景下,(加入redis中没有)会有大量请求打在mysql上。

解决策略:

        (多个线程同时查询数据库某条数据时)

===》在第一个数据的请求上(加上一个互斥锁)

===》等待第一个线程查询到了数据 , 并做了缓存

===》后面的线程进来发现已经有缓存了 

===》直接走缓存

/*** 加强补充,避免突然key失效了,打爆mysql,做一下预防,尽量不出现击穿的情况。* @param id* @return*/public User findUserById2(Integer id){User user = null;String key = CACHE_KEY_USER+id;//1 先从redis里面查询,如果有直接返回结果,如果没有再去查询mysql,// 第1次查询redis,加锁前user = (User) redisTemplate.opsForValue().get(key);if(user == null) {//2 大厂用,对于高QPS的优化,进来就先加锁,保证一个请求操作,让外面的redis等待一下,避免击穿mysqlsynchronized (UserService.class){//第2次查询redis,加锁后user = (User) redisTemplate.opsForValue().get(key);//3 二次查redis还是null,可以去查mysql了(mysql默认有数据)if (user == null) {//4 查询mysql拿数据(mysql默认有数据)user = userMapper.selectByPrimaryKey(id);if (user == null) {return null;}else{//5 mysql里面有数据的,需要回写redis,完成数据一致性的同步工作redisTemplate.opsForValue().setIfAbsent(key,user,7L,TimeUnit.DAYS);}}}}return user;}

3. 数据一致性的理解

(1)如果 redis 中有数据   ===》  需要和  数据库中的值相同

(2)如果 redis 中没有数据  ===》  数据库中的值的是最新值,  回写到redis中

(3)缓存按照操作来分

        1.只读缓存(没有回写操作,少数情况下)

        2.读写缓存

                2.1 同步直写策略

                写数据库后也同步写redis缓存(热点数据、VIP重要数据 ==》这一秒填写、下一秒更新)

                2.2 异步缓写策略

                mysql数据变动了,可以允许业务上一定时间后作用于redis(仓库、物流系统、积分变更等 ==》 允许一定时延后缓存更新)

                可能会出现异常,借助kafka 或者 RabbitMQ 等消息中间件 ,实现重试重写

4. 数据库和缓存一致性的  几种策略

目的 :  达到最终的一致性

做法 :  给缓存设置过期时间   定期清理缓存并回写    ==》 保证最终一致性

1.停机

        (eg)凌晨升级  先往mysql灌入10000条数据, 在解决与mysql同步问题

2. 4种 更新策略

        (1)先更新数据库,在更新缓存

        (2)先更新缓存,在更新数据库

        (3)先删除缓存,在更新数据库

        (4)先更新数据库,在删除缓存

一、4种 更新策略

(1)先更新数据库,在更新缓存

Q1:redis回写失败,读到的是redis的脏数据

        1.先更新mysql的某商品的库存,当前商品的库存是100,更新为99个。
        2.先更新mysql修改为99成功,然后更新redis。
        3.此时假设异常出现,更新redis失败了,这导致mysql里面的库存是99而redis里面的还是100
        4.上述发生,会让数据库里面和缓存redis里面数据不一致,读到redis脏数据

Q2: 多线程对于同一份数据update, 回写redis出岔子,数据的写入覆盖

最终导致 mysql 80 , redis 100

(2)先更新缓存,在更新数据库(不太推荐)

Q1: 不太推荐 ==》 业务上一般把 mysql 作为底单数据库,保证最后的解释

Q2:多线程对于同一份数据update, 写入mysql出岔子,数据的写入覆盖

(3)先删除缓存,在更新数据库

Q1: 会出现延时

Q2: 此时redis里面的数据是空的,B线程来读取,先去读redis里数据(已经被A线程delete掉了),此处出来2个问题:

  2.1     B从mysql获得了旧值

       B线程发现redis里没有(缓存缺失)马上去mysql里面读取,从数据库里面读取来的是旧值

  2.2     B会把获得的旧值写回redis 

     获得旧值数据后返回前台并回写进redis(刚被A线程删除的旧数据有极大可能又被写回了)。

Q3:A线程更新完mysql,发现redis里面的缓存是脏数据,A线程直接懵逼了,o(╥﹏╥)o

        两个并发操作,一个是更新操作,另一个是查询操作,

        A删除缓存后,B查询操作没有命中缓存,B先把老数据读出来后放到缓存中,然后A更新操作更新了数据库。

        于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的,而且还一直这样脏下去了。

解决策略

1. 采用延时双删策略

(4)先更新数据库,在删除缓存

1.异常问题

 2.业务指导思想

2.1 微软云

2.2 阿里巴巴cache

 3.解决方案

1 可以把要删除的缓存值或者是要更新的数据库值暂存到消息队列中(例如使用Kafka/RabbitMQ等)。

2 当程序没有能够成功地删除缓存值或者是更新数据库值时,可以从消息队列中重新读取这些值,然后再次进行删除或更新。
3 如果能够成功地删除或更新,我们就要把这些值从消息队列中去除,以免重复操作,此时,我们也可以保证数据库和缓存的数据一致了,否则还需要再次进行重试
4 如果重试超过的一定次数后还是没有成功,我们就需要向业务层发送报错信息了,通知运维人员。

 4.经典分布式事务问题

最终一致性体现案例:

1.流量充值,先下发短信,实际充值滞后5min

2.电商下单,先下发短信,具体物流明天见

 (0)如何选择方案

大多数业务场景:

        优先使用先更新数库,在删除缓存的方案


四、面试题

1.使用缓存  会涉及到  redis缓存与数据库  双存储双写

双写  ==》 数据库一致性问题  ==》 如何解决呢?

2. 双写一致性, 先去操作 redis or mysql,   why?  

3.  延时双删  怎么说?

        应用在需要更新数据时,先删除缓存再更新mysql数据库的策略下,所发生A线程需要更新数据,第一次删除缓存,更新完数据后,再次删除缓存,再将更新后的数据写入缓存。

延时双删会遇到一些问题:

Q1:这个删除需要睡眠多久呢?

         一般来说,线程Asleep的时间,就需要大于线程B读取数据再写入缓存的时间。
        第一种方法:
        在业务程序运行的时候,统计下线程读数据和写缓存的操作时间自行评估自己的项目的读数据业务逻辑的耗时,以此为基础来进行估算。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上加百毫秒即可。
        这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
        第二种方法:
        新启动一个后台监控程序,比如后面讲解的WatchDog监控程序,去加时

Q2:这种同步策略,吞吐量降低如何解决?

        启动一个线程来监听mysql是否更新完毕

4. 微服务查询  redis无  mysql有, 为保证数据 双写一致性 回写redis  需要注意什么?

==》 双检 加锁 策略

==》 避免 缓存击穿

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

这篇关于Mysql 、Redis 数据双写一致性 更新策略与应用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/928203

相关文章

Mysql虚拟列的使用场景

《Mysql虚拟列的使用场景》MySQL虚拟列是一种在查询时动态生成的特殊列,它不占用存储空间,可以提高查询效率和数据处理便利性,本文给大家介绍Mysql虚拟列的相关知识,感兴趣的朋友一起看看吧... 目录1. 介绍mysql虚拟列1.1 定义和作用1.2 虚拟列与普通列的区别2. MySQL虚拟列的类型2

使用MongoDB进行数据存储的操作流程

《使用MongoDB进行数据存储的操作流程》在现代应用开发中,数据存储是一个至关重要的部分,随着数据量的增大和复杂性的增加,传统的关系型数据库有时难以应对高并发和大数据量的处理需求,MongoDB作为... 目录什么是MongoDB?MongoDB的优势使用MongoDB进行数据存储1. 安装MongoDB

mysql数据库分区的使用

《mysql数据库分区的使用》MySQL分区技术通过将大表分割成多个较小片段,提高查询性能、管理效率和数据存储效率,本文就来介绍一下mysql数据库分区的使用,感兴趣的可以了解一下... 目录【一】分区的基本概念【1】物理存储与逻辑分割【2】查询性能提升【3】数据管理与维护【4】扩展性与并行处理【二】分区的

MySQL中时区参数time_zone解读

《MySQL中时区参数time_zone解读》MySQL时区参数time_zone用于控制系统函数和字段的DEFAULTCURRENT_TIMESTAMP属性,修改时区可能会影响timestamp类型... 目录前言1.时区参数影响2.如何设置3.字段类型选择总结前言mysql 时区参数 time_zon

Python MySQL如何通过Binlog获取变更记录恢复数据

《PythonMySQL如何通过Binlog获取变更记录恢复数据》本文介绍了如何使用Python和pymysqlreplication库通过MySQL的二进制日志(Binlog)获取数据库的变更记录... 目录python mysql通过Binlog获取变更记录恢复数据1.安装pymysqlreplicat

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动

使用SQL语言查询多个Excel表格的操作方法

《使用SQL语言查询多个Excel表格的操作方法》本文介绍了如何使用SQL语言查询多个Excel表格,通过将所有Excel表格放入一个.xlsx文件中,并使用pandas和pandasql库进行读取和... 目录如何用SQL语言查询多个Excel表格如何使用sql查询excel内容1. 简介2. 实现思路3

Oracle数据库使用 listagg去重删除重复数据的方法汇总

《Oracle数据库使用listagg去重删除重复数据的方法汇总》文章介绍了在Oracle数据库中使用LISTAGG和XMLAGG函数进行字符串聚合并去重的方法,包括去重聚合、使用XML解析和CLO... 目录案例表第一种:使用wm_concat() + distinct去重聚合第二种:使用listagg,

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Redis延迟队列的实现示例

《Redis延迟队列的实现示例》Redis延迟队列是一种使用Redis实现的消息队列,本文主要介绍了Redis延迟队列的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习... 目录一、什么是 Redis 延迟队列二、实现原理三、Java 代码示例四、注意事项五、使用 Redi