什么是分布式事务

简单说一下事务

事务就是单个逻辑执行的一系列操作,要么全部成功,要么全部失败。
事务包含4个特性(ACID):

  1. Atomicity(原子性):事务中包含的所有操作要么全做,要么全不做。
  2. Consistency(一致性):事务开始以前,数据库处于一致性的状态,事务结束后,数据库也必须处于一致性的状态。
  3. Isolation(隔离性):系统必须保证事务不受其他并发执行的事务的影响。
  4. Durability(持久性):一个事务一旦成功完成,它对数据库的改变必须是永久的,即使是在系统遇到故障的情况下也不会丢失。

假如没有事务

我们以银行的ATM机为例子:

取款操作一般为两个核心步骤:

  1. 余额扣除相应金额
  2. ATM机吐钞

如果金额扣除了,而ATM机却因某些原因无法吐钞,那用户就崩溃了。而若是金额没扣除,ATM却吐钞了,那就是银行崩溃了。所以事务(分布式)的重要性在这里就体现的淋漓尽致了,这也正是事务中的一致性。

说说分布式事务

分布式事务的体现有很多种,其中最具代表性的是由Oracle Tuxedo系统提出的XA分布式事务协议。

XA协议包含两阶段提交(2PC)三阶段提交(3PC两种实现。

两阶段提交(2PC)

两阶段提交就像支持多人游戏的网游游戏模式(可参考近日火热的PUBG)。

在游戏开始前,一个队伍中会有两种角色,队长与队员,也分别对应着事务协调者事务参与者

正向流程

一个XA两阶段提交的正向流程分为这两阶段:

第一阶段:

  1. 以发送邀请的玩家A为首,邀请到了呵自己开黑的小伙伴B、C、D进入队伍,并请求他们点击准备按钮。
  2. 小伙伴 B、C、D 全部准备就绪。

第二阶段:

  1. A 大吼一声,「伞兵一号准备就绪!」随即点击开始游戏。
  2. A 首先进入游戏,等待 B、C、D片刻后,大家都成功进入游戏地图。

对应到正经的XA中是这样的:

第一阶段:

  1. 协调者向参与者们发送Prepare请求

  2. 参与者们各自执行自己与事务有关的数据更新,写入Undo Log和Redo Log。如果参与者执行成功,暂时不提交事务,而是向事务协调节点返回“Done”消息。

    当事务协调者接到了所有参与者的返回消息,整个分布式事务将会进入第二阶段。

第二阶段:

  1. 如果事务协调节点在之前所收到都是正向返回,那么它将会向所有事务参与者发出Commit请求。
  2. 接到Commit请求之后,事务参与者节点会各自进行本地的事务提交,并释放锁资源。当本地事务完成提交后,将会向事务协调者返回“ACK”消息。
  3. 事务协调者接收到所有事务参与者的“完成”反馈,整个分布式事务完成。

失败处理

  1. 如果某个事务参与者反馈失败消息,说明该节点的本地事务执行不成功,必须回滚。
  2. 于是在第二阶段,事务协调节点向所有的事务参与者发送Abort请求。接收到Abort请求之后,各个事务参与者节点需要在本地进行事务的回滚操作,回滚操作依照Undo Log来进行。

XA两阶段提交的不足

性能问题

XA协议遵循强一致性。在事务执行过程中,各个节点占用着数据库资源,只有当所有节点准备完毕,事务协调者才会通知提交,参与者提交后释放资源。这样的过程有着非常明显的性能问题。

协调者单点故障问题

事务协调者是整个XA模型的核心,一旦事务协调者节点挂掉,参与者收不到提交或是回滚通知,参与者会一直处于中间状态无法完成事务。

丢失消息导致的不一致问题

在XA协议的第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就导致了节点之间数据的不一致。

如何避免XA两阶段提交的种种问题

有许多其他的分布式事务方案可供选择:

XA三阶段提交

XA三阶段提交在两阶段提交的基础上增加了CanCommit阶段,并且引入了超时机制。一旦事物参与者迟迟没有接到协调者的commit请求,会自动进行本地commit。这样有效解决了协调者单点故障的问题。但是性能问题和不一致的问题仍然没有根本解决。

MQ事务

利用消息中间件来异步完成事务的后一半更新,实现系统的最终一致性。这个方式避免了像XA协议那样的性能问题。

TCC事务

TCC事务是Try、Commit、Cancel三种指令的缩写,其逻辑模式类似于XA两阶段提交,但是实现方式是在代码层面来人为实现。