关于一致性,你该知道的事儿(下)

关于一致性,你该知道的事儿(下)

  • 前言
  • 一、并发修改单个对象
    • 1.1 原子写操作
    • 1.2 显示加锁
    • 1.3 原子的TestAndSet
    • 1.4 版本号机制
  • 二、 多个相关对象的一致性
    • 2.1 最大努力实现
    • 2.2 2PC && TCCC
    • 2.3.基于可靠消息的一致性方案
    • 2.4.Saga事务
  • 三、 多个副本的双写一致性
    • 3.1 写入时更新
    • 3.2 读取时更新
  • 四、后记
  • 参考

前言

上篇文章讲了一些关于较为底层的一致性内容,这篇文章上升一个层次,讨论讨论应用层面的一些一致性内容。

底层给我们提供了实现数据逻辑一致性的一些保证,但是由于上层应用多种多样,需求也各不相同,有些系统宁愿吞吐量降低也不允许不一致的情况发生,而有的系统则可以接受一定程度的不一致,但是需要保证一定的高可用和高并发(因此这些系统可能采没有实现事务功能的数据库系统)。 因此,要实现整个应用的一致性,上层系统还需要注意很多东西。下面是几点引起应用不一致的几个常见场景。


一、并发修改单个对象

和数据库一样,并发操作是引起不一致的一个重要场景,但是大多数web服务场景的应用不可避免的要支持并发(一个一个串行执行的请求任务在遇到io阻塞时效率会低到哭的)。

在这里插入图片描述

比如说,很多业务场景都会遇到read-modfy-write的逻辑,即先从数据库中读取要操作的数据D,然后根据用户请求对数据进行更新,成为D’, 然后将更新后的值写入到数据库中。

很常见的一个例子就是多个人更新同一个文档, 如果多个请求同时到来,需要进行这种“read-modify-write”的操作,设计不当有可能会造成后者的更新不包括前者修改后的数据,因此导致前者的修改丢失(更新丢失)的情形。

再比如说一种情况,一个群组,每加入一个人,就需要在群资料里的总人数中进行加1操作,如果使用普通的“read-modify-write”操作,并发场景下很有可能会出现更新数目不准确状况(两次+1操作被因为冲突变成了一次)。

有一些方式可以应用到上述场景来一定程度上的应对上述问题。

1.1 原子写操作

很多数据库提供了原子更新操作,将“read-modify-write”的逻辑下沉到数据库层面,可以解决某些场景的更新丢失问题。

比如说mongo提供单个document级别的原子操作。如果我们需要更新的数据是在同一个document中,那么使用mongo可以避免更新丢失(注意,这里是针对单个document情况,对于复杂的业务逻辑需要更新多个document,mongo只保证了每个document的原子性,而不保证整个update操作的原子性)。

redis提供的大部分单个命令操作时原子性的, 有些场景可以利用redis的原子操作实现一些防止并发冲突的功能,比如说原子自增,序列号分发等。

1.2 显示加锁

有些数据库(比如说mysql)提供了对返回的结果集加锁的功能。所以,应用程序可以根据请求对查询的结果集加锁,显示锁定待更新的对象。当其他的请求尝试读取对象的时候,必须等待当前请求的执行队列完成。

Select * from page 
where name = "modify_page"
for update;     //for update 指示数据库对返回的所有数据行进行加锁

锁是一把双刃剑,虽然好用易理解,但是用得不好往往会引起效率的降低,使用宜谨慎。

1.3 原子的TestAndSet

有些数据库支持原子性的testAndSet操作,即只有当前值没有被其他人修改时才执行更新写入操作。

比如说对于两个用户同时需要更新一篇文档,只有当前页面从上次读取出后没有发生变化,才会执行当前的更新操作;
如下:

update page 
set content = "new content"
where id = 1234 and content = "old content"

1.4 版本号机制

有这样一个场景,比如说要提供一个简单的kv存储系统给客户,客户通过调用接口来操作这些kv值。 但是有一个需求,客户端需要明确知道调用是否有并发冲突,即"我调用的时候要么成功,要么失败。但是不允许有人和我一起调用成功"。

