分布式事务入门

分布式事务入门

什么是分布式事务

要理解分布式事务,先看看什么是事务?

事务是指是程序中一系列严密的逻辑操作,而且所有操作必须全部成功完成,否则在每个操作中所作的所有更改都会被撤消。可以通俗理解为 (ALL OR NOTHING):就是把多件事情当做一件事情来处理,好比大家同在一条船上,要活一起活,要完一起完 。

而随着微服务、SOA等服务架构大规模普及,一个单体应用往往被拆分成N个不同的微服务。这时候本来是本地数据库的一致性就演变成为了不同服务之间不同数据库之间的一致性。又或者是相同服务之前的不同数据库(分库)之间的一致性。

不同服务之间不同数据库产生的分布式事务

必须要保证积分、优惠券、余额同时扣减成功。

相同服务之间不同数据库产生的分布式事务

扣款和增款必须同时成功

分布式事务就是要保证上述两种不同数据库之间的一致性而产生的。分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。

分布式系统的一致性问题

下面举几个例子说一下常见的系统存在的哪些不一致的问题。

下订单和扣库存

电商系统中有一个经典的案例,即下订单和扣库存如何保持一致。如果先下订单,,扣库存失败,那么就会导致超卖;如果下订单不成功,扣库存成功,就会导致少卖。

同步调用超时

服务化的系统间调用常常因为网络问题导致系统间调用超时,即使是网络状况很好的机房在亿次流量的基数下,同步调用超时也是家常便饭。系统A同步调用系统B超时,系统A可以明确导致超市反馈,但是无法确定系统B是否已经完成了预设的功能。于是,系统A不知道应该继续做什么,如何反馈给使用方。

异步回调超时

此案例和上一个同步超时的案例类似,不过这是一个受理模式的场景,使用了异步回调返回处理结果,系统A同步调用系统B发起指令,系统B采用受理模式,受理后则返回成功信息,然后系统B处理后异步通知系统A处理结果。在这个过程中,如果系统A由于某种原因迟迟没有收到回调结果,那么这两个系统间的状态就不一致,互相认知的状态不同会导致系统间发生错误,在严重情况下会影响核心链路上的交易的状态准确性,甚至会导致资金损失。

掉单

在分布式系统中,两个系统写作处理一个流程,分别为对方的上下游,如果一个系统中存在一个请求(通常指订单),另一个系统不存在,则会导致掉单,掉单的后果很严重,有时也会导致资金损失。

系统间状态不一致

此案例与上面掉单的案例类似,不同的是两个系统间都存在请求,但请求的状态不一致。

缓存和数据库不一致

缓存和数据库之间的数据如何保持一致性?是要保持强一致性还是弱一致性。

本地缓存节点间不一致

一个服务池上的多个节点为了满足较高的性能需求,需要使用本地缓存,这样每个节点都会有一份缓存数据的复制,如果这些数据是静态的,不变的,就永远不会有问题,但是如果这些数据时半静态的或者经常被更新的,则被更新时各个节点的更新是有先后顺序的,在更新的瞬间,在某个时间窗内各个节点的数据时不一致的,如果这些数据视为某个开关服务的,则想象一下重复的请求进入了不同的节点,一个请求进入了开关打开的逻辑,同时另一个进入了开关关闭的逻辑,会导致请求被处理两次,最坏的情况是导致资金损失。

缓存数据结构不一致

某系统需要在缓存中暂存某种类型的数据,该数据由多个数据元素组成,其中,某个数据元素需要从数据库或者服务中获取,如果一部分数据元素获取失败,则由于程序处理不正确,仍然将不完成的数据存入缓存中,在缓存使用者使用时很有可能因为数据的不完全而抛出异常,例如NullPointerException等,然后可能因为没有合理处理异常而导致程序出错。

分布式事务的理论基础

CAP

CAP定理

CAP定理,又被称为布鲁尔定理,是分布式计算领域公认的一个定理。

  • C:Consistency,一致性。在分布式系统中的所有数据备份,在同一时刻,具有同样的值,所有节点在同一时刻读取的数据都是最新的数据副本。
  • A:Availability,可用性,好的响应性能。完全的可用性指的是在任何故障模型下,服务都会在有限的时间内处理完成并进行响应
  • P:Partition tolerance,分区容忍性。尽管网络上有部分消息丢失,但系统仍然可以继续工作。

CAP原理证明,任何分布式系统只可同时满足以上两点,另外一个必须被牺牲。分布式的服务化系统都需要满足分区容忍性,那么只能选择CP(一致性+分区容忍性)或者AP(可用性+分区容忍性)这两个,而不能选择AC(可用性+一致性)。

