读书笔记

深入理解分布式共识算法部分读书笔记,书作者 释慧利

分布式与集群

  • 分布式
    分布式是指将同一个应用的不同功能模块分别部署,它们之间通过约定的通信协议进行交互。

  • 集群
    集群是指将同一个应用部署在多台服务器上,它们拥有相同的功能,所有的成员都是平等的。

在后端部署的过程中,“分布式+集群”的部署方式也很常见。
举例:
我们将原本的订单服务拆解为库存服务和支付服务,同时,为了提升并发处理能力,为库存服务和支付服务采用集群部署,并为此增加负载均衡策略。例如当创建一个订单时,订单服务根据负载均衡策略,会将请求分发给支付服务集群和库存服务集群中的任意成员来完成。

集群除了提升并发能力之外,还常用于满足容错性。我们期望当个别成员发生故障时,不会影响整体服务的可用性。

对于数据库的容错最有效的容错方式仍旧是集群。有以下的两种方案:

  1. 主备同步,在整成运行中只有主成员,当主成员发生故障时,备成员晋升成主成员。
  2. 多个成员组成集群,整体向外提供服务,当单个成员故障时,不影响整体服务的可用性。

第一种方案相对简单些,不过问题仍旧存在:

  1. 在主成员将数据同步给备成员之前,主成员宕机,那么这部分数据就会丢失。
  2. 备成员晋升为主成员期间,服务是不可用的,还有可能需要人工参与。

第二种方案较为困难,因为每个成员都是对等的服务,所以其中一个成员发生故障并不会影响其它成员。但此方案要求每个成员之间保持数据完全对齐,才能提供正确的服务。这是比较困难的,如何保证呢?复制状态机!

基于状态机的分布式系统最关键的是决定输入的顺序。因为相同的输入顺序才能使所有非错误的成员达到相同状态,这样才能保证这些成员间的数据一致。

为了使一组成员拥有相同顺序的输入,我们需要在状态机上再设计一个保证协议,即共识算法。

共识算法需要保证:从客户端看来,系统中的所有成员都选择了同一个提案。让使用者看起来无论操作哪个成员,都是在操作整个集群。

共识算法运行在集群之上,单个成员谈不上共识。

共识与集群的最大区别是成员之间数据的处理方式不同。集群通常需要引入一个额外的存储服务来保证数据的一致性; 而共识是为了解决数据的一致性而存在的,因此共识系统不需要依赖外部的存储服务。例如,ZooKeeper就不需要额外的存储服务,而是依赖自身的ZAB协议就能使各个成员之间的数据保持一致,但是业务逻辑通常需要引入一个公共的数据库服务(这也合理,业务的写入对于有共识协议的存储不一定能满足,比如大并发、写多读少时的性能问题。一个明显的例子就是Etcd本身也是提供存储服务的,但是对于写多读少的性能很差)。

共识与一致性

共识不等于一致性,共识要求的是大部分参与者对于某一个决议达成一致。而一致性则是要求所有参与成员均拥有某种相同的状态(相同的数据)。区别在于大多数和全部。共识算法可以理解为放弃了一部分成员的一致性,而增加了可用性。

拜占庭故障

Lamport证明了,在同步网络下,通过验证消息真伪并且背叛者不超过1/3的情况下才有可能达成共识。其共识的原则也是少数服从多数。多数的决策生效。
所以集群数量如果是3个,则需要引入1个成员才能达到不超过1/3这个条件。所以集群的成员需要满足成员总是为3F+1,即最多容忍F个成员背叛。

从ACID和BASE到CAP

ACID 追求一致性

  • 原子性:在一组操作中,要么全部操作执行成功,要么全部操作执行失败,不存在中间数据,也不存在部分操作执行成功,部分失败的情况。
  • 一致性:每一次事务的操作不会影响数据库当前的完整性,数据库从一个完整的状态到另一个完整的状态。这里的完整有两个含义,即,
    a. 规则约束的完整性,这里的规则更多的是相对于业务而言的,满足业务的某种规则,比如转账业务,要求转账前和转账后两个账户的整体余额是不变的。
    b. 事务执行的顺序,即,在外部看来的顺序性。符合某种发生的顺序。满足外部认为的一致顺序。
  • 隔离性:在多个事务并发执行时,保证事务与事务之间不互相干扰的程度。
  • 持久性:在食物执行成功之后,所有操作的执行结果都是永久的,哪怕服务发生故障。

