Redis中缓存和数据库双写数据不一致

news2024/10/6 16:27:08

先更新数据库,还是先更新缓存?

1.先更新数据库,再更新缓存
2.先更新缓存,再更新数据库
在这里插入图片描述

1.先更新数据库,再更新缓存

举个例子,比如【请求A】和【请求B】两个请求,同时更新【同一条】数据,
则可能出现图中的顺序:
【请求A】先将数据库的数据更新为1,然后在更新缓存前,【请求B】将数据库的数据更新为2,紧接着把缓存更新为2,然后【请求A】更新缓存为1.此时,数据库中的数据是2,而缓存中的数据却是1,出现了缓存和数据库中的数据不一致的现象
在这里插入图片描述

2.先更新缓存,再更新数据库。

举个例子,【请求A】和【请求B】两个请求,同时更新【同一条】数据,
则可能出现这样的顺序:
【请求A】先将缓存的数据更新为1,然后在更新数据库前,【请求B】来了,将缓存的数据更新为2,紧接着把把数据库更新为2,然后【请求A】将数据库的数据更新为1.此时,数据库中的数据是1,而缓存中的数据却是2,出现了缓存和数据库中的数据不一致的现象
在这里插入图片描述

结论

所以,无论是【先更新数据库,再更新缓存】,还是【先更新缓存,再更新数据库】,
这两个方案都存在并发问题,当两个请求并发更新同一条数据的时候,可能会出现
缓存和数据库中的数据不一致的现象

Cache Aside策略

Cache Aside(旁路缓存)策略,该策略可以细分为【读策略】和【写策略】
写策略的步骤:
1.更新数据库中的数据;
2.删除缓存中的数据

读策略的步骤:
1.如果读取的数据命中了缓存,则直接返回数据
2.如果读取的数据没有命中缓存,则从数据库中读取数据,然后将数据写入到缓存,并且返回给用户

但是【写策略】中的数据库和缓存操作又有不同的顺序:
1.先删除缓存,再更新数据库
2.先更新数据库,再删除缓存
在这里插入图片描述

1.先删除缓存,再更新数据库。

举个例子,以用户表的场景来分析。
假设某个用户的年龄是20,请求A要更新用户年龄为21,所以它会删除
缓存中的内容。这时,另一个请求B要读取这个用户的年龄,它查询缓存
发现未命中后,会从数据库中读取到年龄为20,并且写入到缓存中,然后
请求A继续更改数据库,将用户的年龄更新为21.

最终,该用户年龄在缓存中是20(旧值),在数据库中是21(新值),缓存和
数据库的数据不一致。可以看到,先删除缓存,再更新数据库,在【读+写】并发的时候,还是会出现缓存和数据库的数据不一致的问题
在这里插入图片描述

解决方案:

针对【先删除缓存,再更新数据库】方法在【读+写】并发请求
而造成缓存不一致的解决办法是【延迟双删】:
伪代码示例。加了个睡眠时间,主要是为了确保请求A在睡眠的时候,请求B能够在这一段时间内完成【从数据库读取数据,再把缺失的缓存写入缓存】的操作,然后请求A睡眠完,再删除缓存。所以请求A的睡眠时间就需要大于请求B【从数据库读取数据+写入缓存】的时间。但是具体睡眠多久其实我们是没法准确预估的,需要进行统计,所以这个方案尽可能保证一致性而已,极端情况下,依然也会出现缓存不一致的现象,因此,还是比较建议用【先更新数据库,再删除缓存】的方案

#删除缓存
redis.delKey(X);
#更新数据库
db.update(X);
#睡眠
Thread.sleep(N);
#再删除缓存
redis.delKey(X);

2.先更新数据库,再删除缓存

继续用【读+写】请求的并发的场景来分析。
假如某个用户数据在缓存中不存在,请求A读取读取数据时从数据库中查询到年龄为20,在未写入缓存中时另一个请求B更新数据。它更新数据库中的年龄为21,并且清空缓存。这时请求A把数据库中读到的年龄为20的数据写入到缓存中。最终,该用户年龄在缓存中是20,数据库中是21,缓存和数据库数据不一致。
在这里插入图片描述

