当前位置: 首页 > news >正文

如何保证Redis缓存和数据库一致性?

想要保证缓存与数据库的双写一致,一共有4种方式:
先更新缓存,再更新数据库;
先更新数据库,再更新缓存;
先删除缓存,再更新数据库;
先更新数据库,再删除缓存。

我们需要做的是根据不同的场景来使用合理的方式来解决数据问题。

第一种:先删除缓存,再更新数据库
在出现失败时可能出现的问题:

  1. 线程A删除缓存成功,线程A更新数据库失败;
  2. 线程B从缓存中读取数据;由于缓存被删,进程B无法从缓存中得到数据,进而从数据库读取数据;此时数据库中的数据更新失败,线程B从数据库成功获取旧的数据,然后将数据更新到了缓存。
    最终,缓存和数据库的数据是一致的,但仍然是旧的数据。

第二种:先更新数据库,再删除缓存
假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生

  1. 缓存刚好失效
  2. 请求A查询数据库,得一个旧值
  3. 请求B将新值写入数据库
  4. 请求B删除缓存
  5. 请求A将查到的旧值写入缓存
    如果发生上述情况,确实是会发生脏数据。
    然而,发生这种情况的概率又有多少呢?
    发生上述情况有一个先天性条件,就是步骤3的写数据库操作比步骤2的读数据库操作耗时更短,才有可能使得步骤4先于步骤5
    数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤3耗时比步骤2更短,这一情形很难出现。
    先更新数据库,再删缓存依然会有问题,不过,问题出现的可能性会因为上面说的原因,变得比较低。

第三种:给所有的缓存一个失效期
第三种方案可以说是一个大杀器,任何不一致,都可以靠失效期解决,失效期越短,数据一致性越高。但是失效期越短,查数据库就会越频繁。因此失效期应该根据业务来定。

  1. 并发不高的情况:
    读: 读redis->没有,读mysql->把mysql数据写回redis,有的话直接从redis中取;
    写: 写mysql->成功,再写redis;
  2. 并发高的情况:
    读: 读redis->没有,读mysql->把mysql数据写回redis,有的话直接从redis中取;
    写:异步话,先写入redis的缓存,就直接返回;定期或特定动作将数据保存到mysql,可以做到多次更新,一次保存;

第四种:加锁,使线程顺序执行
如果一个服务部署到了多个机器,就变成了分布式锁,或者是分布式队列按顺序去操作数据库或者 Redis,带来的副作用就是:数据库本来是并发的,现在变成串行的了,加锁或者排队执行的方案降低了系统性能,所以这个方案看起来不太可行。

第五种:采用双删
先删除缓存,再更新数据库,当更新数据后休眠一段时间再删除一次缓存。

方案推荐两种:

  1. 项目整合quartz等定时任务框架,去实现延时3–5s再去执行最后一步任务 。(推荐使用)
  2. 创建线程池,线程池中拿一个线程,线程体中延时3-5s再去执行最后一步任务(不能忘了启动线程)

第六种:异步更新缓存(基于订阅binlog的同步机制)
MySQL binlog增量订阅消费+消息队列+增量数据更新到redis读Redis
热数据基本都在Redis写MySQL:增删改都是操作MySQL更新Redis数据:MySQ的数据操作binlog,来更新到Redis:
1)数据操作主要分为两大块:一个是全量(将全部数据一次写入到redis)一个是增量(实时更新)。
这里说的是增量,指的是mysql的update、insert、delate变更数据。
2)读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。
这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。
其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。
这里可以结合使用canal(阿里的一款开源框架),通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。
当然,这里的消息推送工具你也可以采用别的第三方:kafka、rabbitMQ等来实现推送更新Redis。

以上就是redis和数据库数据保持一致的方案。

相关文章:

  • 2023年网络安全比赛--attack(新)数据包分析中职组(超详细)
  • 【Android -- 软技能】聊聊程序员的软技能
  • STM32F1硬件SPI驱动nRF24L01通过按键控制数据收发带状态反馈
  • 2022济南大学acm新生赛题解
  • ES+Redis+MySQL,这个高可用架构设计太顶了!
  • js逆向爬取某音乐网站某歌手的歌曲
  • Python打包成exe,文件太大问题解决办法(比保姆级还保姆级)
  • 【小白】git是什么?gitee和git和github的关系?
  • 如何用Python求解微分方程组
  • 初识C++需要了解的一些东西(2)
  • 【面试题】面试官:如果后端给你 1w 条数据,你如何做展示?
  • 伯努利方程示例 Python 计算(汽水流体和喷泉工程)
  • 想要成为高级网络工程师,只需要具备这几点
  • 【Python练习】序列结构
  • 【微信小程序】-- 网络数据请求(十九)
  • 【React系列】如何构建React应用程序
  • 【个人向】《HTTP图解》阅后小结
  • EOS是什么
  • vue自定义指令实现v-tap插件
  • 大整数乘法-表格法
  • 机器人定位导航技术 激光SLAM与视觉SLAM谁更胜一筹?
  • 基于Mobx的多页面小程序的全局共享状态管理实践
  • 模仿 Go Sort 排序接口实现的自定义排序
  • 如何借助 NoSQL 提高 JPA 应用性能
  • 试着探索高并发下的系统架构面貌
  • 小程序开发中的那些坑
  • 移动端唤起键盘时取消position:fixed定位
  • 正则表达式
  • puppet连载22:define用法
  • 进程与线程(三)——进程/线程间通信
  • ![CDATA[ ]] 是什么东东
  • #HarmonyOS:Web组件的使用
  • (3)(3.2) MAVLink2数据包签名(安全)
  • (C语言)二分查找 超详细
  • (Matalb时序预测)WOA-BP鲸鱼算法优化BP神经网络的多维时序回归预测
  • (Redis使用系列) SpirngBoot中关于Redis的值的各种方式的存储与取出 三
  • (Redis使用系列) Springboot 使用redis实现接口Api限流 十
  • (WSI分类)WSI分类文献小综述 2024
  • (九)One-Wire总线-DS18B20
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • (转)EOS中账户、钱包和密钥的关系
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • (转载)跟我一起学习VIM - The Life Changing Editor
  • .net 7 上传文件踩坑
  • .Net Core 中间件与过滤器
  • .net framework 4.0中如何 输出 form 的name属性。
  • .Net Memory Profiler的使用举例
  • .Net 基于.Net8开发的一个Asp.Net Core Webapi小型易用框架
  • .NET简谈互操作(五:基础知识之Dynamic平台调用)
  • .NET值类型变量“活”在哪?
  • /*在DataTable中更新、删除数据*/
  • /etc/fstab和/etc/mtab的区别
  • ??javascript里的变量问题
  • @PostConstruct 注解的方法用于资源的初始化
  • @property @synthesize @dynamic 及相关属性作用探究