Base 追求可用性

在某些场景下,无需强的一致性,更要保证系统的可用性,同时每个应用可以采用适当的方法使系统数据达到最终的一致性。

  • 基本可用(Basically Available):当分布式系统出现故障的时候,允许损失部分可用性,但不等于系统不可用。这里的损失部分可用性通常包括两个方面:响应时间的损失和在功能上的降级。
    a. 响应时间的损失:当部分节点宕机或者机房出现故障时,在请求增加响应时间的基础上,需要给用户返回正确的数据,而不是拒绝服务。
    b. 在功能上的降级:当处于流量高峰时,一部分请求可能会返回降级的数据,而不会真正请求后端核心系统,以保证后端系统的稳定性。
  • 软状态(Soft State):指允许系统中的数据存在中间状态,并认为该中间状态不会影响系统的整体可用性。即允许节点间数据同步存在延迟。允许出现事务的中间状态。例如,节点之间的投票协商和多副本之间的数据同步都需要进行网络交互,交互过程中的状态即是软状态的一种体现。
  • 最终一致性(Eventually Consistene):系统中的所有副本在经过一个时间期限后最终达到一致的状态。在BASE理论中系统允许出现事务的中间状态(软状态),但是在经过一个时间期限后,要求事务结束,所有操作要么全部成功,要么全部失败。

BASE 理论的应用

BASE 理论应用于多个为服务之间的调用。将一个大型系统拆分成多个微服务是势在必行的,但因此会降低系统的可用性。比如每个单个的微服务的可用性是99.9%,如果有多个微服务来完成一个事务,比如3个,则可用性为 99.9% x 99.9% x 99.9% 约等于 99.7%,相当于每30发生故障的时间增加了 86mins。导致行业内长时间形成困局。

而解决这一问题的方案则是BASE理论。在允许存在软状态的基础上,我们只需要保证整个事务的基本可用性和最终一致性即可,而不需要实时保证强一致性。这就需要在设计微服务的接口和调用微服务接口的客户端要做一些补偿。通常采用异步补偿机制。

举例,如果采用异步补偿机制,则需要明确那些操作属于非关键操作,如果非关键操作失败,则应允许业务流程继续执行,然后再异步补偿非关键操作,以此来降低非关键操作失败对整个事务可用性的影响。
比如在订单系统中,如果支付服务和库存服务均正常完成,但积分系统异常,此时通常不会放弃本次操作。因为在订单系统中我们认为,给用户增加积分操作不是至关重要的,因此,即使失败了,我们可以通过定时任务或者消息队列的方式,在下单完成之后再给用户增加积分。
但如果因为支付系统或者库存系统任意一个发生以外,我们需要回滚此次事务。因为这两个在订单系统中属于关键业务。

实现异步补偿机制时需要注意以下几点

  1. 每个补偿操作都应设置重试机制,且需要实现幂等。(因为有重试,所以需要幂等,来保证接口即使执行了多次也不会多加或多减)
  2. 整个事务应由工作流驱动,记录每个分支数据的处理结果。(这就有点像状态机)
  3. 对于事务状态,通常需要提供特殊的接口进行查询。(给上层提供一个视角?)
  4. 对于所有分支事务,需要ui wu提供回滚事务的接口。(保证可以回滚整个事务)

CAP - 分布式系统的PH试纸

CAP 猜想是Brewer在Towards Robust Distributed Systems演讲中提出的猜想,Brewer对ACID和BASE做了进一步对比分析,在ACID中的C和BASE中的A的基础上扩展出了一个新维度,及分区容错性(Partitions Tolerance,简写P),以此做出CAP猜想。

  • 一致性:所有节点的数据实时保持一致。
  • 可用性:任何情况都能够处理客户端的每个请求。
  • 分区容错性:发生分区时,系统应该持续提供服务。

