0
点赞
收藏
分享

微信扫一扫

面试又被问分布式事务?看完这篇吊打面试官

一、什么是分布式事务?

聊什么是分布式事务前,先聊一下我们熟悉的单机事务。所谓单机事务是相对分布式事务来说的,即数据库事务。大家都知道数据库事务有ACID这四个特性:

  • A(Atomicity):原子性,指单个事务中的操作要么全部完成,要么全部不完成。
  • C(Consistency):一致性,指事务前后数据的完整性必须保持一致
  • I(Isolation):隔离性,指多个事务对数据可见性的规则。
  • D(Durability):持久性,指事务提交后,永久有效,即使系统故障也不会丢失。

既然数据库事务有这四个特性的,那么分布式事务也不例外,应该具备这四个特性。

在微服务架构下,服务之间通过“网络通信”调用,分布式事务就是要保证多个服务在“网络通信”之间的事务一致性,简单理解就是跨JVM进程产生的事务一致性。比如:订单微服务和库存微服务,订单微服务下单,库存微服务扣减两个动作要保证事务一致性。


二、分布式事务有哪几种解决方案?

2阶段提交(2PC)

二阶段提交协议(Two-phase Commit,即 2PC)是常用的分布式事务解决方案,即将事务的提交过程分为两个阶段来进行处理。

2PC引入一个第三方的事务协调者,即Coordinator,其他参与事务的节点为参与者,即Participants。协调者统筹整个事务行为,负责通知参与者进行Commit还是Rollback操作。

两个阶段分别为:

  • 准备阶段
  • 提交阶段


准备阶段(投票阶段)

面试又被问分布式事务?看完这篇吊打面试官_2PC

  1. 协调者向所有参与者发送事务内容,询问是否可以提交事务,并等待答复
  2. 各参与者执行事务操作,将 undo 和 redo 信息记入事务日志中(但不提交事务)
  3. 如参与者执行成功,给协调者反馈同意,否则反馈中止


提交阶段

面试又被问分布式事务?看完这篇吊打面试官_事务消息_02

  1. 协调者节点向所有参与者节点发出正式提交(commit)的请求。
  2. 参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
  3. 参与者节点向协调者节点发送ack完成消息。
  4. 协调者节点收到所有参与者节点反馈的ack完成消息后,完成事务。


回滚阶段


面试又被问分布式事务?看完这篇吊打面试官_2PC_03

  1. 协调者节点向所有参与者节点发出回滚操作(rollback)的请求。
  2. 参与者节点利用阶段1写入的undo信息执行回滚,并释放在整个事务期间内占用的资源。
  3. 参与者节点向协调者节点发送ack回滚完成消息。
  4. 协调者节点收到所有参与者节点反馈的ack回滚完成消息后,取消事务。


缺点:

  • 性能问题:执行过程中,所有参与节点都是事务阻塞性的,当参与者占有公共资源时,其他第三方节点访问公共资源就不得不处于阻塞状态,为了数据的一致性而牺牲了可用性,对性能影响较大,不适合高并发高性能场景
  • 可靠性问题:2PC非常依赖协调者,当协调者发生故障时,尤其是第二阶段,那么所有的参与者就会都处于锁定事务资源的状态中,而无法继续完成事务操作
  • 数据一致性问题:在阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这会导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
  • 二阶段无法解决的问题:协调者在发出 commit 消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了,那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。


优点:尽可能的保证了数据强一致性,适合对数据强一致性要求较高的场景。(并不能100%保证强一致)

3阶段提交(3PC)

3PC,三阶段提交协议,是二阶段提交协议的改进版本,三阶段提交有两个改动点:

  • 在协调者和参与者中都引入超时机制
  • 在第一阶段和第二阶段中插入一个准备阶段,保证了在最后提交阶段之前各参与节点的状态是一致的。


所以3PC会分为3个阶段,CanCommit 准备阶段、PreCommit 预提交阶段、DoCommit 提交阶段,处理流程如下:


面试又被问分布式事务?看完这篇吊打面试官_事务消息_04

阶段一:CanCommit阶段

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

  • 事务询问:协调者向所有参与者发出包含事务内容的 canCommit 请求,询问是否可以提交事务,并等待所有参与者答复。
  • 响应反馈:参与者收到 canCommit 请求后,如果认为可以执行事务操作,则反馈 yes 并进入预备状态,否则反馈 no。