如果在网络上有消息丢失,就出现了网络分区,复制操作可能会被延后,这时候,如果我们选择等待复制完成再返回,则可能导致有限的时间内无法响应,失去了可用性;二如果选择不等待复制完成,在主分片写完后立即返回,则具备了可用性,但失去了一致性。

BASE

BASE思想解决了CAP提出的分布式系统的一致性和可用性不可兼得的问题。BASE思想和ACID原理截然不同,它满足CAP原理,通过牺牲强一致性获得可用性,一般应用于服务化系统的应用层或者大数据处理系统中,通过达到最终一致性来尽量满足业务的绝大多数需求。

BASE模型包含如下三个元素:

  • BA:Basically Available,基本可用
  • S:Soft State,软状态,状态可以在一段时间内不同步
  • E:Eventually Consistent,最终一致,在一定的时间窗口内,最终数据达成一致即可。

分布式一致性协议

国际开放标准组织Open Group定义了DTS(分布式事务处理模型),模型中包含4种角色:应用程序、事务管理器、资源管理器和通信资源管理器。事务管理器是统管全局的管理者,资源管理器和通信资源管理器是事务的参与者。

下面的两阶段提交协议、三阶段提交协议以及TCC协议,都是根据DTS这一思想演变而来的。

两阶段提交协议(强一致性)

JEE的XA协议就是根据两阶段提交来保证事务的完整性,并实现分布式服务化的强一致性。

两阶段提交协议把分布式事务分为两个阶段,一个是准备阶段,另一个是提交阶段。准备阶段和提交阶段都是由事务管理器发起的。

两阶段提交协议的流程如下所述:

  • 准备阶段:事务管理器向资源管理器发起指令,资源管理器评估自己的状态,如果资源管理器评估指令可以完成,则会写redo 或者 undo日志,然后锁定资源,执行操作,但是并不提交
  • 提交阶段:如果每个资源管理器明确返回准备成功,也就是预留资源和执行操作成功,则事务管理器向资源管理器发起提交指令,资源管理器提交资源变更的事务,释放锁定的资源;如果任何一个资源管理器明确返回准备失败,也就是预留资源或者执行操作失败,则事务管理器向资源管理器发起终止之灵,资源管理器取消已经变更的事务,执行undo日志,释放锁定的资源。

两阶段事务提交成功

两阶段事务提交成功

两阶段事务提交失败,回滚

两阶段提交协议在准备阶段锁定资源,这时一个重量级的操作,能保证强一致性,但是实现起来复杂、成本较高、不够灵活,更重要的是它还有以下的致命问题:

  • 阻塞:任何一次指令都必须受到明确的响应,才会继续下一步,否则处于阻塞状态,占用的资源会被一直锁定,不会被释放。
  • 单点故障:如果事务管理器宕机,资源管理器没有事务管理器指挥,就会一直阻塞,尽管可以通过选择新的事务管理器替代原有的事务管理器,但是如果事务管理器在发送一个提交指令后宕机,并且提交指令仅仅被一个资源管理器接受,并且资源管理器接受后也宕机,那么新上任的资源管理器无法处理这种情况
  • 脑裂:事务管理器发送提交指令,有的资源管理器受到并执行了事务,有的没有收到就没有执行事务,多个参与者之间状态就不一致

三阶段提交协议

三阶段提交协议是两阶段提交协议的改进版本。它通过超时机制解决了阻塞的问题,并把两阶段增加为以下三个阶段:

  • 询问阶段:事务管理器询问资源管理器是否可以完成指令,资源管理器只需要回答是或者不是,而不需要做真正的操作,这个阶段超时会导致终止。
  • 准备阶段:如果在询问阶段所有资源管理器都返回可以执行操作,则事务管理器向资源管理器发送预执行请求,然后资源管理器写redo和undo日志,执行操作但是不提交操作;如果在询问阶段任意资源管理器返回不能执行操作的结果,则事务管理器向资源管理器发送终止请求,这里的逻辑与两阶段提交协议的准备阶段是类似的
  • 提交阶段:如果每个资源管理器在准备阶段都返回准备成功,也就是说预留资源和执行操作成功,则事务管理器向资源管理器发起提交指令,资源管理器提交资源变更的事务,释放锁定的资源;如果任何参与者返回准备失败,也就是说预留资源或者执行操作失败,则事务管理器向资源管理器发起终止指令,资源管理器取消已经变更的事务,执行undo日志,释放锁定的资源,这里的逻辑与两阶段提交协议的提交阶段是一致的。

三阶段提交-询问阶段成功

三阶段提交-询问阶段失败

三阶段提交-准备阶段成功

三阶段提交-准备阶段失败

三阶段提交-提交成功时序图

