分布式之数据库和缓存双写一致性方案解析

2023-05-16

【本文转自博客园 作者:孤独烟 原文链接:https://www.cnblogs.com/rjzheng/p/9041659.html】
为什么写这篇文章?

  首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用。在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作。

分布式之数据库和缓存双写一致性方案解析

  但是在更新缓存方面,对于更新完数据库,是更新缓存呢,还是删除缓存。又或者是先删除缓存,再更新数据库,其实大家存在很大的争议。目前没有一篇全面的博客,对这几种方案进行解析。于是博主战战兢兢,顶着被大家喷的风险,写了这篇文章。

  文章结构

  本文由以下三个部分组成1、讲解缓存更新策略2、对每种策略进行缺点分析3、针对缺点给出改进方案

  正文

  先做一个说明,从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。这种方案下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。因此,接下来讨论的思路不依赖于给缓存设置过期时间这个方案。在这里,我们讨论三种更新策略:

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

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

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

  应该没人问我,为什么没有先更新缓存,再更新数据库这种策略。

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

  这套方案,大家是普遍反对的。为什么呢?有如下两点原因。原因一(线程安全角度)同时有请求A和请求B进行更新操作,那么会出现(1)线程A更新了数据库(2)线程B更新了数据库(3)线程B更新了缓存(4)线程A更新了缓存这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。原因二(业务场景角度)有如下两点:(1)如果你是一个写数据库场景比较多,而读数据场景比较少的业务需求,采用这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能。(2)如果你写入数据库的值,并不是直接写入缓存的,而是要经过一系列复杂的计算再写入缓存。那么,每次写入数据库后,都再次计算写入缓存的值,无疑是浪费性能的。显然,删除缓存更为适合。

  接下来讨论的就是争议最大的,先删缓存,再更新数据库。还是先更新数据库,再删缓存的问题。

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

  该方案会导致不一致的原因是。同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:(1)请求A进行写操作,删除缓存(2)请求B查询发现缓存不存在(3)请求B去数据库查询得到旧值(4)请求B将旧值写入缓存(5)请求A将新值写入数据库上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。那么,如何解决呢?采用延时双删策略伪代码如下

  public void write(String key,Object data){

  redis.delKey(key);

  db.updateData(data);

  Thread.sleep(1000);

  redis.delKey(key);

  }

  转化为中文描述就是(1)先淘汰缓存(2)再写数据库(这两步和原来一样)(3)休眠1秒,再次淘汰缓存这么做,可以将1秒内所造成的缓存脏数据,再次删除。那么,这个1秒怎么确定的,具体该休眠多久呢?针对上面的情形,读者应该自行评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。如果你用了mysql的读写分离架构怎么办?ok,在这种情况下,造成数据不一致的原因如下,还是两个请求,一个请求A进行更新操作,另一个请求B进行查询操作。(1)请求A进行写操作,删除缓存(2)请求A将数据写入数据库了,(3)请求B查询缓存发现,缓存没有值(4)请求B去从库查询,这时,还没有完成主从同步,因此查询到的是旧值(5)请求B将旧值写入缓存(6)数据库完成主从同步,从库变为新值上述情形,就是数据不一致的原因。还是使用双删延时策略。只是,睡眠时间修改为在主从同步的延时时间基础上,加几百ms。采用这种同步淘汰策略,吞吐量降低怎么办?ok,那就将第二次删除作为异步的。自己起一个线程,异步删除。这样,写的请求就不用沉睡一段时间后了,再返回。这么做,加大吞吐量。第二次删除,如果删除失败怎么办?这是个非常好的问题,因为第二次删除失败,就会出现如下情形。还是有两个请求,一个请求A进行更新操作,另一个请求B进行查询操作,为了方便,假设是单库:(1)请求A进行写操作,删除缓存(2)请求B查询发现缓存不存在(3)请求B去数据库查询得到旧值(4)请求B将旧值写入缓存(5)请求A将新值写入数据库(6)请求A试图去删除请求B写入对缓存值,结果失败了。ok,这也就是说。如果第二次删除缓存失败,会再次出现缓存和数据库不一致的问题。如何解决呢?具体解决方案,且看博主对第(3)种更新策略的解析。

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

  首先,先说一下。老外提出了一个缓存更新套路,名为《Cache-Aside pattern》。其中就指出

  失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。

  命中:应用程序从cache中取数据,取到后返回。

  更新:先把数据存到数据库中,成功后,再让缓存失效。

  另外,知名社交网站facebook也在论文《Scaling Memcache at Facebook》中提出,他们用的也是先更新数据库,再删缓存的策略。

  这种情况不存在并发问题么?

  不是的。假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生

  (1)缓存刚好失效

  (2)请求A查询数据库,得一个旧值

  (3)请求B将新值写入数据库

  (4)请求B删除缓存

  (5)请求A将查到的旧值写入缓存

  ok,如果发生上述情况,确实是会发生脏数据。

  然而,发生这种情况的概率又有多少呢?

  发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。假设,有人非要抬杠,有强迫症,一定要解决怎么办?

  如何解决上述并发问题?首先,给缓存设有效时间是一种方案。其次,采用策略(2)里给出的异步延时删除策略,保证读请求完成以后,再进行删除操作。

  还有其他造成不一致的原因么?有的,这也是缓存更新策略(2)和缓存更新策略(3)都存在的一个问题,如果删缓存失败了怎么办,那不是会有不一致的情况出现么。比如一个写数据请求,然后写入数据库了,删缓存失败了,这会就出现不一致的情况了。这也是缓存更新策略(2)里留下的最后一个疑问。

  如何解决?提供一个保障的重试机制即可,这里给出两套方案。方案一:如下图所示