分析

从上面的理论上分析,先更新数据库,再删除缓存也是会出现数据不一致性的问题,但是在实际中,这个问题出现的概率并不高。因为缓存的写入通常要远远快于数据库的写入,所以在实际中很难出现请求B已经更新了数据库并且删除了缓存,请求A才更新完
缓存的情况。而一旦请求A早于请求B删除缓存之前更新了缓存,那么接下来的请求就会因为缓存不命中而从数据库中重新读取数据,所以不会出现这种不一致的情况。所以,【先更新数据库+再删除缓存】的方案,是可以保证数据一致性的,再加上一个【过期时间】,就算在这期间存在缓存数据不一直,有过期时间来兜底,这样也能达到最终一致。

【先更新数据库,再删除缓存】存在的问题:

前面的分析都是建立再这两个操作都能同时执行成功的情况下,如果在删除缓存(第二个操作)的时候失败了,导致缓存中的数据是旧值,如果没有前面的过期时间兜底的话,后续的请求就会一直是缓存中的就数据

【先更新数据库,再删除缓存】的方案虽然保证了数据库与缓存的数据一致性,但是每次更新数据的时候,缓存的数据都会被删除,这样会对缓存的命中率带来影响。所以,如果业务对缓存命中率有很高的要求,可以采用【更新数据库+更新缓存】的方案,因为更新缓存并不会出现缓存未命中的情况,但是这个方案,前面提到,在两个更新请求并发执行的
时候,会出现数据不一致的问题,因为更新数据库和更新缓存这两个操作是独立的,我们又没有对操作做任何并发控制,那么当两个线程并发更新它们的话,就会因为写入顺序的不同造成数据不一致需要增加一些手段来解决这个问题,有两种做法

  • 1.在更新缓存前先加个分布式锁,保证同一时间之运行一个请求更新缓存,就不会产生并发问题了,但是引入锁之后,对于写入性能就会带来影响
  • 2.在更新完缓存时,给缓存加上较短的过期时间,这样即时出现缓存不一致的情况,缓存的数据也会很快过期,对业务来说也可以接受

如何保证【先更新数据库,再删除缓存】这两个操作能执行成功?

举个例子:
应用要把数据X的值从1更新为2,先成功更新了数据库,然后在Redis缓存
中删除X的缓存,但是这个操作却失败了,这个时候数据库中的X的新值为2,Redis中的X的缓存值为1,出现了数据库和缓存数据不一致的问题。
那么后续有访问数据X的请求,会先在Redis中查询,因为缓存中并没有删除,所以缓存命中,但是读到的却是旧值1.其实不管先操作数据库,还是先操作缓存,只要第二个操作失败都会出现数据不一致的问题,解决方案有两种:

  • 1.重试机制
  • 2.订阅MySQL binlog,再操作缓存
    在这里插入图片描述

1.重试机制。

我们可以引入消息队列,将第二个操作(删除缓存)要操作的数据加入到消息队列,由消费者来操作数据。

  • 1.1 如果应用删除缓存失败,可以从消息队列中重新读取数据,然后再次删除缓存,这个就是重试机制。当然,如果重试超过一定的次数,还是没有成功,就需要向业务层发送报错消息了
  • 1.2 如果删除缓存成功,就要把数据从消息队列中移除,避免重复操作,否则就继续重试
    在这里插入图片描述

2.订阅MySQL binlog,再操作缓存

【先更新数据库,再删除缓存】的策略第一步是更新数据库,那么更新数据库成功,就会产生一条变更日志,记录在binlog里。于是我们就可以通过订阅binlog日志,拿到具体要操作的数据,然后再执行缓存删除,阿里开源的Cannal中间件就是基于这个实现的。