面试又被问分布式事务?看完这篇吊打面试官_分布式事务_05

阶段二:PreCommit阶段

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

  • 假如所有参与者均反馈 yes,协调者预执行事务,具体如下:
  1. 发送预提交请求:协调者向参与者发送 PreCommit 请求,并进入准备阶段
  2. 事务预提交 :参与者接收到 PreCommit 请求后,会执行本地事务操作,并将 undo 和 redo 信息记录到事务日志中(但不提交事务)
  3. 响应反馈 :如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。​

面试又被问分布式事务?看完这篇吊打面试官_TCC_06

  • 任何一个参与者反馈 no,或者等待超时后协调者尚无法收到所有参与者的反馈,即中断事务
  1. 发送中断请求 :协调者向所有参与者发送abort请求。
  2. 中断事务 :参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。

面试又被问分布式事务?看完这篇吊打面试官_TCC_07

阶段三:doCommit阶段

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

进入阶段 3 后,无论协调者出现问题,或者协调者与参与者网络出现问题,都会导致参与者无法接收到协调者发出的 doCommit 请求或 abort 请求。此时,参与者都会在等待超时之后,继续执行事务提交。

提交事务

  1. 发送提交请求:协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。
  2. 事务提交:参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
  3. 响应反馈:事务提交完之后,向协调者发送ack响应。
  4. 完成事务:协调者接收到所有参与者的ack响应之后,完成事务。

面试又被问分布式事务?看完这篇吊打面试官_事务消息_08

中断事务

任何一个参与者反馈 no,或者等待超时后协调者尚无法收到所有参与者的反馈,即中断事务

  1. 发送中断请求:如果协调者处于工作状态,向所有参与者发出 abort 请求
  2. 事务回滚:参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。
  3. 反馈结果:参与者完成事务回滚之后,向协调者反馈ACK消息
  4. 中断事务:协调者接收到参与者反馈的ACK消息之后,执行事务的中断。

面试又被问分布式事务?看完这篇吊打面试官_2PC_09

优点:

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

缺点:

数据不一致问题依然存在,当在参与者收到 preCommit 请求后等待 doCommit 指令时,此时如果协调者请求中断事务,而协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。

面试又被问分布式事务?看完这篇吊打面试官_TCC_10 TCC(事务补偿)

TCC(Try Confirm Cancel)方案是一种应用层面侵入业务的两阶段提交。其核心思想是:针对每个操作,都要实现对应的确认和补偿(撤销)操作相当于XA来说,TCC可以不依赖于数据库,它是通过业务逻辑来控制确认和补偿操作的,所以它用了’Cancel’而非’Rollback’的字眼。它是一个应用层面的2PC。

TCC的执行流程可以分为两个阶段,分别如下:

  1. 第一阶段:Try,业务系统做检测并预留资源 (加锁,锁住资源),比如常见的下单,在try阶段,我们不是真正的减库存,而是把下单的库存给锁定住。
  2. 第二阶段:根据第一阶段的结果决定是执行confirm还是cancel
  • Confirm:执行真正的业务(执行业务,释放锁)
  • Cancle:是对Try阶段预留资源的释放(出问题,释放锁)

面试又被问分布式事务?看完这篇吊打面试官_2PC_11

最终一致性保证

  • TCC 事务机制以初步操作(Try)为中心的,确认操作(Confirm)和取消操作(Cancel)都是围绕初步操作(Try)而展开。因此,Try 阶段中的操作,其保障性是最好的,即使失败,仍然有取消操作(Cancel)可以将其执行结果撤销。
  • Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。也就是说只要Try成功,Confirm一定成功(TCC设计之初的定义) 。
  • Confirm与Cancel如果失败,由TCC框架进行重试补偿
  • 存在极低概率在CC环节彻底失败,则需要定时任务或人工介入

方案总结

TCC 事务机制相比于上面介绍的 XA 事务机制,有以下优点:

  • 性能提升:具体业务来实现控制,资源锁的粒度变小,不会锁定整个资源。
  • 数据最终一致性:基于 Confirm 和 Cancel 的幂等性,保证事务最终完成确认或者取消,保证数据的一致性。
  • 可靠性:解决了 XA 协议的协调者单点故障问题,由主业务方发起并控制整个业务活动,业务活动管理器也变成多点,引入集群。