分布式之数据库和缓存双写一致性方案解析

  流程如下所示(1)更新数据库数据;(2)缓存因为种种问题删除失败(3)将需要删除的key发送至消息队列(4)自己消费消息,获得需要删除的key(5)继续重试删除操作,直到成功然而,该方案有一个缺点,对业务线代码造成大量的侵入。于是有了方案二,在方案二中,启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。

  方案二:

分布式之数据库和缓存双写一致性方案解析

  流程如下图所示:(1)更新数据库数据(2)数据库会将操作信息写入binlog日志当中(3)订阅程序提取出所需要的数据以及key(4)另起一段非业务代码,获得该信息(5)尝试删除缓存操作,发现删除失败(6)将这些信息发送至消息队列(7)重新从消息队列中获得该数据,重试操作。

  备注说明:上述的订阅binlog程序在mysql中有现成的中间件叫canal,可以完成订阅binlog日志的功能。至于oracle中,博主目前不知道有没有现成中间件可以使用。另外,重试机制,博主是采用的是消息队列的方式。如果对一致性要求不是很高,直接在程序中另起一个线程,每隔一段时间去重试即可,这些大家可以灵活自由发挥,只是提供一个思路。

  总结

  本文其实是对目前互联网中已有的一致性方案,进行了一个总结。对于先删缓存,再更新数据库的更新策略,还有方案提出维护一个内存队列的方式,博主看了一下,觉得实现异常复杂,没有必要,因此没有必要在文中给出。最后,希望大家有所收获。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

