分布式事务

Posted lodor

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式事务相关的知识,希望对你有一定的参考价值。

事务的定义

       事务是一个序列操作,其中的操作要么都执行,要么都不执行,它是一个不可分割的工作单位

 

数据库本地事务

   

事务的ACID要素

 

 原子性(Atomicity)

       整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

 一致性(Consistency)

      一个事务可以封装状态改变(除非它是一个只读的)。事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。

       也就是说:如果事务是并发多个,系统也必须如同串行事务一样操作。其主要特征是保护性和不变性(Preserving an Invariant),以转账案例为例,假设有五个账户,每个账户余额是100元,那么五个账户总额是500元,如果在这个5个账户之间同时发生多个转账,无论并发多少个,比如在A与B账户之间转账5元,在C与D账户之间转账10元,在B与E之间转账15元,五个账户总额也应该还是500元,这就是保护性和不变性。

隔离性(Isolation)

      隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请求,使得在同一时间仅有一个请求用于同一数据。

持久性(Durability)

      在事务完成以后,该事务对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。

 

 

Mysql事务实现原理

      需要使用事务就必须选用支持事务的数据库引擎如InnoDB和Falcon,MyISAM不支持事务

 

   回滚日志和重做日志

       回滚日志(undo log)和重做日志(redo log);在数据库系统中,事务的原子性和持久性是由事务日志(transaction log)保证的,在实现时也就是上面提到的两种日志,前者用于对事务的影响进行撤销,后者在错误处理时对已经提交的事务进行重做,它们能保证两点:

  1. 发生错误或者需要回滚的事务能够成功回滚(原子性);

  2. 在事务提交后,数据没来得及写会磁盘就宕机时,在下次重新启动后能够成功恢复数据(持久性);

   在数据库中,这两种日志经常都是一起工作的,我们可以将它们整体看做一条事务日志,其中包含了事务的 ID、修改的行元素以及修改前后的值。

 

分布式事务

 

技术图片

      以上图为例,接口服务调用交易服务接口,交易服务保存交易到交易库,然后返回交易所得积分,接口服务再调用积分服务接口给会员增加积分,如果调用积分服务接口出现超时或异常情况,则怎么办?

 

cap理论

     分布式系统的CAP理论:理论首先把分布式系统中的三个特性进行了如下归纳:
● 一致性(C):

     在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)

● 可用性(A):

     在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)

● 分区容错性(P):

     以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。(以上述交易积分为例,比如交易保存花费100ms时间,然后调用积分接口,则交易数据与积分数据存在100ms的不一致性,针对分布式系统,肯定会存在分区的)

CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容错性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡,没有系统能同时保证这三点。

在分布式系统中,网络无法 100% 可靠,分区其实是一个必然现象。

  •   如果我们选择了 CA 而放弃了 P,那么当发生分区现象时,为了保证一致性,这个时候必须拒绝请求,但是 A 又不允许,所以分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。

  •   对于 CP 来说,放弃可用性,追求一致性和分区容错性,ZooKeeper 其实就是追求的强一致。

  •   对于 AP 来说,放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,后面的 BASE 也是根据 AP 来扩展。

 

 

 

1、强一致性

     这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大,比如对于关系型数据库,要求更新过的数据能被后续的访问都能看到

2、弱一致性

     这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不久承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态

3、最终一致性

     最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型

 

base理论

      BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的缩写。BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结, 是基于CAP定理逐步演化而来的。BASE理论面向的是大型高可用可扩展的分布式系统,和传统的事物ACID特性是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。接下来看一下BASE中的三要素:

1、基本可用

      基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性----注意,这绝不等价于系统不可用。比如:

  (1)响应时间上的损失。正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了1~2秒

  (2)系统功能上的损失:正常情况下,在一个电子商务网站上进行购物的时候,消费者几乎能够顺利完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面

2、软状态

      软状态指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时

3、最终一致性

      最终一致性强调的是所有的数据副本,在经过一段时间的同步之后,最终都能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

 

      总的来说,BASE理论面向的是大型高可用可扩展的分布式系统,和传统的事物ACID特性是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。但同时,在实际的分布式场景中,不同业务单元和组件对数据一致性的要求是不同的,因此在具体的分布式系统架构设计过程中,ACID特性和BASE理论往往又会结合在一起。

 

业界方案

 

 

一,二阶段提交2PC

     2PC,二阶段提交协议,即将事务的提交过程分为两个阶段来进行处理:准备阶段和提交阶段。事务的发起者称协调者,事务的执行者称参与者。参考XA协议