CAP猜想的核心思想是:任何分布式系统,其一致性、可用性、分区容错性最多只能满足其中两个。
依次,任何分布式系统都可以归为3类架构:CP、AP、CA。它们的特性总结如下:

  • CP(锁定事务资源):所有节点的数据需要实时保持一致,意味着当处理写请求的时候,需要锁定各个分支事务的资源。当发生分区的时候,各节点之间不能联通,请求完成所需的时间依赖于分区恢复的时间。在此期间为了确保返回客户端数据的准确性且不破坏一致性,可能会因为无法响应最新数据而拒绝响应,即放弃A。
  • AP(尽最大能力提供服务):要求每个节点拥有集群的所有能力或数据,能自己处理客户端的每个请求,这样才能实现尽最大能力容错的要求。当发生分区时,可以通过缓存或者本地数据来处理客户端的请求,以达到可用性,但是各个节点数据将会出现不一致的情况,即放弃C。
  • CA(本地一致性):在不考虑P的情况下,意味着集群正常运行,一致性和可用性是可以同时满足的(一个正确的系统就应该符合这个要求)。但是网络分区不可避免,永远可能发生,因此选择CA的系统通常会在发生分区时,让各个子分区满足CA。

CAP定理描述

2002年,Lynch在发表的Brewer's Conjecture and the Fasibility of Consistent, Avaliable, Partition-Tolerant WebServices 论文中证明了CAP猜想,从此CAP猜想上升为定理。同时其对CAP进行了更具体的第二次描述:

  • 一致性:被形容为原子性和串行化,每个读/写操作都像是一个原子操作,并且像是全局排好序一样,后面的读操作一定能读到前面的写操作。这意味着在分布式系统中执行一个操作就像在单节点上执行一样。
  • 可用性:系统中未发生故障的节点接受的每个请求都必须产生响应,即,每个请求最终都必须终止,但是不要求终止之前所需的时间长短。
  • 分区容错性:允许在集群中丢失任意数量的消息(请求),因为当发生分区时,A分区发送到B分区的消息可能会全部丢失。

一致性必须保证每个请求都是原子的,即使由于分区导致任意的消息不能被传递。
可用性必须保证每个请求都响应,即使由于分区导致任意的消息丢失。
分区容错性需要保证,即使分区中只存在一个节点,也要返回有效的原子响应。

为什么C、A、P三者不可兼得

在分布式系统中,各个组件必然部署在不同的节点上,因为网络本身是不可靠的,所以必然会出现子网络,也一定存在延迟和数据丢失的情况,即网络分区是必然存在的。因此P(分区容错性)是分布式系统必须要面对和解决的问题(因为你无法保证系统运行的环境永远不发生网络分区)。

C、A、P三者不可兼得,变成如何在C(一致性)和A(可用性)二者之间进行抉择。
比如,在分布式环境中,为了确保系统的可用性,通常将数据复制到多个备份节点上,而复制的过程需要通过网络交互。当发生网络分区时候,将面临如下两个选择:

  • 如果坚持保持节点之间的数据一致(选择C),则需要等到网络分区恢复后,将数据复制完成才可以对外部提供服务。在这期间发生网络分区将不能对外提供服务,因为它不能保证此时的数据的一致性。
  • 如果选择可用性(选择A),则当发生网络分区时,依然需要对外部提供服务。但是由于网络分区的原因,同步不了最新的数据,因此返回的数据可能不是最新的(与其它节点的数据不一致)数据。

由此可见,当发生网络分区时,我们只能在C和A中选择其一。

CAP的应用

CAP的指导作用是说,在架构设计中,不要浪费经历去设计一个满足一致性、可用性和分区容错性三者的完美系统,需要根据自己的业务场景进行取舍。值得注意的是CAP中的一致性和可用性表现为强一致性和完全(100%)可用性。