Cannal模拟MySQL主从复制的交互协议,把自己伪装成一个MySQL的从节点,向MySQL主节点发送dump请求,MySQL收到请求后,就会开始推送binlog给Cannal,Cannal解析binlog字节流之后,转换为便于读取的结构化数据,供下游程序订阅使用.
在这里插入图片描述
所以如果要想保证【先更新数据库,再删除缓存】策略第二个操作能执行成功,我们可以使用【消息队列来重试缓存的删除】,或者【订阅MySQL binlog再操作缓存】,这两种方法有一个共同的特点,都是采用异步操作缓存

疑问

为什么是删除缓存,而不是更新缓存?
删除一个数据,相比更新一个数据更加轻量级,出问题的概率更小。在实际业务中,缓存的数据可能不是直接来自数据库表,也许来自多张底层数据表的聚合。比如商品详情信息,在底层可能会关联商品表、价格表、库存表等,如果更新了一个价格字段,那么就要更新整个数据库,还要关联的去查询和汇总各个周边业务系统的数据,这个操作会非常耗时。从另外一个角度,不是所有的缓存数据都是频繁访问的,更新后的
缓存可能会长事件不被访问,所以说,从计算资源和整体性能的考虑,更新的时候删除缓存,等到下次查询命中再填充缓存,是一个更好的方案。
系统设计中有一个设计叫Lazy Loading,适用于那些加载代价大的操作,删除缓存而不是更新缓存,就是懒加载思想的一个应用

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分布式搜索引擎elasticsearch(2)

1.DSL查询文档 elasticsearch的查询依然是基于JSON风格的DSL来实现的。 1.1.DSL查询分类 Elasticsearch提供了基于JSON的DSL([Domain Specific Language](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html))来定义查…

python爬虫实战——小红书

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

让手机变相机,微单变全画幅的外设-斯莫格摄影套装开箱

大家好,我是Circaboy,近期给我的手机和相机入了一整套专业的摄影外设套装,然后我发现专业设备加持下的摄影着实是比我之前的要好很多,所以我就写了个文章做个简单的介绍和专业外设加持下的摄影对比。 本次入手的是斯莫格摄影套装…

Linux学习(4)——使用编辑器

1.gedit编辑器 简单易懂,依赖图形界面。可以使用ctrlc ctrlv等快捷键,ctrls进行保存,与windows系统中相类似。 2.vi/vim编辑器 vi/vim可以直接通过控制台的终端完成文本的编辑,不依赖图形界面,使用范围更广。它的编辑…

cesiumlab中shp转3dtiles白模效果一

安装cesiumlab 如果没有安装cesiumlab,去官网下载安装一个即可 http://www.cesiumlab.com/cesiumlab.html 效果 步骤 1、准备shp面数据 2、打开cesiumlab软件转换 选择shp面数据 设置高度,如果shp面中有高度字段,可以用高度字段&#xff…

ASP.NET排课实验室排课,生成班级课表实验室课表教师课表(vb.net)-214-(代码+说明)

转载地址: http://www.3q2008.com/soft/search.asp?keyword214 要看成品演示 请联系客服发给您成品演示 课题:实验课排课系统 计算机 上机课 一周上5天课,周一到周五 一周上5天课,周一到周五 因为我排的是实验课,最好1&#xf…

javaweb day16 mysql

mysql 安装: 企业开发使用方法 安装虚拟机代替服务器 数据模型 创建数据库 写法 sql简介

【考研数学】打基础用张宇《30讲》还是武忠祥《基础篇》?

基础课不太可能所有的东西全都覆盖,还是先搭起一个知识框架,然后不断的填充和完善。 所以不必太过于在意少一些东西,我们不可能一口吃成胖子,基础知识肯定不会遗漏的,只可能一些技巧不到位。 从自己的情况考虑&#…

HTTP压测工具wrk安装与使用