这种kv存储怎么设计呢?直接使用已有的kv组件(如redis)肯定是不行的,因为它无法防止并发冲突,虽然redis可以使用单个线程执行客户端发来的请求(串行化请求),但是它会"悄咪咪"的把客户的请求都执行了,当返回结果给客户端时,客户端也不知道自己的请求是不是穿插了其他的请求。

在这里插入图片描述
有其他的解决方案不?

可以参考版本控制的类似思想来解决这个问题。每个kv数据要保存一个版本号代表当前数据的版本 dataVersion, 每次操作完数据之后,dataVersion自增。同时当客户端请求的时候,让其带一个请求的版本号reqVersion(版本号可以是一个中心的版本分发器,也可以让app层或者redis返回时response携带,但必须是全局唯一的),只有reqVersion 和dataVersion 匹配时(比如说reqVersion=dataVersion+1),操作可以进行,否测返回客户端并发冲突。

这样当同时多个客户端请求时,如果携带相同的reqVersion来请求,只有一个可以成功,其他的将返回并发操作失败。如下图所示。

在这里插入图片描述
(上述请求假设req1先到达服务端)

这种方式从某种程度上和上面的TestAndSet有点像,都是先Test,符合某种条件时才进行Set; 不一样的一点是把判断的条件移到了客户端(让客户端请求时携带),这样让客户端能感知到并发的处理结果。其实如果不考虑判断的条件放在何处,TestAndSet和版本号机制本质上都是属于乐观锁的方式,只有在更新时才判断条件是否满足,是否有其他的线程更改了条件。

二、 多个相关对象的一致性

当现在很多的互联网应用开始划分业务为各个微服务的时候,各个微服务之间的联系就变得繁多了起来。很多时候,一个上游的请求会导致下游多个服务的调用;一个业务逻辑需要同时(这里的同时指的不是时间上的同时)修改多个对象,这就需要保证这多个数据对象的一致性。

一个很常见的业务场景就是订单支付,如下图所示。一个订单请求涉及到下游多个服务和对应数据对象的修改,。请求之后这些数据对象要保证一致性,不能订单数据为已支付,但是库存数据没修改。
在这里插入图片描述

我们要达成多个数据对象一致性(专业点叫做分布式事务),要么一起提交修改,要么都不提交修改的最终效果,这有哪些方式呢?

一般有常见的有几种方式(我用的还不多,在此简单介绍):

2.1 最大努力实现

最简单的一种方式就是重试策略。当要修改的其中某个数据对象不成功的时候(因为网络超时、或者机器宕机等原因),就重新发起请求,不断重试,直到重试成功或者达到最大的重试次数。

本质上来说,这种方式不一定能达到多个数据对象的一致性,因此只能算作最大努力实现。但对于一些一致性要求没那么高的场景,如果上层的应用设计的合理,还是可以使用这种方式的,毕竟执行失败是少数情况,很多时候retry几下就可以成功的。

2.2 2PC && TCCC

要么一起提交修改,要么都不提交修改。是不是和我们上面讨论的2PC协议有点类似?

没错,2PC也是实现这种分布式事务一种经典协议(只不过之前说它是在分布式数据库层面,现在讨论的是在业务应用层面),通过"Parepare”—>“Commit/Rollback"两个阶段来实现多个数据对象的一致性。

上文中有论述,这里就不重复了。 这里介绍一个在2PC在业务层面的一个变种,TCCC。

TCC是 Try-Confirm-Cancel 的简称,如其名字中所表述的,它的执行过程分为3个阶段:

  1. Try : 检测预留留资源, 对应于2PC的Prepare阶段
  2. Confirm: 真正的业务操作提交, 对应2PC的Commit阶段
  3. Cancel: 预留留资源释放, 对应2PC的Rollback阶段

如下图所示:
在这里插入图片描述
从某成程度上说,TCC是2PC在业务层间的套用,可以实现最终的一致性。但是2PC存在的问题,它也存在,而且这种方式对业务的侵入较强,会带来一定的开发量。

2.3.基于可靠消息的一致性方案

还有一种基于可靠消息的一致性方案,通过消息中间件自身提供的异步+持久化+重试的策略保证(当然也不是完全保证)消息一定会被消息的订阅方消费。 那么只需要保证业务操作(修改其中的某个数据对象)和消息的发送(传递修改其他对象的指令)是事务性的即可。