在实际生产中,更加需要的是二者之间的调节剂,即对一致性和可用性的强度进行调节,使得系统在用户允许和业务要求的情况下,让系统的一致性和可用性达到最佳状态,而不是非黑即白。在实际生产中,也不需要100%的强一致性和100%完全的可用性。

例如,在一个跨区域电商平台中,任何数据的修改都需要跨区域地通知其它节点完成数据同步。

在商家对商品详情进行修改的场景中,并不要求在所有的节点上该商品的信息都保持实时同步,而只要在经过一个期间后所有区域的用户都能看到修改后的内容,而这个期间是用户允许的情况即可。即,在本次的修改请求中,并不依赖所有节点成功响应,可用性便会有相应的提升。

在用户购买商品的场景中,对库存的修改相对来说一致性要求更高一些,即,期望的是当最后一个商品被购买后,所有区域的用户都能看到该商品已经售罄的状态。为了实现库存的实时同步,在用户购买商品的请求中,需要依赖所有节点成功响应,则可用性便会有所降低。

在实际开发中,追求的应该是用户感知的可用性需要在一致性和可用性之间权衡。

常见分布式共识算法原理与实战

如果要设计一个强一致性的CP架构系统,该如何实现?针对这一需求,逐渐衍生出了 两阶段提交和三阶段提交协议。

2PC、3PC - 分布式事务的解决方案

2PC

两阶段提交是一个基于协调者的强一致性原子提交协议。在分布式事务中,由于各个分支事务只能知道自己执行的结果是成功还是失败,并不清楚其它分支事务的执行结果,因此需要设计一个协调者的身份,各个分支事务向协调者上报执行状态,再由协调者根据各个分支事务的执行结果决定全局事务的提交或回滚。

  • 协调者:可以由事务的发起者充当,也可以由第三方组件充当,如Seata的TC角色。协调者维护全局事务和分支事务的状态,驱动全局事务和分支事务提交或回滚。
  • 参与者:通常由各个资源管理者充当,负责执行各个分支事务提交或回滚,并向协调者汇报执行状态。

两阶段提交协议,由准备阶段和提交/回滚阶段组成。第一个阶段用于各个分支事务资源锁定(文中这里类似于OCC的处理方式,其实第一阶段也算是给协调者承诺的阶段,承诺其已经准备好提交),第二阶段用于全局事务的提交或回滚。

  • 阶段一:准备阶段,又称为投票阶段(Vote Request),由协调者向参与者发送请求,以询问当前事务能否处理成功。详细的流程如下:
    1. 开启全局事务。当协调者收到客户端的请求后,它分别将各个分支事务需要处理的内容,通过Prepare请求发送给所有参与者,然后询问各个参与者能否正常处理自己的分支事务,并等待各个参与者进行响应。
    2. 处理分支事务,当参与者收到Prepare请求后,便锁定事务资源,然后尝试执行各自的分支事务,记录 Undo和Redo信息,但并不提交分支事务。
    3. 汇报分支事务状态,参与者根据第2步执行的结果,向协调者汇报各自的分支事务状态。例如,Yes表示自己的分支事务可以正常提交,No表示自己的分支事务不能提交。
  • 阶段二:提交/回滚阶段,协调者在超过约定的时间内没有收到全部参与者的响应时,或者在收到所有的参与者的响应中存在部分分支事务的返回为No,便会发起全局事务回滚。如果收到的所有分支事务的状态都为Yes,发起全局事务提交,同时向客户端返回全局事务结果,结束本次全局事务提交。详细的流程如下:
    1. 驱动全局事务提交/回滚。如果协调者在超过约定时间内收到第一阶段所有参与者的响应,且所有分支事务的状态为Yes,则向所有参与者发起Commit请求。否则向所有参与者发起Rollback请求。
    2. 提交/回滚分支事务。参与者根据第一阶段记录的Redo和Undo信息对各自的分支事务进行提交或回滚,并释放第一阶段锁定的事务资源。
    3. 汇报分支事务状态。参与者在处理提交/回滚分支事务后,向协调者反馈自己负责的分支事务状态。
    4. 关闭全局事务。当协调者收到所有参与者的反馈后,向客户端返回结果并关闭本次事务。