分布式之数据库和缓存双写一致性方案解析 的相关文章

  • uC/OS-III 学习笔记(一)——uC/OS-III移植

    uC OS III 学习笔记 xff08 一 xff09 uC OS III移植 前言 最近毕设要做一个嵌入式设备 xff0c 需要用到操作系统 本人只会制板和写ARM裸机程序 xff0c OS方面是一个小白 xff0c 只对uC OS和L
  • ST-Link不能下载程序的几种解决办法

    ST Link不能下载程序的几种解决办法 一直在用J LINK xff0c 最近改用ST Link xff0c 出现了不少无法下载程序的情况 xff0c 这里列出几种解决的办法 xff08 针对STM32F103系列 xff09 xff1a
  • Manjaro安装与配置

    Manjaro安装与配置 昨天在原来的OpenSUSE上安装anaconda3 43 tensorflow xff0c 结果系统莫名其妙启动不起来了 xff0c 显示D BUS无法启动 xff0c XServer也出了问题 试了好久没有找到
  • cuda练习(一):使用cuda将rbg图像转为灰度图像

    创建工程 使用cmake创建工程 xff0c CMakeLists txt如下 xff1a cmake minimum required VERSION 2 8 project image process find package Open
  • cuda练习(二):灰度统计直方图

    编写代码 首先将上次的转灰度图的程序拷过来用于生成灰度图 共编写了cpu gpu wrong naive gpu naive gpu usesharemem四种方式实现 cpu版本 cpu版本代码很简单 xff1a void getGray
  • 理论解析:如何让A*寻路算法适应2D网格平台游戏

    本文是系列教程 如何让A 寻路算法适应基于2D网格实现的平台游戏 的第一部分 这个系列教程共有 xff16 个部分 xff0c 作者Daniel Branicki详细解释为什么以及该如何修改A 寻路算法来适应基于2D网格实现的平台游戏 基于
  • cuda练习(三):使用gpu进行排序

    生成数据 为了简便期间 xff0c 生成不重复的数据 define NUM ELEMENT 4096 define NUM LISTS 32 template lt class T gt void c swap T amp x T amp
  • 室内无源定位—激光SLAM在无人机上的飞行测试

    室内无源定位 激光SLAM在无人机上的飞行测试 开篇关于开源SLAM Cartographer的无人机飞行测试总结从0开始构建激光SLAM 开篇 关于无人机 无人车的室内定问题一直是一个老大难问题 xff0c 目前的定位方案分为外部定位和自
  • 基于SLAM的无人机室内自主定位与导航

    TSLAM室内自主定位系统 xff0c 运用slam自主定位技术 xff0c 在复杂环境下 xff0c 无需依赖外部定位信号 xff0c 也能为智能机器人提供可靠的定位信息和地图信息 xff0c 可广泛运用在智能机器人 xff08 无人机
  • 一个毕业6年的程序员工作经历和成长感悟(终)

    接上篇 xff1a 一个毕业6年的程序员工作经历和成长感悟 xff08 上 xff09 一个毕业6年的程序员工作经历和成长感悟 xff08 中 xff09 一个毕业6年的程序员工作经历和成长感悟 xff08 下 xff09 回望过去 6 年
  • Key exchange was not finished,connection is closed近期遇到这个错误sshd更新导致的

    Key exchange was not finished connection is closed cannot negotiate proposals do not match 近期遇到这个错误sshd更新导致的问题 有说jekins的
  • 敏捷之旅大连2013总结回顾

    12月21日 xff0c 敏捷之旅大连站如期召开 xff0c 这是今年我在大连组织的第九次程序员社区活动 xff0c 在此简单总结一下 这次活动考虑到参会人员会比平时多一些 xff0c 所以选择了中山区的比较大的会议室 xff0c 从十二点
  • 演说(zhi)之法

    近年来 xff0c 参加了很多各种各样的技术会议 xff0c 在其中也听了很多高手和牛人们的演说 在总结了自己的一些经验之后 xff0c 也会在一些场合和大家分享 在以上的过程中 xff0c 越来越觉得 xff0c 想要为听众们奉献一场精彩
  • 2013年组织社区活动总结

    不觉间 xff0c 又到了年末岁尾 时间过得真快啊 每到这个时候 xff0c 总是需要对过去的一年做个总结 xff0c 再对明年的事情做个计划 xff0c 今年也不例外 xff0c 呵呵 接下来我就对程序员社区相关工作 xff0c 先做下2
  • 窗体继承,然后实现按钮点击事件的重写

    做了一阵子Winform的程序之后 xff0c 越来越能够做到把窗体 控件等都看作类来对待了 以前做VB的时候 xff0c 对这些控件都是有一种敬畏的心理 xff0c 根本就不敢对其做什么 xff0c 而且当时也的确做不了什么 xff0c
  • 参加百度轻应用编程马拉松总结

    上个周末 xff0c 我到北京参加了百度举办的轻应用编程马拉松大赛 xff0c 感觉非常不错 xff0c 在此总结一下 这是我第一次参加编程马拉松的活动 xff0c 对此充满了好奇也充满了期望 xff0c 更是希望自己以后也能够组织类似的活
  • 将AD的文件导入立创EDA

    https docs lceda cn cn Import Import Altium Designer index html
  • 前天奶奶来了 xff0c 把屋子里面的东西都收拾了一下 xff0c 尤其是佳佳的玩具 xff0c 有好多毛绒玩具 xff0c 都放在一个柜子的层里面了 早上佳佳醒来 xff0c 发现了新大陆 xff01 美羊羊都碰头了 xff01 维尼的碰
  • 超级简单的抽奖工具

    昨天快到中午的时候接到业务部门的一个需求 xff0c 要求对现有的抽奖软件进行改进 问题是 xff1a 现在的抽奖软件每次只能够抽出一个中奖号码 xff0c 而此次设置的各种奖项的中奖人数加起来有500人 xff0c 如果使用原有的软件 x
  • 程序员应知——把小事做好

    在从事软件开发的这些年中 xff0c 近期越来越多地听到这样的论点 xff1a 当前的程序员越来越浮躁 我的感觉也是如此 xff0c 由于在软件公司中 xff0c 人才流动特别快 xff0c 因此很多人的职位也变化的比较快 xff0c 很可