如下图所示为基于消息中间件的一致性方案,通过【消息发送方】执行本地事务(修改某个数据对象)和发送消息到服务端(也就是消息中间件服务)的一个类2PC过程来实现某种程度上的原子性。

首先消息发送方会发送一个“半事务消息”,然后再执行本地事务,根据本地事务执行的结果,来给消息服务端再发送一个commit或rollback的确认消息。只有服务端收到commit消息后,才会真正的发送消息给订阅方。
在这里插入图片描述

这种方式使用消息中间件的方式解耦了修改两个对象数据的过程,对性能的损耗和业务的入侵更小。现在很消息中间件都实现了事务消息的功能,可以很好的帮上层业务实现多个对象的一致性问题。

2.4.Saga事务

还有一种应用于长事务的Saga方案,通过将长事务拆分为多个本地短事务来执行,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。(有点类似于数据库中的undo操作,出有问题了就逆向操作)。

如下图所示:
在这里插入图片描述
在这里插入图片描述
从上文介绍的几种应对多个相关联对象的一致性方案来看,很多方案或多或少都能看到重试的影子(第2、3哥方案中间过程也依赖于重试),或多或少也都有点加锁的味道。 一般来说,有锁就影响并发,影响性能。 在性能、可用性和一致性方面,具体采用哪种方案还是要看具体的业务场景和需求。

三、 多个副本的双写一致性

如前所述,除了数据库系统给我们提供的多副本机制,我们还会遇到不同异构层次涉及的多个副本,具体来说是缓存系统涉及到的多个副本。在应用中常见的就是类似 本地内存-> redis缓存->数据库系统这种,我们为了提供系统的读写性能,把一部分常用的数据缓存到更快访问的介质之上,对用上层应用来说这个过程也涉及到一致性的相关问题。 虽然一般来说这种方式不会要求多么强的一致性,但是不同的操作顺序也会对一致性有不同的影响。

这里简要讨论一下【Redis缓存—>数据库】这种缓存架构,应用层不同的操作顺序带来的不同结果。

对于【Redis缓存—>数据库】这种缓存架构方式,读取方式肯定是先从Redis中读取,如果Redis不存在,再从数据库中读取。

但是写入更新就有好几种方式了,按照缓存更新的时机,分为写入时更新,或者读取时更新

这么一说下来,就有如下几种操作方式了。

3.1 写入时更新

写入时更新分为【更新缓存–>更新数据库】 和 【更新数据库–>更新缓存】两种方式。这两种方式都会有并发冲突带来的不一致现象。比如说第一种方式吧,A、B两个进程并发来一套上述的流程。
在这里插入图片描述
【更新缓存–>更新数据库】

最终发生了Redis和数据库中数据不一致的情况。

第二种方式也是一样,都会产生这种A1->B1->B2->A2(A1:表示A进程执行第1个操作)的问题。
在这里插入图片描述
【更新数据库–>更新缓存】


我们这里没考虑执行失败,或者宕机的情况,如果考虑这种情况的话,第一种方式要比第二种方式影响更大些,因为在第一种方式里,如果Redis更新成功了,但是数据库失败了,数据就不仅仅是不一致的问题,而是产生了脏数据,缓存毕竟是缓存,我们最终要是要以数据库中的数据为准。

3.2 读取时更新

【删除缓存–>更新数据库】和【更新数据库–> 删除缓存】是两种读取时更新的方式,这两方式先删除缓存,然后下一次读取的时候就可以从数据库中读取更新的数据。这两种方式相当于是把写写冲突造成的不一致转移到了读写上。比如说下面的并发场景:

在这里插入图片描述
【删除缓存–>更新数据库】

在这里插入图片描述
【更新数据库–> 删除缓存】

这两种方式也会造成最终的缓存Dc和数据库Db不一致。相比来说,第四种方式要比第三种方式发生不一致的概率更小点,因为更新缓存的速度要远远大于更新数据库,第四种方式中ClientA 把数据库都更新了,缓存也删了,ClientB还没有更新缓存,这种情况不能说没有,但是概率上要少些。