缺点:

  • TCC 的 Try、Confirm 和 Cancel 操作功能要按具体业务来实现,业务耦合度较高,增加了开发成本。


Saga

Saga事务模型又叫做长时间运行的事务(Long-running-transaction), 它是由普林斯顿大学的H.Garcia-Molina等人提出,它描述的是另外一种在没有两阶段提交的的情况下解决分布式系统中复杂的业务事务问题。Saga的论文。

该模型其核心思想就是拆分分布式系统中的长事务为多个短事务,或者叫多个本地事务,然后由 Saga工作流引擎负责协调,如果整个流程正常结束,那么就算是业务成功完成,如果在这过程中实现失败,那么Saga工作流引擎就会以相反的顺序调用补偿操作,重新进行业务回滚。

Saga也是一种补偿协议,在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。

面试又被问分布式事务?看完这篇吊打面试官_事务消息_12

你可以看到Saga跟TCC很像,但是Saga更加宽松,一致性更弱,在Saga看来,在一阶段直接做提交/确认操作就好了,有问题再做补偿。这样的话,Saga可以拥有比XA和TCC更好的性能(XA、TCC需要锁定资源或预留资源),而且Saga强调通过事件驱动异步处理,实现高吞吐。

可以看出Saga是对TCC的一种“妥协”,从TCC的三个接口变为两个接口,一阶段直接提交缺少对资源的隔离(如果一阶段提交后,后面发现需要做补偿,但是补偿操作执行前有另外的事务更改了数据,这时数据已经变“脏”了,那么这时该如何处理是一个问题。在TCC没有这个问题,因为资源已经被hold住了),因此对使用者也是比较宽松的,对于现有业务的改造也会比较简单。

Saga实现分两种,一种是Saga状态机实现,一种是Saga AOP Proxy实现。Saga状态机实现,在关于参与者服务编排实现又有集中式和协同式两种分支。这点就不展开了。

TCC vs Saga

TCC和Saga都属于补偿型事务模型,Saga没有Try,直接Commit,所有会产生实际的事务痕迹,而补偿做的是反向操作。TCC是二阶段的广义实现,利用了数据的中间态,Cancel是中间状态的数据进行撤销,从而不存在数据污染问题。

使用场景对比:

  • TCC 适用于执行时间确定且较短、对一致性要求比较高、数据隔离强的业务
  • Saga 适用于业务流程长、业务流程多的业务,在银行业金融机构使用广泛
  • TCC 对现有业务改造较大,Saga则相对少点


本地消息表

本地消息表的方案最初是由 eBay 提出,核心思路是将分布式事务拆分成本地事务进行处理。

角色:

  • 事务主动方
  • 事务被动方

通过在事务主动发起方额外新建事务消息表,事务发起方处理业务和记录事务消息在本地事务中完成,轮询事务消息表的数据发送事务消息,事务被动方基于消息中间件消费事务消息表中的事务。

这样可以避免以下两种情况导致的数据不一致性:

  • 业务处理成功、事务消息发送失败
  • 业务处理失败、事务消息发送成功

整体流程如下图:

面试又被问分布式事务?看完这篇吊打面试官_TCC_13

上图中整体的处理步骤如下:

  • 事务主动方在同一个本地事务中处理业务和写消息表操作
  • ②事务主动方通过消息中间件,通知事务被动方处理事务通知事务待消息。事务主动方主动写消息到消息队列,事务消费方消费并处理消息队列中的消息。
  • ③事务被动方通过消息中间件,通知事务主动方事务已处理的消息。
  • 事务主动方接收中间件的消息,更新消息表的状态为已处理。

一些必要的容错处理如下:

  • 处理出错,由于还在事务主动方的本地事务中,直接回滚即可
  • 当②、③处理出错,由于事务主动方本地保存了消息,只需要轮询消息重新通过消息中间件发送,事务被动方重新读取消息处理业务即可。
  • 如果是业务上处理失败,事务被动方可以发消息给事务主动方回滚事务
  • 如果事务被动方已经消费了消息,事务主动方需要回滚事务的话,需要发消息通知事务主动方进行回滚事务。

优点

  • 从应用设计开发的角度实现了消息数据的可靠性,消息数据的可靠性不依赖于消息中间件,弱化了对 MQ 中间件特性的依赖。
  • 方案轻量,容易实现。

