分布式系统:缓存与数据库一致性问题

news2024/11/25 17:23:08

前言

缓存设计是应用系统设计中重要的一环,是通过空间换取时间的一种策略,达到高性能访问数据的目的;但是缓存的数据并不是时刻存在内存中,当数据发生变化时,如何与数据库中的数据保持一致,以满足业务系统要求,本篇将给出具体分析。

image.png

一致性分类

  • 强一致性:这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大,这种情况比如秒杀系统,商家后台,他会设置秒杀商品,参与秒杀活动,一旦说他参与了秒杀活动,商品的库存本来是在数据库里的,此时必须直接被加载到缓存里,缓存立马就要可以被使用。
  • 弱一致性:这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态
  • 最终一致性:最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型,比如微博的粉丝数,页面每天的访问数

缓存更新机制

缓存的更新,一般分为被动更新与主动更新,被动更新是指缓存在有效期到后,被淘汰。

被动更新如下步骤:
step1: 发起方查数据,缓存中没有,从数据库中获取,并写入缓存,同时设置过期时间 t;
step2: 在 t 内,所有的查询,都由缓存提供,所有的写,直接写数据库;
step3: 当缓存数据到过期时间 t 后,缓存数据失效。后面的查询,回到了第 1 步。

主动更新,一般为调用方发起缓存与数据库同时更新,缓存分为删除、更新,数据库分为更新,通过组合与先后顺序,分为如下四种情况:
更新缓存、更新数据库更新数据库,更新缓存删除缓存,更新数据库更新数据库,删除缓存

更新缓存、更新数据库

这种情况,当缓存更新成功,数据库更新不成功时,数据不一致的风险比较高,所以一般不采用

更新数据库、更新缓存

当更新完数据库,缓存的加载前需要通过大量复杂计算才能得出缓存的值,不仅让发起方阻塞,影响性能;而且如果缓存命中率不高,很少使用,更浪费前期的复杂计算成本与缓存空间,这里就不符合懒加载的设计思想,故一般也不采用

删除缓存、更新数据库

如图所示,当两个调用方线程高并发访问的情况下,A 线程先删除缓存,再更新数据库,此过程时间较长,B 线程在 A 删除缓存后,迅速读取缓存,因缓存每命中,从数据库中读取再加载缓存,此时缓存还是旧值,等 A 线程更新完数据库后,发现又出现数据不一致的现象。

image.png

一般大概率情况下,出现此根源的原因是读比写快,所以这种一般也不采用,如果非得采用,需要在写完数据库之后延迟一段时间再删除一次缓存,也就是我们熟知的 延时双删,延迟多久呢,一般看数据库的更新时长来决定,此做法也会带来系统吞吐量下降

Cache-Aside

也叫做旁路缓存模式,流程就是先更新数据库,再删除缓存,虽然这种方式也会带来不一致的情况,比如如下场景:

image.png

前提:缓存无数据,数据库有数据。
A:查询,B:更新
过程如下:
step1: A 查缓存,无数据,去读数据库,旧值;
step2: B 更新数据库为新值;
step3: B 删除缓存;
step4: A 将旧值写入缓存。

该场景最终也会出现不一致,产生的根源是是读比写慢,这种是小概率事件,一般很少出现,如果非要解决这种情况,还是上面说的延迟双删

Read/Write Through

上面的方式,数据库是缓存的来源,主导是数据库,而  Read/Write Through模式,相当于缓存占主导。在 cache-aside 模式中,我们的应用代码需要维护两个数据存储,一个是缓存(Cache),一个是数据库(Repository)。而 Read/Write Through 做法是把更新数据库(Repository)的操作由缓存自己代理了,所以,对于应用层来说,就简单很多了。可以理解为,应用认为后端就是一个单一的存储,而存储自己维护自己的 Cache。

Read Through

Read Through  就是在查询操作中更新缓存,也就是说,当缓存失效的时候(过期或 LRU 换出),Cache Aside 是由调用方负责把数据加载入缓存而 Read Through 则用缓存服务自己来加载,从而对应用方是透明的

image.png

这个简要流程和 Cache-Aside 很像,其实Read-Through就是多了一层Cache-Provider,流程如下:

image.png

Read-Through 实际只是在Cache-Aside之上进行了一层封装,它会让程序代码变得更简洁,同时也减少数据源上的负载

Write Through

Write Through,和 Read Through 相仿,不过是在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由 Cache 自己同步更新数据库

image.png

值得注意的是,该方案在实现过程中,程序启动时,需将数据库的数据, 提前放到缓存中,不能等启动完成,再放缓存中。

Read Through/Write Through 策略的特点是由缓存节点而非应用程序来和数据库打交道,在我们开发过程中相比 Cache Aside 策略要少见一些,原因是我们经常使用的分布式缓存组件,无论是 Memcached 还是 Redis 都不提供写入数据库和自动加载数据库中的数据的功能。而我们在使用本地缓存的时候可以考虑使用这种策略。

Write Behind