三阶段提交协议与两阶段提交协议主要有以下两个不同点:

  • 增加了一个询问阶段,询问阶段可以确保尽可能早地发现无法执行操作而需要终止的行为,但是它并不能发现所有这种行为,只会减少这种情况发生
  • 在准备阶段以后,事务管理器和资源管理器执行的任务都增加了超时,一旦超时,则事务管理器和资源管理器都会继续提交事务,默认为成功,这也是根据概率统计超市后默认为成功的正确性最大。

TCC(最终一致性)

TCC协议将一个任务拆分成Try、Confirm、Cancel三个步骤。正常流程会先执行Try,如果执行没有问题,则再执行Confirm,如果执行过程中出了问题,则执行操作的逆操作Cancel。

  • Try:尝试执行,完成所有业务检查(一致性),预留必须业务资源(准隔离性)
  • Confirm:确认执行真正执行业务,不作任何业务检查,只使用Try阶段预留的业务资源,Confirm操作满足幂等性。要求具备幂等设计,Confirm失败后需要进行重试。
  • Cancel:取消执行,释放Try阶段预留的业务资源。 Cancel操作满足幂等性,Cancel阶段的异常和Confirm阶段异常处理方案基本上一致。

从正常流程上讲,这仍然是一个两阶段提交协议,但是在执行出现问题时有一定的自我修复能力,如果任何参与者出现了问题,则事务管理器通过执行操作的逆操作Cancel之前的操作,达到最终一致的状态。

本地消息表(最终一致性)

本地消息表的方案最初是由ebay提出的。核心思路是将分布式事务拆分成本地事务进行处理。方案通过在事务主动发起方额外新建事务消息表,事务发起方处理业务和记录事务消息在本地事务中完成,轮询事务消息表的数据发送事务消息,事务被动方基于消息中间件消费事务消息表中的事务。

这样设计可以避免“业务处理成功+事务消息发送失败”或“业务处理失败+事务消息发送成功”的棘手情况出现,保证2个系统事务的数据一致性。

本地消息表

以上文案例1为例,扣库存和下订单服务分布在不同的服务器节点上,其中库存服务是事务主动方,订单服务是事务被动方。

事务的主动方需要往外新建事务消息表,用于记录分布式事务的消息的发生、处理状态。

整个业务处理流程如下:

本地消息表业务流程

  1. 事务主动方处理本地事务
  2. 事务主动方通过消息中间件,通知事务被动方处理事务
  3. 事务被动方通过消息中间件,通知事务主动方已处理的消息

为了数据一致性,当处理错误需要重试,事务发送方和事务接收方相关业务处理需要支持幂等。具体保存一致性的容错处理如下:

  1. 当步骤1处理出错,事务回滚,等同于什么都没有发生
  2. 当步骤2、步骤3处理出错,由于未处理的事务消息还是保存在事务发送方,事务发送方可以定时轮询为超时消息数据,再次发送的消息中间件进行处理。事务被动方消费事务消息重试处理。
  3. 如果是业务上的失败,事务被动方可以发消息给事务主动方进行回滚。
  4. 如果多个事务被动方已经消费消息,事务主动方需要回滚事务时需要通知事务被动方回滚。

MQ事务(最终一致性)

基于MQ的分布式事务方案其实是对本地消息表的封装,将本地消息表基于MQ 内部,其他方面的协议基本与本地消息表一致。

在本地消息表方案中,保证事务主动方发写业务表数据和写消息表数据的一致性是基于数据库事务,RocketMQ的事务消息相对于普通MQ,相对于提供了2PC的提交接口,方案如下:

正常情况——事务主动方发消息 这种情况下,事务主动方服务正常,没有发生故障,发消息流程如下:

MQ事务成功

  1. 发送方向 MQ服务端(MQ Server)发送half消息
  2. MQ Server 将消息持久化成功之后,向发送方 ACK 确认消息已经发送成功
  3. 发送方开始执行本地事务逻辑
  4. 发送方根据本地事务执行结果向 MQ Server 提交二次确认(commit 或是 rollback)
  5. MQ Server 收到 commit 状态则将半消息标记为可投递,订阅方最终将收到该消息;MQ Server 收到 rollback 状态则删除half消息,订阅方将不会接受该消息。

MQ事务异常

  1. MQ Server 对该消息发起消息回查。
  2. 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
  3. 发送方根据检查得到的本地事务的最终状态再次提交二次确认
  4. MQ Server基于commit / rollback 对消息进行投递或者删除

Saga事务(最终一致性)

其核心思想是将长事务拆分为多个本地短事务,由Saga事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。

每个Saga事务都是由一些本地子事务T1、T2、……、Tn组成,对应的每个事务Ti,都会有一Ti,撤销操作Ci。

Saga事务只有两种执行路径:

  1. 事务正常执行完成:T1、T2、……、Tn
  2. 事务Ti执行失败:T1、T2、……、Ti、Ci、Ci-1、……、C1

Saga事务执行情况

Saga恢复策略

向前恢复

向前恢复