随机推荐

  • 程序员应知——学习、思考与分享

    有人说 xff0c 程序员是个苦差事 xff0c 一辈子总是要不停地学习 xff0c 学习新的技术 xff0c 学习新的架构 xff0c 学习新的工具 xff0c 一旦一段时间不学习 xff0c 就会发现其他人嘴里冒出来的新鲜词 xff0c
  • Evernote和有道云笔记的比较

    每个人可能都有随手记录一些事情的习惯 xff0c 可能是为了不忘记 xff0c 也可能是随时闪现在头脑中的一些想法 xff0c 因此就有了便利贴 xff0c 而在计算机或者说互联网的时代 xff0c 我们就有了更多选择 xff0c 可以随时
  • 软件开发中的哲学——世界的本原是物质(一)

    在这个系列博客的第一篇中 xff0c 首先要涉及到的哲学原理就是 世界的本原是物质 在IT领域 xff0c 有硬件和软件之分 xff0c 而二者之间的关系 xff0c 就和物质与精神类似 没有硬件的存在 xff0c 那么软件就没有能够发挥作
  • 在Prezi中输入简体中文的完美解决方案

    Prezi是一种在线制作演示文档 xff08 PPT xff09 的工具 xff0c 它与传统的Powerpoint或者Keynote的表现形式完全不同 xff0c 被称为 powerpoint的颠覆者 xff0c 在36Kr上曾经有过多篇
  • C程序中头文件相互包含精华(网摘小结)

    h 中一般放的是同名 c 文件中定义的变量 数组 函数的声明 xff0c 需要让 c 外部使用的声明 1 h 文件作用 1 方便开发 包含一些文件需要的共同的常量 结构 类型定义 函数 变量申明 xff1b 2 提供接口 对一个软件包来说可
  • 【无标题】sourceTree使用教程,比TortoiseSVN小乌龟更好用的一款软件

    俗话说的好工欲善其事必先利其器 xff0c Git分布式版本控制系统是我们日常开发中不可或缺的 目前市面上比较流行的Git可视化管理工具有SourceTree Github Desktop TortoiseGit xff0c 综合网上的一些
  • Vim(gvim)配色方案推荐

    如果经常用vim进行编辑 xff0c 那么一款好的vim配色就是必然的啦 xff0c 今天我们就来介绍一下我比较喜欢的几款配色 xff08 配色效果主要针对gvim xff0c 因为在vim下工作都是终端操作 xff09 由于本人是以程序员
  • 如何定义python的全局变量

    定义全局变量 global var 61 10 def some function 在函数中使用全局变量 print 34 Global variable value 34 global var some function 在Python中
  • 网上推荐的学习ucosii的三本书

    1 xff0c 嵌入式实时操作系统uc os II教程 西安电子科技大学出版 xff0c 有流程图 2 xff0c 嵌入式实时操作系统uc os II原理与应用 xff08 第二版 xff09 任哲 北航出版 3 xff0c 基于嵌入式实时
  • UC/OSII源码阅读知识点(第一章)

    嵌入式实时操作系统uc os原理与实践 xff08 卢有亮 电子工业出版社 xff09 1 在STM32上使用的ARM CORTEX处理器中 xff0c 具有主堆栈MSP和进程堆栈PSP xff0c 具有Pendsv和Systick中断 2
  • 802.11控制帧&管理帧

    控制帧主要用于协助数据帧的传递 xff0c 可用于管理无线媒介的访问 提供MAC层的可靠性 以下只讲帧类型 xff0c 不讲帧结构 1 1 RTS帧 xff1a 用来取得媒介的控制权 xff0c 用于传送分段帧 xff0c 分段由网卡驱动程
  • linux常用变量含义

    是传给脚本的参数个数 0 是脚本本身的名字 1 是传递给该shell脚本的第一个参数 2 是传递给该shell脚本的第二个参数 64 是传给脚本的所有参数的列表 是以一个单字符串显示所有向脚本传递的参数 xff0c 与位置变量不同 xff0
  • 四核 x86 MinnowBoard 和 UP Squared 单板计算机

    MinnowBoard的 MinnowBoard Turbot Quad 和Aaeon的UP平方单板计算机开始出货 xff0c 以社区网站和运行Linux和Android的英特尔SoC为特点 5月23日 xff0c Intel支持的Minn
  • 无人机悬停 优象科技LC302 V1.1光流模块

    5月30日 xff0c 学校组织了一年一度的五月风活动 xff0c 每个社团纷纷拿出自己协会的作品 在我们科技爱好者协会中 xff0c 展示了光流模块 xff0c 特斯拉线圈 xff0c 蓝牙小车 xff0c 激光显示仪器 xff0c 空气
  • git中tag与release的创建以及两者的区别

    简介 本文辨析在参与开源项目时会遇到的tag与release的概念区别与联系 xff0c 并比较两者的创建方法 定义 标签 xff08 tag xff09 是特定提交 xff08 commit 一个指针 xff0c 也就是每个tag对应一个
  • PX4源码的Makefile详细理解(包含部分makefile语法规则和编译逻辑)

    啰嗦 xff1a 越会一件事情 xff0c 就会忘了不会一件事情的感觉 前段时间在微信上看到这句话 xff0c 深以为然 xff0c 这就是为什么很多时候懂的人觉得自己讲的很清楚了 xff0c 但是不懂的人却觉得并没有讲的清楚明了 xff0
  • c/c++语言结构体中的冒号的用法

    结构体中常见的冒号的用法是表示位域 有些信息在存储时 xff0c 并不需要占用一个完整的字节 xff0c 而只需占几个或一个二进制位 例如在存放一个开关量时 xff0c 只有0 和 1 两种状态 xff0c 用一位二进位即可 为了节省存储空
  • ubuntu 升级内核的具体步骤

    收藏于 2013 04 09 迁移自本人的百度空间 转载自 http forum ubuntu org cn viewtopic php p 61 2730876 ubuntu 12 04内核是linux 3 2 0 24 xff0c 其实
  • 不花钱的机器人——ROS机器人仿真平台 | 模拟器 | Autolabor Simulation

    没钱买机器人底盘和激光雷达 xff0c 照样也能玩转机器人 xff01 Autolabor Simulation是什么 Autolabor Simulation是由 Autolabor 推出的一款基于ROS xff08 Robot Oper
  • 分布式之数据库和缓存双写一致性方案解析

    本文转自博客园 作者 xff1a 孤独烟 原文链接 xff1a https www cnblogs com rjzheng p 9041659 html 为什么写这篇文章 首先 xff0c 缓存由于其高并发和高性能的特性 xff0c 已经在