Write Behind 又叫 Write Back。底层思想就是在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。这个设计的好处就是让数据的 I/O 操作速度飞快(因为是直接操作内存),同时带来吞吐量大幅上升;因为异步,Write Behind 还可以合并对同一个数据的多次操作,所以性能的提高是相当可观的。

image.png

但是,其带来的问题是,数据不是强一致性的,而且可能会丢失(我们知道 Unix/Linux 非正常关机会导致数据丢失,就是因为这个事)。在软件设计上,我们基本上不可能做出一个没有缺陷的设计,就像算法设计中的时间换空间,空间换时间一个道理,有时候,强一致性和高性能,高可用和高性性是有冲突的。如果说软件功能模块的思维是逻辑与实现,那么软件架构设计的思维是权衡与取舍

这种方式下,缓存和数据库的一致性不强,对一致性要求高的系统要谨慎使用。但是它适合频繁写的场景,MySQL 的InnoDB Buffer Pool 机制就使用到这种模式。

Write Behind 实际应用

按照实际架构应用层面落地参考方案,流程图如下(以用户发表视频为例)

image.png

按照这种方案,正常来说只要 10 分钟内数据正常写完就没问题,可以实现最终一致性,但是一旦超出 10 分钟,就会缓存失效,造成缓存不一致

如果出现这种 kafka 消费入库失败,则会触发报警系统,看具体是什么问题,基本上 kaka 入库失败只有两种情况,一种是出错了,另外一种是消费能力不够,那么直接扩容即可

后记

说到底这个问题根本没有通用方案,需要根据场景做权衡,比如类似于微博这样高并发场景,那么上边只要涉及删除缓存的方案基本都很难实现,因为很可能删除缓存的下一时间热点数据直接全部打在数据库上,整个服务直接崩溃

然后本质上缓存和数据库的更新就不是一个原子操作,想要彻底实现强一致性,可以了解下分布式事务或是其他强一致性协议,比如说两阶段提交协议 —— prepare, commit/rollback,比如 Java 7 的 XAResource,还有 MySQL 5.7 的 XA Transaction,有些 cache 也支持 XA,比如 EhCache

参考链接

  • 美团二面:Redis 与 MySQL 双写一致性如何保证? - 掘金 (juejin.cn)
  • 分布式缓存--缓存与数据库一致性方案 - 小猪爸爸 - 博客园 (cnblogs.com)
  • 如何保障 MySQL 和 Redis 的数据一致性? | 二哥的 Java 进阶之路 (javabetter.cn)
  • 三种缓存策略:Cache Aside 策略、Read/Write Through 策略、Write Back 策略
  • 分布式系统中的缓存与数据库一致性

本文由博客一文多发平台 OpenWrite 发布!

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

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

相关文章

Excel·VBA二维数组S形排列

与之前的文章《ExcelVBA螺旋数组函数》将一维数组转为二维螺旋数组 本文将数组转为S形排列的二维数组,类似考场座位S形顺序 Function S形排列(ByVal arr, ByVal num_rows&, ByVal num_cols&, Optional ByVal mode$ "row")将数组arr转为num_rows…

【并发】 第四篇 原子操作(一)

导航 一. 非原子操作二. 原子操作1. 使用锁2. 使用原子类一. 非原子操作 首先在并发场景中, 一个线程需要对内存中的某个共享变量的值进行修改时,大致可分为以下三个步骤: # 1.从内存中读取共享变量的值,到线程本地 # 2.执行修改操作 # 3.将修改后的新值,重新写回到内存中此时…

PostgreSQL入门到实战-第十九弹

PostgreSQL入门到实战 PostgreSQL中表连接操作(三)官网地址PostgreSQL概述PostgreSQL中INNER JOIN命令理论PostgreSQL中INNER JOIN命令实战更新计划 PostgreSQL中表连接操作(三) 使用PostgreSQL INNER JOIN子句从多个表中选择数据。 官网地址 声明: 由于操作系统, 版本更新等…

Android 输入法框架

输入法属于输入系统的一部分,区别于输入系统只能向系统产生时间,输入法能向系统输入具体的内容,下面来认识输入法的大体框架,以下内容参考清华大学出版社出版的《Android图形显示系统》。 输入法框架包含3个组件,各组件…

车载视频智能创作解决方案,影视级视频制作

当下车载视频已经不仅仅是一种记录行车过程的方式,更是展现企业品牌形象、传递文化内涵的重要媒介。传统的车载视频制作方式往往耗时耗力,效率低下,无法满足企业对于高质量、高效率的需求。针对这一痛点,美摄科技凭借其在视频智能…

搭建MyBatis框架

文章目录 一、开发环境和准备工作二、创建MyBatis核心配置文件三、创建Mapper接口创建MyBatis的映射文件 四、测试添加用户功能五、日志加入依赖加入log4j的配置文件 六、测试修改功能七、测试删除功能八、测试查询功能根据id查询查询所有信息 一、开发环境和准备工作 MyBatis需…

Python爬虫怎么挣钱?6个Python爬虫赚钱方式,搞搞副业不是问题

