美文网首页
事务一致性实现

事务一致性实现

作者: lesline | 来源:发表于2020-06-28 09:47 被阅读0次

    理论基础

    分布式系统是相对集中式系统来说的。集中式系统相对简单,可以简单的理解为集中式系统将相关程序、数据集中在一台主机上。以数据库中为例,在集中式系统中,由于只有一个数据库实例,系统是可以对外提供事务的ACID特性的。但对分布式系统来说,由于数据分布在不同的主机上,系统调用时很可能出现网络延迟和故障等问题,事务的一致性是没法保障的。基于分布式系统的特点,相继提出了CAP、BASE理论。

    理论发展路径

    事务ACID特性

    事务是随数据库产生的,可以认为是将一系列数据操作看作变成一个工作单位,这些操作的顺序性是一定的,且各操作是不可分割的一个整体。数据库事务必须具备ACID特性,自1970年由Jim Gray提出以来,ACID特性一直以来作为关系型数据库的基本要求,ACID包含四部分:Atomic(原子性)、Consistency(一致性)、Isolation(隔离性)和Durability(持久性)。

    原子性:一个事务具有原子性是指这个事务中的全部操作可以认为是一个操作,事务中的操作或者全部执行,或者都不执行,不会出现部分成功的情况。如果中间某个操作发生错误,立刻返回到原始状态。例如账户转账操作:账户A转到账户B五元,要么同时满足账户A减少五元,账户B增加五元;要么转账失败,两个账户不发生变化。不会出现只有一个账户发生变化的情况。

    一致性:事务的一致性是指无论是指单事务还是多事务并发情况下,对数据的读写对外是一致的,事务之间不会看到其它事务处理一半的数据。完整性约束了a+b=10,一个事务改变了a,那么b也应该随之改变.。

    隔离性:事务的隔离性同一致性关联较大,事务的隔离性要求数据库对数据具有并发控制能力。操作同一数据的不同的事务之间是隔离的,这样才能保证其一致性。主要有四个隔离级别:读未提交、读提交、可重复读和串行化,不同的数据实现略有差异。

    持久性:事务的持久性是指存储的持久,事务提交后数据写到数据库中,即便有系统故障(如断电等),只要存储介质没有破坏数据仍然存在。

    CAP定理

    1985年,Fischer、Lynch和Patterson发表论文证明了FLP Impossibility(FLP不可能性),其大致意思是:在分布式系统中,即使只有一个进程失败,也没有任何算法能保证非失败进程达到一致性。实际上该定理宣布了在分布式系统中百分之百保证一致性是不可能的。2000年Eric Brewer提出的CAP猜想:对于分布式系统来说,系统中的一致性(Consistency)、可用性(Availability)、网络分区容忍(Partition Tolerance)三个特性不能同时满足,只能满足其中的两个。2002年,Lynch与其他人通过反证法证明了Brewer的猜想,CAP理论也逐渐被人们所接受。

    三个特性的定义如下:

    1. 一致性:所有可用节点在同一时间拥有相同的数据
    2. 可用性:对数据的读和定总能成功,即数据具备高可用性
    3. 分区容忍性:分布式系统中的一部分节点出现故障,系统仍能对外提供可靠的服务

    CAP原理下,针对不同的权衡可选择的方案有三种,如图1所示:

    图1CAP权衡方案图

    CA:牺牲分区容错性,将数据尽量放在一台机器上,这也是传统关系性数据库的实现方式,这种方案问题比较明显,一旦分布式中的某一节点出错,系统便不可用,这也就失去了分布式系统的意义。2PC相关协议也可以认为属于此类。

    CP:牺牲可用性,一旦有节点出错,可能出现系统的暂时不可用,Paxos算法的实现可以保证在半数节点有效的情况下保证可用性。

    AP:牺牲一致性,通过达到最终一致性来设计应用,代表有Gossip的实现Cassandra。

    一般来说,分区容忍性是分布式系统必须保证的,否则分布式系统的定位就形同虚设。现实中设计中,会对系统的一致性和可用性进行取舍。

    BASE理论

    BASE理论是由Dan Pritchett于2008年提出,是基于大量分布式系统实践经验而得,主要思想是通过减小对强一致性的要求得到最大可用性。是与ACID完全不同的解决方案。

    1. 基本可用(Basically Available)

    基本可用是指不保证百分之百的可用,但有可能是高可用的。当分布式系统中出现意想不到的故障时,通过损失部分可用性来保护系统。例如当系统压力过大或某些节点故障时通过排队等待或降级来暂时解决问题。

    1. 软状态(Soft State)

    软状态又叫弱状态,是对状态时效性而言的,允许状态延时同步出现中间状态,系统整体可用性不因中间状态的出现而变化。

    1. 最终一致性(Eventual Consistency)

    最终一致性是相对强一致性而说的,有时系统并不需要数据立刻达到一致性,很多情况下,系统能在可控的时间范围内达到一致性即可。

    总的来说,BASE理论面向的是需要保证高并发、高可用、可扩展的分布式系统,通过牺牲强一致性来获取高可用性;而ACID是基于单机数据库而言,可以提供强一致性。两者拥有截然不同的设计目标和理念,很多时候两者会结合使用。舍弃一定的一致性时,保证可用性时相关模式有补偿模式、可靠事件模式等。

    实现

    在分布式系统中,每个系统中的数据库各自具有ACID特性的,各系统自身是能够确定操作的成功和失败的,但当涉及到多个系统时,交易的事务一致性就难以保证。经过前人的努力,已经提出了很多解决方案,其中广泛应用的有:二阶提交协议、三阶提交协议、Sagas事务、补偿模式、TCC模式、事件驱动模式等。


    各实现方式比较.png

    两/三阶段提交

    两阶段提交协议是相对比较简单的分布式事务协议,通过增加一个协调者来管理各节点的资源,现在的很多关系型数据库就是基于此协议实现分布式事务管理的。为了统一各数据库厂商实现,OpenGroup组织定义的XA规范和X/Open DTP模型。由于二阶段提交协议固有的缺点,基于此协议提出了优化的三阶段提交协议。

    二阶段提交

    二阶段提交协议的大体思路为:在系统中增加协调者的角色,通过协调者来管理系统的各参与者,协调者向各参与者发出事务内容,询问是否允许执行提交操作,参与者给出响应结果,协调者根据所有参与者的响应结果确定是否真正提交事务,二阶段提交协调的操作序列如图所示:

    提交
    取消

    二阶段提交序列图

    1. 准备阶段:准备阶段也叫投票阶段,协调者向系统中的所有参与者发送prepare请求,注意prepare请求中是有事务内容的,各参与者会根据情况决定是否提交,如果认为可以提交,就将事务内容以Redo和Undo的形式写到事务日志中并返回Yes响应,如果认为为不可以提交,就反馈No响应。(注意:写入Redo和Undo信息并不代表事务已提交)
    2. 提交阶段:提交阶段也叫执行阶段,如果协调者从所有参与者收到的都是Yes响应,就发送执行事务的commit消息,否则发送rollback消息回滚,回滚Undo信息,在完成事务或回滚事务完成后都要向协调者返回ack消息。

    以上是二阶段提交的操作过程,可以看出,二阶段提交简单、实现也较为方便,但有几个非常大的缺点:

    1. 同步阻塞

    此协议最明显的问题就是同步阻塞,这极大影响了系统的可用性。在二阶段提交的提交阶段,如果有一个节点未反馈信息,协调者就要一直等待。这种情况只能通过设置超时时间来控制,但即使如此,超时中断事务的处理仍会导致一个节点失败,整个事务也失败。

    1. 单点故障

    由于协调者只有一个,如果协调者出现问题,那么两阶段提交便无法继续运行。更为严重的是,如果协调者是在第二阶段出现问题的话,其它参与者被第一阶段锁定的资源便无法释放,仍占用资源但不能提供事务处理能力。

    1. 数据不一致

    在协议的提交阶段中,在协调者向全部参与者发送commit请求过程中,由于网络原因(网络不稳定或网络异常)或协调者节点出现不可用的情况,会出现只有部分参与者收到了commit请求的情况,这样就导致收到请求的参与者会进行事务提交,未收到请求的参与者不知道最终执行要求,即即使引入超时提交或超时回滚机制,由于不知到提交阶段的命令,都会做出错误的判断。最终系统中的参与者出现数据不一致的场景。

    从以上说明可以看出,二阶段提交是不严密的,存在理论性缺陷的。其运行过程中会导致同步阻塞、单点问题、数据不一致问题,为了解决相关问题,提出了优化之后的三阶段提交。

    三阶段提交

    三阶段提交是为了改进二阶段提交存在的问题而得的。与两阶段提交相比主要有两个不同点。一是在系统中引入了超时机制;二是把二阶段提交中的准备阶段变成两个阶段。原先的二个阶段变成了三个阶段:询问阶段(CanCommit)、预提交阶段(PreCommit)、提交阶段(DoCommit),其提交序列如图3所示:


    确认过程
    取消流程

    图3:三阶段提交序列图

    1. 询问阶段:协调者首先发起事务询问,向所有参与者发送包含事务内容的canCommit请求,各参与者通过根据事务内容判读是否可以提交该事务,根据判断结果返回Yes响应或No响应。
    2. 预提交阶段:协调者收到询问阶段的响应时,如果都是Yes响应,就发送preCommit命令进行管事务预提交操作,也就是将事务信息写入Undo日志和Redo日志中;如果接收到的有No响应,就向向参与者发送放弃(abort)命令,事务终结。
    3. 提交阶段:预提交阶段事务preCommit成功返回后,开始对事务最终提交,首先协调者发送doCommit请求给所有参与者。收到请求的参与者会对日志做提交操作从而使信息永久保存,最后反馈Ack信息结束事务。

    与二阶段提交相比,三阶段提交中第一阶段如果有参与者不能提交会立刻中断事务,而不像二阶段协议中那样,只能通过发送回滚命令来保证,这样就大大增大了事务成功的概率。此外,三阶段提交过程中引入了超时机制:在一定时间内,如果不能收到协调者的最终结果就默认提交,这是基于系统正常的概率还是大于异常的情况。可以看出,三阶段提交解决了单点故障和阻塞的问题,但数据不致问题仍然存在,例如在预提交阶段,协调者发送的放弃(abort)命令由于网络问题没有到达参与者,在参与者超时提交后,这样收到命令执行的参与者与超时提交的参与者就出现了数据不一致的情况。

    由于以上原因,现在很多分布式数据库实现中的跨库事务能力并没有得到大量应用,相对二/三阶段提交中要么数据强一致,要么数据不一致的保证,系统更倾向于数据的最终一致性保证。

    2.2.2 Sagas模式

    Sagas模式*的处理过程为:将原本一个大事务的操作切分成一个一个的子事务,每个子事务都可以提交或回滚(通过反操作实现),这样对大事务的提交和回滚就变成了对每个子事务的提交和回滚,另外对提交事务和回滚事务要有重试机制。小事务可能存在于当前系统中的本地数据库中,也可能存在于远程系统下的数据库中。例如,在微服务架构下,会把原本一体式设计下的单个系统拆分为多个子系统,原本一个大交易事务就会是多个数据库下的小交易事务,要保证这些小事务最终全部成功或者全部失败。

    在该模式下要做以下保证:

    1. 切分出的子交易是一个子事务,满足ACID特性
    2. 要对子交易提供逆向操作,用于回滚交易
    3. 如果执行过程中,发生子交易出错,就以出错的子交易开始,反向执行子交易逆操作,最终取消交易
    4. 对逆操作失败的交易记录状态或日志,用于后续重试
    5. 重试逆操作一定次数后仍然失败,进行人工干预

    Sagas模式相对简单,只要对子交易提供逆操作即可,一些场景下实用性很好,由于事务被切分的很小,对资源的点用时间也变小,性能能够得到有效保证。不过,在实际应用中的某些场景下,一旦有子交易出错就全部取消,这样代价很大,甚至是没有必要的,完全可以根据具体的业务场景来判断,主要交易成功后,后续的子交易如果发生失败就进行正向重试。

    补偿模式

    补偿模式最大的特点就是补偿机制:对一个业务操作进行执行过程中,如果出现网络超时或系统异常等情况,立刻进行补偿操作,保证系统满足最终一致性。该模式需要注意两点:一是对于补偿失败操作需要增加重试机制来提高补偿的成功率;二是交易应该是满足幂等的,由于网络原因,补偿完全有可能是对之前成功的交易进行。

    可以看出,补偿模式相对简单,对于某些场景是不错的解决方案,但有以下两个弊端:一是补偿可能会一直失败,如果业务操作对一致性要求不高的话,这种情况还可以接受(例如发给邮件系统一直发送失败),但在核心业务中,补偿是要求必须成功的,这种情况下,补偿模式就不具可用性;二是系统没有隔离性,补偿成功前是存在中间数据的,中间数据有可能是系统不允许的。

    补偿模式的使用是有局限的,为了弥补以上缺陷,研究人员基于补偿模式提出了TCC模式。

    TCC模式

    与补偿模式不同的是,TCC模式将一个业务操作分成了两步,并且每个服务提供者提供三种服务接口,TCC模式的结构如图4所示:


    图4 TCC模式交互图

    服务提供者的三种服务接口为:

    • Try操作:服务提供者通过预留业务源来确定业务操作的可行性,实际就是为最终的业务操作进行了检查
    • Confirm操作:执行最终业务操作,执行成功后,相当于使用了Try操作预留的资源
    • Cancel操作:对之前的Try操作进行取消,也就是对预留的资源进行了归还

    业务操作的两个阶段为:

    • 预留资源阶段:服务调用者调用所有相关服务提供者的Try操作进行预留资源,反馈资源的执行结果。
    • 提交阶段:根据第一阶段的执行结果对资源进行提交或回滚,如果存在某服务提供者预留资源不成功情况,要取消之前所有的预留资源。

    举个网贷系统的例子:投资人投资时,如果投资人有未使用的红包,可以使用红包账户和投资人资金账户付款,借款人资金账户收款。假设红包账户、投资人资金账户、借款人资金账户在三个不同的系统中,传统关系统型数据库中的单事务操作已不能满足要求,我们看下系统如何通过TCC模式进行操作的:

    Try阶段:调用红包账户系统减少投资人的红包账户余额;调用资金账户系统,减少投资人账户余额(预留业务资源)。

    Cancel阶段:如果Try阶段有任一服务失败,需要将余额减掉的部分增加回去,即调用红包账户系统增加投资人的红包账户余额,调用资金账户系统,增加投资人账户余额(预留业务资源)。

    Confirm阶段:如果Try阶段所有服务都成功,则借款人账户增加账户余额,流程结束。

    注意:Confirm操作和Cancel操作是有可能出错的,一旦出错,要有配套的重试机制保证数据的最终一致,此外,这两个操作需要满足幂等性的,幂等性的实现方式不止一种,可以通过每次业务操作产生一个唯一值实现,也可以通过状态机的形式实现。

    事件驱动模式

    事件驱动模式是把业务操作转化为一个事件操作,事件的载体就是消息,通过发布事件、订阅事件、执行事件最终完成业务的操作,在执行过程中有一个最重要的角色,那就是消息代理服务。消息代理服务用作事件的中转站,接收事件发布和事件订阅,这里的消息代理服务其实就是MQ服务。事件驱动模式首先要有事件产生,接着要保证产生的事件一定会被执行。所以在实际应用中,要对事件持久化。对事件持久化最为简单的作法就是建一张信息发送表,将事件固化到数据库中。信息发送表的使用方式主要有两种:一是内部事件表实现,二是外部事件表实现。

    内部事件表实现

    内部事件表实现就是将信息发送表保存在业务服务的数据库中。除此之外,还要有相应的恢复事件机制发布事件。通过业务服务数据库的ACID特性保证信息发送表和业务数据操作的原子性。通常情况下,在处理完一个业务操作后会马上发布事件,业务操作的同时事件也保存在了信息事件表中。

    本地事件表实现的交互流程如图5所示:


    图5 事件驱动模式-本地事件表实现交互图

    内部事件表实现的流程主要有三个步骤:

    1. 持久化事件:业务服务系统记录业务数据,并将事件固化到信息发送表中,这两部操作处于同一个事务里。
    2. 发布事件:可以在第一步操作完后立刻发布式事件,也可以通过定时轮训的方式发布事件,发布成功后删除成功的事件,由于消息代理服务有可能会出现丢消息的情况,可以将删除事件操作推后进行。
    3. 执行事件:已订阅该事件的消费端获取事件并消费,消费的过程就是执行事件的过程。

    从以上处理过程,可以看出,内部事件表实现下,事件发送机制是嵌入在业务服务中的,带来的好处就是实现相对简单,缺点就是业务数据库存储了事件信息,增加了业务系统的压力。

    外部事件表实现

    由于内部事件表实现方式会给业务系统带来压力,所以提出了外部事件表实现,该实现的最大改进就是提出了一个“事件系统”,由事件系统来负责接收事件发布请求、持久化事件信息、发布事件等操作,将事件发布的任务从业务系统抽离出来。

    外部事件表实现的交互流程如图6所示:


    图6 事件驱动模式-外部事件表实现交互图

    外部事件表实现的流程步骤如下:

    1. 业务服务系统在业务事务提交前,发送事件请求给事件系统,事件系统接收到请求后持久化到自已数据库的事件发送表中,事件状态置为待发送;
    2. 业务服务系统提交事务后,发送事件确认请求给事件系统,事件系统接收到请求后开始发布事件,并将事件状态置为已发送;如果业务服务系统发生事务回滚,调用事件系统的取消事件接口进行取消;
    3. 消费端通过订阅事件,消费消息服务中的事件消息。

    需要注意的是:如果业务服务系统在第一步骤成功之后,未对事件系统中的事件进行确认或取消,事件系统可以通过业务服务系统提供的查询接口定时轮训事件的状态,让业务系统来决定是否对事件进行发布。

    外部事件表实现成功的将业务数据和事件数据隔离开来,减少发布事件的压力,降低了功能的耦合度,系统扩容也更加方便。选用内部事件表还是外表事件表实现,可以跟据系统的实际情况而定。如果在业务系统对增加发布事件功能带来的压力在可承受的范围内,完全可以采用相对简单的内部事件表实现。实际应用中,有很多优化实现,可以用缓存替代持久化的表,也可以将多个发送事件请求合并成一条发送;有时为了提供最大限度的安全,也可以在业务服务系统和事件系统中都存储事件消息。

    相关文章

      网友评论

          本文标题:事务一致性实现

          本文链接:https://www.haomeiwen.com/subject/szrifktx.html