一、前言 wrk是一个基于C语言开发的用于HTTP性能测试的开源工具,它可以模拟多个并发连接,测量服务器的响应时间和吞吐量,并且会给出较为全面的测试结果 1、本文主要内容 在Windows、macOS、Linux(CentOS & Ubuntu等&#xff…

使用ChatGPT高效完成简历制作[中篇]-有爱AI实战教程(五)

演示站点: https://ai.uaai.cn 对话模块 官方论坛: www.jingyuai.com 京娱AI 导读:在使用 ChatGPT 时,当你给的指令越精确,它的回答会越到位,举例来说,假如你要请它帮忙写文案,如果没…

上海亚商投顾:沪指震荡调整 飞行汽车概念股持续爆发

上海亚商投顾前言:无惧大盘涨跌,解密龙虎榜资金,跟踪一线游资和机构资金动向,识别短期热点和强势个股。 一.市场情绪 沪指昨日震荡调整,深成指走势稍强,创业板指一度涨超1%,黄白二线走势分化&a…

Nginx怎么去做负载均衡?

一.什么是负载均衡? 负载均衡是一种在计算机网络中分配工作负载的技术,旨在将请求或任务均匀地分配给多个服务器、计算资源或其他设备,以避免单个节点过载,并提高系统的可靠性、稳定性和性能。负载均衡通常用于分布式系统、网络服…

QT网络编程之实现TCP客户端和服务端

一.QT5.12实现TCP客户端和服务端功能 1.QT中实现TCP通信主要用到了以下类:QTcpServer、QTcpSocket、QHostAddress 2.基本流程: 使用QTcpServer来创建一个TCP服务器,在新的连接建立时,将新建立连接的socket添加到列表中&#xf…

Copilot如何将word文稿一键转为PPT

背景 很多小伙伴平时经常会遇到的一个场景是,如何将word文稿图文转为PPT。 这个过程是既复杂而又无趣的。 现在,有了copilot,你可以一键搞定! 使用copilot Pro来实现 比如我们想要做一个关于copilot studio的PPT展示&#xf…

使用tui-image-editor 图片编辑 标注图片

需求背景: 鼠标悬浮在图片上 出现编辑按钮 点击编辑 对该图片进行编辑(输入文案、涂鸦、标记、裁剪等) 可以体验一下它线上编辑器 Image-editor | TOAST UI :: Make Your Web Delicious! 使用 首先在你的前端项目中安装: np…

Spring Boot 中的 Sleuth 详解

Spring Boot 中的 Sleuth 是一个用于分布式追踪的库,它可以帮助你追踪和理解分布式系统中的请求如何跨越多个服务和网络调用。通过使用 Sleuth,你可以收集关于请求路径、延迟、异常等的信息,从而更容易地诊断问题并进行性能优化。 一、下面是…

chown: changing ownership of ‘.‘: Permission denied 的一种解法

前言 最近在新电脑用 colima docker 启动服务遇到了这样的报错 chown: changing ownership of .: Permission denied在网上搜索了很久,不管是google还是stack overflow都没有突破口,只要绑定了 volumes 就会报错,按照网上说的方法&#xff…

分布式协议笔记

目录 一致性协议 2pc 3pc CanCommit PreCommit doCommit 回滚 3PC的优点和缺陷 paxos算法 一、Paxos算法背景 二、Paxos算法流程 Paxos算法实例1 Paxos算法实例2 Paxos算法实例3 三、Multi-Paxos算法 附Paxos算法推导过程 raft 概念介绍 算法步骤 Leader选…

css实现高度是宽度一半的效果

1、方法一&#xff1a;使用变量:root、var()、clac()实现&#xff1a; 1.1 效果如下&#xff1a; 2.2 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title>&l…

AMEYA360:稳先微汽车驱动芯片—智能高边开关WS7系列

近几年&#xff0c;新能源汽车高速发展&#xff0c;用车浪潮蔓延全球&#xff0c;我国新能源汽车占有量连续9年居全球前列&#xff0c;2023年全年市占率达37.7%&#xff0c;市场规模可观&#xff0c;并显现出以下特点&#xff1a;电车产品对比油车优势明显、消费者接受度高、市…