技术图片

阶段1:准备阶段
  1、协调者向所有参与者发送事务内容,询问是否可以提交事务,并等待所有参与者答复。
  2、各参与者执行事务操作,将Undo和Redo信息记入事务日志中(但不提交事务)。
  3、如参与者执行成功,给协调者反馈YES,即可以提交;如执行失败,给协调者反馈NO,即不可提交。

 

阶段2:提交阶段

     如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源)。

      注意:在2PC中,当等待超时,会进行回滚操作。

 

事务中断情况

技术图片

劣势

  1.   同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态,各个参与者在等待协调者发出提交或中断请求时,会一直阻塞,而协调者的发出时间要依赖于所有参与者的响应时间,如果协调者宕机了(单点),那么他就一直阻塞在这,而且无法达成一致(3PC引入了超时提交解决)。

  2.   单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)

  3.   数据不一致。出现分区,或者网络故障。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据不一致性的现象。

  4.   太过保守:2pc没有设计相应的容错机制,当任意一个参与者节点宕机,那么协调者超时没收到响应,就会导致整个事务回滚失败。

  5.   二阶段无法解决的问题:协调者(在第二阶段)发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。

  6.   2PC模型中,在prepare阶段需要等待所有参与子事务的反馈,因此可能造成数据库资源锁定时间过长,不适合并发高以及子事务生命周长较长的业务场景。两阶段提交这种解决方案属于牺牲了一部分可用性来换取的一致性

  

三,3PC

    3PC,即三阶段提交,是2阶段提交的改进版,其将二阶段提交协议的“准备阶段”一份为二,形成了cancommit,precommit,do commit三个阶段。

     与两阶段提交不同的是,三阶段提交有两个改动点。

     1、引入超时机制。(超时提交策略,当第三阶段参与者等待协调者超时后会提交事务,解决参与者同步阻塞问题,同时能在发生单点故障时,继续达成一致)
     2、在第一阶段和第二阶段中插入一个准备阶段。(也是为了减少同步阻塞的发生范围)

CanCommit阶段

    3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。

    1.事务询问 协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。

    2.响应反馈 参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No

 

PreCommit阶段

     协调者根据参与者的反应情况来决定是否可以记性事务的PreCommit操作。根据响应情况,有以下两种可能。

假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。

    1.发送预提交请求 协调者向参与者发送PreCommit请求,并进入Prepared阶段。

    2.事务预提交 参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。

    3.响应反馈 如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。

 

假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。

1.发送中断请求 协调者向所有参与者发送abort请求。

2.中断事务 参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。

 

doCommit阶段

    该阶段进行真正的事务提交,也可以分为以下两种情况。

执行提交

    1.发送提交请求 协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。

    2.事务提交 参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。

    3.响应反馈 事务提交完之后,向协调者发送Ack响应。

    4.完成事务 协调者接收到所有参与者的ack响应之后,完成事务。

 

中断事务 协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。

    1.发送中断请求 协调者向所有参与者发送abort请求

    2.事务回滚 参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。

    3.反馈结果 参与者完成事务回滚之后,向协调者发送ACK消息

    4.中断事务 协调者接收到参与者反馈的ACK消息之后,执行事务的中断。

注意:

    在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者rebort请求时,会在等待超时之后,会继续进行事务的提交。

    引入超时提交的依据:其实这个应该是基于概率来决定的,当进入第三阶段时,说明参与者在第二阶段已经收到了PreCommit请求,那么协调者产生PreCommit请求的前提条件是他在第二阶段开始之前,收到所有参与者的CanCommit响应都是Yes。(一旦参与者收到了PreCommit,意味他知道大家其实都同意修改了)所以,一句话概括就是,当进入第三阶段时,由于网络超时等原因,虽然参与者没有收到commit或者abort响应,但是他有理由相信:成功提交的几率很大。

优点:

       降低了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调者单点问题,阶段3中协调者出现问题时,参与者会继续提交事务。
 
缺陷:

     脑裂问题依然存在,即在参与者收到PreCommit请求后等待最终指令,如果此时协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。

 