1.最典型的就是找爬虫外包活儿 网络爬虫最通常的的挣钱方式通过外包网站,做中小规模的爬虫项目,向甲方提供数据抓取,数据结构化,数据清洗等服务。新入行的程序员大多都会先尝试这个方向,直接靠技术手段挣钱&#xff0…

如果学又学不进,产又产不出,又需要挣钱生存,应该怎么办?机会很多,为下次做好准备

面对学习困难、工作压力和生存需求,可以考虑以下方法来应对: 寻求帮助:如果学习困难,可以寻求老师、同学或专业人士的帮助和指导。他们可能能够提供额外的支持和解决方案。 调整学习方法:尝试不同的学习方法和技巧&am…

2024 年第十四届 MathorCup 完整解题思路

C 题 物流网络分拣中心货量预测及人员排班 电商物流网络在订单履约中由多个环节组成,图 1 是一个简化的物流 网络示意图。其中,分拣中心作为网络的中间环节,需要将包裹按照不同 流向进行分拣并发往下一个场地,最终使包裹到达消费者手中。分拣中心 管理效率的提升,对整体网络的…

K8S资源管理之计算资源管理

1.详解Requests和Limits参数 以CPU为例,下图显示了未设置Limits与设置了Requests和Limits的CPU使用率的区别 尽管Requests和Limits只能被设置到容器上,但是设置了Pod级别的Requests和Limits能大大提高管理Pod的便利性和灵活性,因此在Kubernet…

基于Java停车场管理系统设计与实现(源码+部署文档)

博主介绍: ✌至今服务客户已经1000、专注于Java技术领域、项目定制、技术答疑、开发工具、毕业项目实战 ✌ 🍅 文末获取源码联系 🍅 👇🏻 精彩专栏 推荐订阅 👇🏻 不然下次找不到 Java项目精品实…

Android Studio导入第三方so库和jar包——Android Studio

导入so库 方式一(libs文件夹) 将项目以【Project】的结构显示,将目标架构对应的so文件夹(如下图中 的arm64-v8a)复制粘贴到app文件下的lib文件夹中(如下图的步骤1 2 3) 在build.gradle&…

网络原理(应用层、传输层)

文章目录 一、应用层1.1 自定义协议1.2 通用协议XMLJSONprotobuf 1.3 DNS 域名解析系统 二、传输层2.1 UDP协议2.2 TCP协议协议端格式及解析可靠性机制确认应答超时重传连接管理(三次握手,四次挥手)流量控制拥塞控制 效率机制滑动窗口延迟应答…

基于java+springboot+vue实现的西安旅游系统(文末源码+Lw)23-265

摘 要 如今社会上各行各业,都喜欢用自己行业的专属软件工作,互联网发展到这个时候,人们已经发现离不开了互联网。新技术的产生,往往能解决一些老技术的弊端问题。因为传统西安旅游系统信息管理难度大,容错率低&#…

oracle创建整个数据库的只读账户

在源用户readonly 下创建只读用户 reader readonly 的表空间为AA 一、创建只读用户 create user reader identified by 密码 default tablespace AA; 二、授权 grant connect to reader ; 三、获取原账号readonly 的查询权限 select grant select on ||owner||.||object…

极狐GitLab对接OAuth2实现SSO

本文作者:极狐(GitLab) 高级解决方案架构师 武让 GitLab 是一个全球知名的一体化 DevOps 平台,很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版,专门为中国程序员服务。可以一键式部署极狐GitLab。 企…

代码+视频,R语言手动绘制连续线条的校准曲线(Calibration curve)(3)

校准曲线图表示的是预测值和实际值的差距,作为预测模型的重要部分,目前很多函数能绘制校准曲线。一般分为两种,一种是通过Hosmer-Lemeshow检验,把P值分为10等分,求出每等分的预测值和实际值的差距。 我们既往已经通过多…

淘宝1688京东店铺所有商品数据接口(item_search_shop接口系列,可测试)

淘宝、1688和京东都提供了API接口供开发者调用,以获取店铺和商品的详细数据。对于您提到的item_search_shop接口系列,这主要是用于获取店铺所有商品的数据。然而,具体的接口名称和功能可能会因平台而异,且可能随着平台的更新而有所…

IE浏览器清理缓存工具

有些项目可能因为浏览器缓存导致使用异常,比如登陆异常。这里提供清除浏览器痕迹的工具,以IE浏览器为例,痕迹的默认存放位置为: C:\Users\Ro\AppData\Local\Microsoft\Windows\Temporary Internet Files 新建bat或者cmd批处理文件…

关于光模块SFP-10G-SR、SFP-10G-LRM和SFP-10G-LR的对比分析

万兆光模块是万兆网络搭建领域中的重要组成部分,是传输万兆速率必要组件。随着网络速率和容量需求的增加,目前万兆光模块的应用量非常大。而在万兆光模块中,短距离光模块的出货量居首,本文将详细介绍3款短距离万兆光模块SFP-10G-S…