缺点

  • 与具体的业务场景绑定,耦合性强,不可公用。
  • 消息数据与业务数据同库,占用业务系统资源。
  • 业务系统在使用关系型数据库的情况下,消息服务性能会受到关系型数据库并发性能的局限。


MQ事务消息

下面主要基于 RocketMQ 4.3 之后的版本介绍 MQ 的分布式事务方案。

事务消息流程处理如下:

面试又被问分布式事务?看完这篇吊打面试官_分布式事务_14

  1. 生产者将消息发送至RocketMQ服务端。
  2. RocketMQ服务端将消息持久化成功之后,向生产者返回Ack确认消息已经发送成功,此时消息被标记为"暂不能投递",这种状态下的消息即为半事务消息(half消息)。
  3. 生产者开始执行本地事务逻辑。
  4. 生产者根据本地事务执行结果向服务端提交二次确认结果(Commit或是Rollback),服务端收到确认结果后处理逻辑如下:
  • 二次确认结果为Commit:服务端将半事务消息标记为可投递,并投递给消费者。
  • 二次确认结果为Rollback:服务端将回滚事务,不会将半事务消息投递给消费者。
  1. 在断网或者是生产者应用重启的特殊情况下,若服务端未收到发送者提交的二次确认结果,或服务端收到的二次确认结果为Unknown未知状态,经过固定时间后,服务端将对消息生产者即生产者集群中任一生产者实例发起消息回查。 
  2. 生产者收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
  3. 生产者根据检查到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息进行处理。

优点

相比本地消息表方案,MQ 事务方案优点是:

  • 消息数据独立存储 ,降低业务系统与消息系统之间的耦合。
  • 吞吐量大于使用本地消息表方案。

缺点

  • 一次消息发送需要两次网络请求(half 消息 + commit/rollback 消息) 。
  • 业务处理服务需要实现消息状态回查接口。
  • RocketMQ不保证消息消费结果和上游事务的一致性,即发送端事务提交,消费端消费失败,无法回滚上游发送端。官方建议消费端做好消息重试,如果有短暂失败可以利用重试机制保证最终处理成功。

最大努力通知

最大努力通知也称为定期校对,是对MQ事务方案的进一步优化。它在事务主动方增加了消息校对的接口,如果事务被动方没有接收到消息,此时可以调用事务主动方提供的消息校对的接口主动获取。

​最大努力通知的整体流程如下图:

面试又被问分布式事务?看完这篇吊打面试官_2PC_15

​在可靠消息事务中,事务主动方需要将消息发送出去,并且消息接收方成功接收,这种可靠性发送是由事务主动方保证的;

但是最大努力通知,事务主动方尽最大努力(重试,轮询....)将事务发送给事务接收方,但是仍然存在消息接收不到,此时需要事务被动方主动调用事务主动方的消息校对接口查询业务消息并消费,这种通知的可靠性是由事务被动方保证的。

最大努力通知适用于业务通知类型,例如微信,支付宝的支付交易结果,就是通过最大努力通知方式通知各个商户,既有回调通知,也有交易查询接口。


三、总结

总结一下各个方案的常见的使用场景:

  • 2PC/3PC:依赖于数据库,能够很好的提供强一致性和强事务性,但相对来说延迟比较高,比较适合传统的单体应用,在同一个方法中存在跨库操作的情况,不适合高并发和高性能要求的场景。
  • TCC:适用于执行时间确定且较短,实时性要求高,对数据一致性要求高,比如互联网金融企业最核心的三个服务:交易、支付、账务。
  • 本地消息表/MQ 事务:都适用于事务中参与方支持操作幂等,对一致性要求不高,业务上能容忍数据不一致到一个人工检查周期,事务涉及的参与方、参与环节较少,业务上有对账/校验系统兜底。
  • Saga 事务:由于 Saga 事务不能保证隔离性,需要在业务层控制并发,适合于业务场景事务并发操作同一资源较少的情况。Saga 相比缺少预提交动作,导致补偿动作的实现比较麻烦,例如业务是发送短信,补偿动作则得再发送一次短信说明撤销,用户体验比较差。Saga 事务较适用于补偿动作容易处理的场景。



举报

相关推荐

0 条评论