本地事务Ti执行失败,就一直执行Ti,直到成功为止,在这种情况下,没有对应的撤销Ci操作。

向后恢复

向后恢复

本地事务Ti执行失败,就执行对应的撤销操作Ci,直到执行完C0为止。

实现方式

命令协调

中央协调器负责集中处理事件的决策和业务逻辑排序。

中央协调器(Orchestrator,简称OSO)以命令/回复的方式与每项服务进行通信,全权负责告诉每个参与者该做什么以及什么时候该做什么。

Saga命令协调模式

事件编排

没有中央协调器(没有单点风险)时,每个服务产生并观察其他服务的事件,并决定是否应采取行动。

在事件编排方法中,第一个服务执行一个事务,然后发布一个事件。该事件被一个或多个服务进行监听,这些服务再执行本地事务并发布(或不发布)新的事件。

当最后一个服务执行本地事务并且不发布任何事件时,意味着分布式事务结束,或者它发布的事件没有被任何Saga参与者听到都意味着事务结束。

Saga事件编排

保证最终一致性的模式

在大规模、高并发服务化系统中,一个功能被拆分成多个具有单一功能的子功能,一个流程会有多个系统的多个单一功能的服务组合实现,如果实现两阶段提交协议和三阶段提交协议,则缺失能解决系统间的一致性问题。除了这两个协议的自身问题,其实现也比较复杂、成本比较高,最重要的是性能不好,相比来看,TCC协议更简单且更容易实现,但是TCC协议由于每个事务都需要执行Try,再执行Confirm,略显臃肿,因此,现实系统的底线是仅仅需要达到最终一致性,而不需要实现专业的、复杂的一致性协议。实现最终一致性有一些非常有效、简单的模式。

查询模式

任何服务操作都需要提供一个查询接口,用来向外部输出操作执行的状态。服务操作的使用方可以通过查询接口的值服务操作的执行状态,然后根据不同的状态来做不同的处理操作。

上文的案例2-5,可以使用查询模式来了解被调用服务的处理情况,决定下一步做什么,例如是补偿未完成的操作还是混滚已经完成的操作。

补偿模式

为了让系统最终达到一状态而做的牡蛎都叫做补偿操作。

补偿操作根据发起形式分为以下几种:

  • 自动恢复:程序根据发生不一致的环境,通过继续进行未完成的操作,或者回滚已经完成的操作,来自动达到一致状态
  • 通知运营:如果程序无法自动恢复,并且设计时考虑到了不一致的场景,则可以提供运营功能,通过运营手动进行补偿
  • 如果很不巧,系统无法自动恢复,有没有运营功能,那么必须通过技术手段来解决,技术手段包括进行数据库变更或者代码变更

异步确保模式

异步确保模式是补偿模式的一个典型案例,经常应用到使用方对响应时间要求不太高的场景中,通常把这类操作从主流程中摘除,通过异步的方式进行处理,处理后把结果通过通知系统通知给使用方。

案例3中,若对某个操作没有收到响应,则通过查询模式、补偿模式和异步确保模式来继续未完成的操作。

定期校对模式

在操作主流程中的系统间执行校对操作,可以在时候异步的批量校对操作的状态,如果发现不一致的操作,则进行补偿。

对于案例4和4,通常通过定期校对模式发现问题,并通过补偿模式来修复,最后达到系统间的最终一致性。

可靠消息模式

通过消息队列实现异步化。

消息的可靠发送

消息的可靠发送可以认为是仅最大努力发送消息通知,有以下两种实现方法。

第一种是在发送消息之前将消息持久到数据库,状态标记为待发送,然后发送消息,如果发送成功,则将消息改为发送成功。定时任务定时从数据库老区在一定时间内未发送的消息并发送。

第二种是持久消息的数据库时独立的。

消息处理器的幂等性

  • 使用数据库表的唯一键进行滤重
  • 使用分布式表对请求进行滤重
  • 使用状态流转的方向性来滤重,通常使用数据库的行级锁进行实现
  • 根据业务的特点,操作本身就是幂等的,例如:删除一个资源等

缓存一致性模式

  • 如果性能要求不是非常高,则尽量使用分布式缓存,而不要使用本地缓存
  • 写缓存时数据一定要完成,如果缓存数据的一部分有效,另一部分无效,则宁可在需要时回源数据库,也不要把部分数据放入缓存中
  • 使用缓存牺牲了一致性,为了提高性能,数据库与缓存只需要保持弱一致性,而不需要保持强一致性,否则就违背了使用缓存的初衷
  • 读的顺序是先读缓存,后读数据库,写的顺序是先写数据库,后写缓存。

参考资料

百度百科-分布式事务

再有人问你分布式事务,把这篇扔给他

理解分布式事务

分布式服务架构原理、设计与实战

0%