故障恢复

协调者发生故障

协调者需要先确定当前最后一个事务实际执行的状态,可以直接从磁盘上读取到。然后继续处理。
协调者需要严格遵守全部Commit/全部Abort的原则即可。这里不再详细描述。

部分参与者发生故障

参与者重启了之后通过读磁盘知道自己上次挂的时候事务执行到哪里。如果已经Commit/Abort了,就可以不用处理了。如果只是Prepared了,则需要询问协调者。如果压根就没有未完成的事务记录,那么就当啥都没发生过。

协调者和部分参与者均发生故障

需要先恢复协调者,让协调者继续推进事务。

二阶段提交优缺点

  • 优点:容易理解、原理简单。
  • 缺点:同步阻塞、数据不一致、单点问题和脑裂
  1. 同步阻塞:二阶段提交协议的阻塞主要体现在参与者需要协调者的指令才能执行第二阶段的操作。当协调者发生故障时,参与者在第一阶段锁定的资源将一直无法释放。
  2. 数据不一致:在第二阶段,如果因为网络异常而导致一部分参与者收到Commit请求,而另一部分参与者没有收到Commit请求,那么结果是一部分参与者提交了事务,而另一部分参与者无法提交。(这里不一定会有数据不一致性,因为在一阶段已经锁定了资源,则后续的访问无法获取到锁就不能访问,这是其一。再者,后续的访问一般会携带一个较大的版本号来访问,同样的,有MVCC机制保证不会返回一个旧的数据,而是会阻塞,因为有事务还在进行中,直到该参与者将事务提交)。(实际上在分布式场景下的原子性是一种延迟原子性,即在一段时间后最终保证所有的事务均是提交或者回滚即保证了原子性。只要不存在某些节点提交,某些节点回滚的情况即可)
  3. 单点问题和脑裂:
    • 单点问题:二阶段提交协议过于依赖协调者,当协调者发生故障时,整个集群将不可用。(这不至于吧,协调者可以是事务的发起者)
    • 脑裂:当集群中出现多个协调者时,将不能保证二阶段提交的正确性。

空回滚和防悬挂

空回滚意味着,在第一阶段,当发生网络丢包时,协调者发送Prepare请求没有送达参与者。根据协调者的超时规则,协调者在等待参与者超时后将发送Rollback给所有参与者。如果此时网络恢复,参与者将会收到Rollback请求,而在此之前参与者并没有处理过第一阶段的任何工作。这导致无东西回滚。直接忽略就行。一个简单的避免方式就是,每个事务均生成一个唯一的XID,协调者将XID携带在消息中,发送给参与者,参与者需要持久化记录该XID,并将自己事务的状态也记录下来。就能判断是否处理过某个XID的事务。

另一种情况,网络延迟导致 Rollback 和 Prepare 消息都被协调者发送出去了。这样就存在 Rollback 和 Prepare 先后到达某个协调者上。有可能存在两个消息被不同的线程并行处理,如果Rollback先执行了,Prepare后执行了,会导致Prepare执行之后事务会一直残留,因为不会再有Rollback来回滚它了。
这里需要将其串行化执行,比如,同一个XID的事务加锁强制其先后处理。上面的场景,Rollback先执行的时候,Prepare是不能执行的,等到Rollback执行完成,并记录日志了,就可以直接忽略该XID的Prepare请求。

3PC