但是采用删缓存有一个缓存穿透问题需要考虑:就是删除了缓存之后要防止突然大量的并发请求到数据库中。

上述只是简单讨论了一下,实际的现实的情况要更复杂(比如说哪个过程执行失败了),也更灵活(有些场景不需要太高的一致性),需要具体问题,具体分析。


四、后记

一致性是个大问题,这两篇文章从单机和分布式的角度,从数据库和应用层面,大概梳理了一致性的相关内容。内容有点多, 因此很多内容只是简单过了个囫囵吞枣。这两篇文章主要是想通过梳理一下一致性的相关内容,来对编程过程中涉及到的一致性有个大概的认识(知道是怎么回事儿,算是属于哪个分类,该往哪个方向考虑问题),以后遇到一致性问题不至于 卖虾米不拿秤-抓瞎。

但是如果从更高的层次来看,这两篇文章的很多内容其实非常相似,抽象的看,研究的可能就是一个东西,只是在实际中被用到了不同的场景,因而有些变化。因此如果能从宏观上来看这些内容,会对一致性有更深的理解(当然,我现在还没到这个程度)。

最后总结以一张“一致性全家图”来结束这两篇关于一致性的文章。
在这里插入图片描述


参考

【1】《DDIA》
【2】 挑战大型系统的缓存设计——应对一致性问题
【3】 不就是分布式事务,这下彻底清楚了😎
【4】 一致性问题与分布式事务
【5】 TCC分布式事务,最终一致性分布式事务
【6】Seata-go: Simple Extensible Autonomous Transaction Architecture(Go version)
【7】关于一致性,你该知道的事儿(上)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ntfs文件系统的优势 NTFS文件系统的特性有哪些 ntfs和fat32有什么区别 苹果电脑怎么管理硬盘

对于数码科技宅在新购得磁盘之后,出于某种原因会在新的磁盘安装操作系统。在安装操作系统时,首先要对磁盘进行分区和格式化,而在此过程中,操作者们需要选择文件系统。文件系统也决定了之后操作的流程程度,一般文件系统…

基于opencv的车辆统计