四,补偿事务TCC

     TCC(Try-Confirm-Cancel)又称补偿事务。其核心思想是:"针对每个操作都要注册一个与其对应的确认和补偿(撤销操作)"。它分为三个操作:

  • Try阶段:主要是对业务系统做检测及资源预留。

  • Confirm阶段:确认执行业务操作。

  • Cancel阶段:取消执行业务操作。

    TCC事务的处理流程与2PC两阶段提交类似,不过2PC通常都是在跨库的DB层面,而TCC本质上就是一个应用层面的2PC,需要通过业务逻辑来实现。这种分布式事务的实现方式的优势在于,可以让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能。

    而不足之处则在于对应用的侵入性非常强,业务逻辑的每个分支都需要实现try、confirm、cancel三个操作。此外,其实现难度也比较大,需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。为了满足一致性的要求,confirm和cancel接口还必须实现幂等。

技术图片

TCC开源框架:ByteTCC,tcc-transaction,himly

 

五,本地消息表(异步确保)

      本地消息表这个方案最初是 eBay 提出的,eBay 的完整方案 https://queue.acm.org/detail.cfm?id=1394128。

      此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。

人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。 

 

对于本地消息队列来说核心是把大事务转变为小事务。还是举上面用 100 元去买一瓶水的例子。

    1. 当你扣钱的时候,你需要在你扣钱的服务器上新增加一个本地消息表,你需要把你扣钱和减去水的库存写入到本地消息表,放入同一个事务(依靠数据库本地事务保证一致性)。

    2. 这个时候有个定时任务去轮询这个本地事务表,把没有发送的消息,扔给商品库存服务器,叫它减去水的库存,到达商品服务器之后,这时得先写入这个服务器的事务表,然后进行扣减,扣减成功后,更新事务表中的状态。

    3. 商品服务器通过定时任务扫描消息表或者直接通知扣钱服务器,扣钱服务器在本地消息表进行状态更新。

    4. 针对一些异常情况,定时扫描未成功处理的消息,进行重新发送,在商品服务器接到消息之后,首先判断是否是重复的。

如果已经接收,再判断是否执行,如果执行在马上又进行通知事务;如果未执行,需要重新执行由业务保证幂等,也就是不会多扣一瓶水。

本地消息队列是 BASE 理论,是最终一致模型,适用于对一致性要求不高的情况。实现这个模型时需要注意重试的幂等

六,MQ

在 RocketMQ 中实现了分布式事务,实际上是对本地消息表的一个封装,将本地消息表移动到了 MQ 内部。

下面简单介绍一下MQ事务 

技术图片

基本流程如下:

  • 第一阶段 Prepared 消息,会拿到消息的地址。

  • 第二阶段执行本地事务。

  • 第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。消息接受者就能使用这个消息。

 

如果确认消息失败,在 RocketMQ Broker 中提供了定时扫描没有更新状态的消息。

如果有消息没有得到确认,会向消息发送者发送消息,来判断是否提交,在 RocketMQ 中是以 Listener 的形式给发送者,用来处理。 

 

如果消费超时,则需要一直重试,消息接收端需要保证幂等。如果消息消费失败,这时就需要人工进行处理,因为这个概率较低,如果为了这种小概率时间而设计这个复杂的流程反而得不偿失。

 

有一些第三方的MQ是支持事务消息的,比如上面RocketMQ,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。

七,最大努力通知型

    这是分布式事务中要求最低的一种, 也可以通过消息中间件实现, 与前面异步确保型操作不同的一点是, 在消息由MQ Server投递到消费者之后, 允许在达到最大重试次数之后正常结束事务.

适用场景

交易结果消息的通知等.

 

 

我们可以怎么来做

 

     2PC/3PC需要资源管理器(mysql, redis)支持XA规范,且整个事务的执行期间需要锁住事务资源,会降低性能。故先排除。

     TCC的模式,需要事务接口提供try,confirm,cancel三个接口,提高了编程的复杂性。需要依赖于业务方来配合提供这样的接口。推行难度大,暂时排除。

最大努力通知型,应用于异构或者服务平台当中

     可以看到ebay的经典模式中,分布式的事务,是通过本地事务+可靠消息,来达到事务的最终一致性的。但是出现了事务消息,就把本地事务的工作给涵盖在事务消息当中了。所以,接下来要基于事务消息来套我们的应用场景,看起是否满足我们对分布式事务产品的要求。

以上是关于分布式事务的主要内容,如果未能解决你的问题,请参考以下文章

86 SpringCloud解决分布式事务

MongoDB4.2分布式事务

分布式事务

分布式事务初探

分布式事务,高并发下分布式事务的解决方案

分布式事务就是由多个本地事务组合而成的事务