三阶段提交协议的核心是将二阶段提交协议中的第一阶段一分为二,形成由询问阶段、预提交阶段和提交阶段组成的事务提交协议。
事务提交协议由此变为:询问 -> 锁定事务资源 -> 提交事务。将锁定事务资源滞后可以减少事务资源锁定的时间长短(范围)。比如,如果集群中存在个别不具备处理事务能力的参与者,那么可以提前中断事务,而不是像二阶段提交协议一样,从一开始就锁定所有参与者的事务资源。
另外,为了解决同步阻塞问题,三阶段提交协议增加了超时机制,解决了当协调者宕机时,参与者无法释放事务资源的问题。这里增加的超时机制主要体现在参与者等待协调者的请求超时后,将会执行默认的提交/回滚指令。(这里相当于协调者自协商来推进事务)

  • 阶段一:CanCommit 询问阶段
    协调者在询问阶段将各个分支事务内容通过CanCommit请求分别发送给所有参与者,参与者收到CanCommit请求后,根据自身逻辑判断能否执行本次事务,然后将结果汇报给协调者。具体的执行逻辑如下:

    1. 开启全局事务。协调者收到客户端开启事务的请求后,会向所有参与者发送一个包含事务内容的CanCommit请求,询问是否可以执行本次事务。
    2. 健康自检。参与者收到CanCommit请求后,需要完成一下两项工作,但并不需要锁定事务资源。
      a. 检查自身健康。例如,检查自身与协调者之间的连接,以及自身处理执行事务的能力。
      b. 判断能否执行本次事务,判断是否与其它事务冲突。(看起来主要是为了检测是否有与其它事务存在冲突)
    3. 汇报分支事务状态。参与者根据第2步执行结果,向协调者汇报各自的分支事务状态。Yes表示自己的分支事务可以正常提交,No表示自己的分支事务不能提交。
  • 阶段二:PreCommit 预提交阶段
    在询问阶段,如果协调者收到了参与者汇报的No响应或者等待参与者汇报超时,则协调者会中断全局事务,向所有参与者发送Abort请求,同样,参与者在这一阶段等待协调者的请求超时后也会自行中断分支事务。由于在询问阶段,参与者没有锁定任何事务资源,因此对预提交阶段的中断事务操作,参与者只需要更新分支事务的状态即可。
    如果协调者收到所有参与者的Yes响应,则会发起PreCommit请求,进入预提交阶段。具体的执行逻辑如下:

    1. 驱动预提交。协调者从询问状态变为预提交状态,并向所有参与者发送PreCommit请求,同时等待参与者的响应。
    2. 处理分支事务。参与者收到PreCommit请求后,更新自己的状态为预提交状态,然后锁定事务资源,尝试执行各自的分支事务,记录Undo和Redo信息,但并不提交分支事务。
    3. 汇报分支事务状态。参与者处理完分支事务后,需要向协调者反馈自己负责的分支事务状态。
  • 阶段三:DoCommit 提交阶段
    同样,根据预提交阶段的结果,提交阶段也存在两种情况。如果协调者收到预提交阶段的反馈存在No的响应,或者等待参与者的反馈超时,则会中断全局事务。中断全局事务的过程如下:

    1. 发送中断请求。由协调者向所有参与者发送Abort请求,通知所有参与者中断分支事务。
    2. 分支事务回滚。参与者根据二阶段记录的Undo信息来执行回滚操作,并释放占用的事务资源。
    3. 反馈回滚结果。参与者向协调者反馈回滚结果。
    4. 关闭全局事务。协调者关闭全局事务,并向客户端返回失败的消息。
      如果在预提交阶段所有参与者反馈的状态为Yes,则执行提交事务请求。经过前两个阶段的缓冲,参与者会认为全局事务是值得提交的。在提交阶段,参与者等待协调者的请求超时后会自行提交分支事务。
      提交全局事务是由协调者发送Commit请求给所有参与者,参与者收到Commit请求后,提交分支事务并释放事务资源。

    提交全局事务的过程如下:

    1. 发送提交请求。协调者从预提交状态变为提交状态,然后向所有的参与者节点发送Commit请求。
    2. 分支事务提交。参与者收到Commit请求后,更新自己的状态为“提交状态”,并提交自己的分支事务,同时释放占用的事务资源。
    3. 反馈提交结果,参与者向协调者反馈提交结果。
    4. 关闭全局事务。协调者关闭全局事务,并向客户端返回成功消息。

故障恢复

由于三阶段提交协议的超时和自动提交/中断机制,大部分情况下都能保证协议的正确性。同样以协调者发生故障和部分参与者发生故障的情况来描述。对于协调者和参与者同时发生故障的情况,则优先恢复协调者。
相比于二阶段提交协议,三阶段提交协议在故障恢复时更加友好,多数情况下参与者可以根据自身的数据来执行对应的指令,而不用向协调者询问。