车辆统计) 一、项目背景二、整体流程三、常用滤波器的特点四、背景减除五、形态学开运算闭运算 六、项目完整代码七、参考资料 一、项目背景 检测并识别视频中来往车辆的数量 最终效果图: 二、整体流程 加载视频图像预处理(去噪、背景减除…

picoCTF-Web Exploitation-Most Cookies

Description Alright, enough of using my own encryption. Flask session cookies should be plenty secure! server.py http://mercury.picoctf.net:53700/ Hints How secure is a flask cookie? 我们先下载server.py,分析逻辑 from flask import Flask, render…

【智能算法】鹭鹰优化算法(SBOA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2024年,Y Fu受到自然界中鹭鹰生存行为启发,提出了鹭鹰优化算法(Secretary Bird Optimization Algorithm, SBOA)。 2.算法原理 2.1算法思想…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-15.5讲 GPIO中断实验-通用中断驱动编写

前言: 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

C++:友元

友元(friend)是 C 中的一个关键字,用于建立类之间的友好关系;通过友元关系,一个类可以授权其他类或函数访问其私有成员或受保护成员,从而突破了 C 中的封装性;友元可以是类或函数,可…

css案例 tab上下滚动,左右滚动

效果图&#xff1a; 完整代码&#xff1a; <template><view class"content"><view class"content-item"><view class"content-title"><h4>美食热搜</h4><ul><li>火鸡面</li><li>糖…

Java---类和对象第一节

目录 1.面向对象初步认识 1.1什么是面向对象 1.2面向对象和面向过程的区别 2.类的定义和使用 2.1简单认识类 2.2类的定义格式 2.3类的实例化 2.4类和对象的说明 3.this关键字 3.1访问本类成员变量 3.2调用构造方法初始化成员变量 3.3this引用的特性 4.对象的构造以…

MES系统与WMS集成方法(满分100学习资料)

导语 大家好&#xff0c;我是智能仓储物流技术研习社的社长&#xff0c;老K。专注分享智能仓储物流技术、智能制造等内容。 新书《智能物流系统构成与技术实践》 完整版文件和更多学习资料&#xff0c;请球友到知识星球【智能仓储物流技术研习社】自行下载 这份文件是关于MES系…

运用分支结构与循环结构写一个猜拳小游戏

下面我们运用平常所学的知识来写一个小游戏&#xff0c;这样能够加强我们学习的趣味性&#xff0c;并且能够更加的巩固我们所学的知识。 游戏代码&#xff1a; 直接放代码&#xff1a;&#xff08;手势可以使用数字来代替&#xff0c;比如0对应石头&#xff0c;1对应剪刀&…

Sass深度解析:性能优化的秘密

首先&#xff0c;这篇文章是基于笔尖AI写作进行文章创作的&#xff0c;喜欢的宝子&#xff0c;也可以去体验下&#xff0c;解放双手&#xff0c;上班直接摸鱼~ 按照惯例&#xff0c;先介绍下这款笔尖AI写作&#xff0c;宝子也可以直接下滑跳过看正文~ 笔尖Ai写作&#xff1a;…

Linux的基础IO:软硬连接 动态库 静态库

目录 软硬连接 硬链接的作用 静态库 制作静态库 安装自定义静态库 动态库 制作动态库 协助OS查找动态库的五种方法 总结 动态库加载 软硬连接 创建硬链接指令&#xff1a;ln 目标文件 链接者 创建软链接指令&#xff1a;ln -s 目标文件 链接者 删除链接指令&…

【管理咨询宝藏96】企业数字化转型的中台战略培训方案

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏96】企业数字化转型的中台战略培训方案 【格式】PDF版本 【关键词】SRM采购、制造型企业转型、数字化转型 【核心观点】 - 数字化转型是指&…

苹果电脑MAC清理系统空间工具CleanMyMacX4.15.3中文版下载

苹果电脑以其出色的性能、优雅的设计和高效的操作系统而受到许多用户的喜爱。然而&#xff0c;随着时间的推移和使用量的增加&#xff0c;你可能会发现你的Mac开始变得缓慢和响应迟缓。这通常是因为硬盘空间被大量占用&#xff0c;影响了系统的整体性能。幸运的是&#xff0c;有…

03.Linux文件操作

1.操作系统与Linux io框架 1.1 io与操作系统 1.1.1 io概念 io 描述的是硬件设备之间的数据交互&#xff0c;分为输⼊ (input) 与输出 (output)。 输⼊&#xff1a;应⽤程序从其他设备获取数据 (read) 暂存到内存设备中&#xff1b;输出&#xff1a;应⽤程序将内存暂存的数据…

蛋糕店做配送小程序的作用是什么

蛋糕烘焙除了生日需要&#xff0c;对喜吃之人来说往往复购率较高&#xff0c;除线下实体店经营外&#xff0c;更多的商家选择线上多种方式获客转化、持续提高生意营收&#xff0c;而除了进驻第三方平台外&#xff0c;构建品牌私域自营店铺也同样重要。 运用【雨科】平台搭建蛋…

zookeeper安装集群模式

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 ZooKeeper是一个分…

【大数据】分布式数据库HBase下载安装教程

目录 1.下载安装 2.配置 2.1.启动hadoop 2.2.单机模式 2.3.伪分布式集群 1.下载安装 HBase和Hadoop之间有版本对应关系&#xff0c;之前用的hadoop是3.1.3&#xff0c;选择的HBase的版本是2.2.X。 下载地址&#xff1a; Index of /dist/hbase 配置环境变量&#xff1a…

Codigger:Vim的革新者,提升开发体验和功能性

深知Vim在编程和文本编辑领域的卓越地位&#xff0c;因此&#xff0c;在设计和开发过程中&#xff0c;Codigger始终将保留Vim的核心功能和高度定制能力作为首要任务。然而&#xff0c;Vim的复杂性和高度定制性也让很多新用户望而却步。为了降低这种使用门槛&#xff0c;Codigge…

Adobe Premiere Pro安装

一、安装包下载 链接&#xff1a;https://pan.baidu.com/s/1aYqTSQQutDguKYZE-yNHiw?pwd72l8 提取码&#xff1a;72l8 二、安装步骤 1.鼠标右击【Pr2024(64bit)】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;【解压到 Pr2024(64bit)】。 2.打开…