协调者故障

引入超时机制将会影响协议的正确性。根据协调者发生故障的时刻,协议将存在以下几种情况:

  1. 如果协调者发生故障之前,全局事务正处于第二阶段,那么所有参与者在等待协调者请求超时后都会默认执行Abort指令。
  2. 如果在协调者发生故障之前,全局事务正处于第二阶段,则存在两种情况:
    • 如果所有参与者都完成了第二阶段的工作,那么根据超时机制,所有参与者最终都会提交分支事务。
    • 如果一部分参与者完成了第二阶段的工作,另一部分参与者未完成第二阶段的工作,那么根据超时机制,一部分参与者将提交分支事务,另一部分参与者将中断分支事务,造成数据不一致。(造成了一部分分支事务先行提交了,另一部分则执行了Abort,这是很严重的问题,直接违背了原子性,需要避免!)
  3. 如果在协调者发生故障之前,全局事务正处于第三阶段,那么所有参与者在等待协调者的请求超时后都会自行提交分支事务。

在上述场景中,只有一种情况会导致算法不安全,即一部分参与者完成了第二阶段工作 ,另一部分参与者未完成第二阶段工作。

要解决这个问题,需要引入心跳机制,让参与者能够感知到协调者的运行状态。当参与者检测到协调者发生异常,且自己处于第二阶段时,此时应临时停用超时机制,等待新的协调者的Commit/Abort请求。这个约束需要满足心跳超时要远小于等待协调者的超时。这样才能在等待协调者响应超时之前,觉察到协调者出现了异常,从而关闭等待协调者响应的定时器。(注:这里还是存在隐患,因为涉及到定时器的问题,避免不了的会有线程调度的干预,仍旧会导致等待协调者响应定时器超时先被处理。此时应该主动探测协调者的状态,再或者通过询问其它参与者状态,是否已经进入了第二阶段。经过几轮的询问,每个参与者都应该能够清楚所有其它参与者的事务状态,是否都已经进入了第二阶段。如果都已经进入了第二阶段,则继续推进执行完成,如果存在有的参与者并没有进入第二阶段,则直接全部回滚掉。此时的决策可以让所有参与者中“最小”的那个来决策。
这里的 最小 表示一种逻辑上的排序,能够当一个临时的事务推进的主导者。这个思想和Percolator的Prewrite中First Key所在的Region作为新的协调者是一个道理。

新上任的协调者需要先询问所有参与者最后一条全局事务的处理情况。在上面的例子中,新上任的协调者将会收到一部分参与者执行了Abort操作,一部分参与者仍处于第二阶段完成的状态,因此新上任的协调者会发送Abort请求给所有参与者,这样协议就能正常恢复了。

另外,在其他场景中,参与者虽然能自己保证协议正确性,但新上任的协调者仍需要询问所有参与者执行的情况, 以维护全局事务的状态。

部分参与者故障

参与者发生故障的情况比较简单,恢复过来的参与者只需要向协调者获取全局事务的状态,然会执行对应的指令即可。可能发生的情景如下:

  1. 如果在参与者发生故障之前,全局事务正处在第一阶段,那么无论该参与者是否已经完成第一阶段工作,协调者将会在第一阶段等待超时或者第二阶段等待超时后,中断全局事务,发生故障的参与者恢复后,可自行中断分支事务。
  2. 如果在参与者发生故障前,全局事务正处于第二阶段,同样存在两种情况:
    • 参与者未完成第二阶段的工作,协调者将会在第二阶段等待超时之后,中断全局事务,发生故障的参与者恢复后,自行中断事务即可,因为第二阶段自己并没有执行,则全局事务一定会中断的,所以参与者可以安心自行中断事务。
    • 参与者完成了第二阶段事务,其有可能向协调者反馈了Yes,也可能没有反馈,根据约定,全局事务有可能会提交,也有可能会中断。参与者恢复过来之后应该向协调者询问其情况,然后执行对应的指令。(此时参与者必须询问协调者,因为自己不知协调者做了什么决策。)
  3. 如果在参与者发生故障之前,全局事务正处于第三阶段,那么无论该参与者是否已经完成第三阶段的工作,在参与者故障恢复之后,都应该向协调者询问全局事务的状态,而不能自行提交分支事务,因为其它参与者在第二阶段可能会执行失败,导致全局事务中断。

三阶段提交协议的优缺点

三阶段提交协议就是为了解决二阶段提交协议同步阻塞的问题而诞生的。其有如下几个优点:

  1. 通过增加超时机制和自动提交/中断功能,减少了参与者的阻塞范围。在二阶段提交协议中,参与者必须要等到协调者的请求后才能执行第二阶段,而在三阶段提交协议中,参与者在等待协调者的请求超时后,可以根据自己的处境来执行响应的操作,离开阻塞状态。
  2. 增加的CanCommit阶段降低了事务资源锁定的范围(这里的范围应该是持有锁的时间长短),不会像二阶段提交协议一样从一开始就锁定所有的事务资源,三阶段提交协议在排斥个别不具备处理事务能力的参与者之后,再进入第二阶段的锁定事务资源。(但是这不能保证第二阶段执行的时候,锁定资源被阻塞的情况,只是提前先预判了。不过它能把不具有事务处理能力的的参与者剔除掉,虽然不知道这个是什么意思。。。)

三阶段提交协议虽然解决了以上问题,但同时也引入了新的麻烦:

  1. 多增加一个阶段等于增加了复杂度,同时到增加的RPC交互也会降低整个协议的协商效率。
  2. 在某些情况下,必然会造成数据的不正确。在三阶段提交中,由于丢包或者协调者发成异常,导致一部分参与者收到了PreCommit请求,另一部分参与者没有收到PreCommit请求。因为超时机制,没收到PreCommit请求的参与者会Abort操作,而收到PreCommit请求额度参与者在等待第三阶段Commit请求超时后,会自动提交分支事务,从而造成了整个协议的不正确。(所以,这里需要在超时Commit之前,相互询问其他参与者的状态,这就需要将事务的所有参与者信息均要同步给每个参与者。或者每个参与者找指定的一个参与者获取。)

Seata

Seata是一款解决为服务架构的分布式事务的框架。它提供了多种事务模式,如XA模式、TCC(Try-Confirm-Cancel,类似于三阶段)模式、Saga模式以及AT模式。

Seata中主要存在3中角色:

  • 事务管理者(TM):负责驱动事务协调者(TC)执行全局事务的开启、提交和回滚操作,其通常由发起全局事务的业务服务充当。
  • 资源管理者(RM):按照事务协调者(TC)的指令,执行分支事务的提交或者回滚操作,并向事务协调者汇报分支事务的状态。其一般由全局事务中涉及的所有下游服务充当。
  • 事务协调者(TC):维护全局事务的状态,执行全局事务的提交和回滚操作,驱动资源管理者(RM)执行分支事务的提交和回滚操作,有Seata-Server充当事务的协调者。

AT 模式

AT 模式是基于两阶段提交协议演化而来的。但AT模式缩小了分支事务资源的锁定范围(锁定时间)。

AT模式与两阶段提交的区别
最大的区别在于AT模式缩小了分支事务资源锁定的范围。由于AT模式第一阶段就提交了分支事务,因此在第二阶段也需要有相应的变化,具体总结如下:

  1. 第一阶段提交分支事务,释放本地锁和事务资源,以提高系统的工作效率。
  2. 增加了Undo日志,用于第二阶段的回滚。
  3. 第二阶段提交异步化,进一步提高了正常工作的协商效率。
  4. 第二阶段回滚操作需要根据Undo日志,对第一阶段已提交的事务进行反向补偿。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,874评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,102评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,676评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,911评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,937评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,935评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,860评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,660评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,113评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,363评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,506评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,238评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,861评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,486评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,674评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,513评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,426评论 2 352

推荐阅读更多精彩内容