一、分布式事务概述
在分布式系统中,各个节点之间在物理上相互独立,通过网络进行沟通和协调。由于存在事务机制,可以保证每个独立节点上的数据操作可以满足 ACID。
相互独立的节点之间无法准确的知道其他节点中的事务执行情况,所以两台机器理论上无法达到一致的状态。如果想让分布式部署的多台机器中的数据保持一致性,就要保证在所有节点的数据写操作,要么全部都执行,要么全部的都不执行。
一台机器在执行本地事务的时候无法知道其他机器中的本地事务的执行结果,也就不知道本次事务到底应该 commit 还是 roolback。常规的解决办法就是引入一个“协调者”的组件来统一调度所有分布式节点的执行。
二、两阶段提交协议
一般分为协调器 TC 和若干事务执行者 Si 两种角色。
- 应用程序(client)发起一个开始请求到 TC;
- TC 先将 prepare 消息写到本地日志,之后向所有的 Si 发起 prepare 消息。
- Si 收到 prepare 消息后,执行具体本机事务,但不会进行 commit,如果成功返回 yes,不成功返回 no。同理,返回前都应把要返回的消息写到日志里,当作凭证。
- TC 收集所有执行器返回的消息,如果所有执行器都返回 yes,那么给所有执行器发生送 commit 消息,执行器收到 commit 后执行本地事务的 commit 操作;如果有任一个执行器返回 no,那么给所有执行器发送 abort 消息,执行器收到 abort 消息后执行事务 abort 操作。
TC 或 Si 把发送或接收到的消息先写到日志里,主要是为了故障后恢复用。
缺点
- 同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态
- 单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)。
三、MQ 消息保证最终一致性
通过将一系列同步的事务操作变为基于消息执行的异步操作,避免了分布式事务中的同步阻塞操作的影响。
执行步骤如下:
- MQ 发送方发送远程事务消息到 MQ Server;
- MQ Server 给予响应,表明事务消息已成功到达 MQ Server;
- MQ 发送方 Commit 本地事务;
- 若本地事务 Commit 成功,则通知 MQ Server 允许对应事务消息被消费;若本地事务失败,则通知 MQ Server 对应事务消息应被丢弃;
- 若 MQ 发送方超时未对 MQ Server 作出本地事务执行状态的反馈,那么需要 MQ Server 向 MQ 发送方主动回查事务状态,以决定事务消息是否能被消费;
- 当得知本地事务执行成功时,MQ Server 允许 MQ 订阅方消费本条事务消息。
需要额外说明的一点,就是事务消息投递到 MQ 订阅方后,并不一定能够成功执行,需要MQ订阅方主动给予消费反馈(ack):
- 如果 MQ 订阅方执行远程事务成功,则给予消费成功的 ack,那么 MQ Server 可以安全将事务消息移除;
- 如果执行失败,MQ Server 需要对消息重新投递,直至消费成功。
注意事项
- 消息中间件在系统中扮演一个重要的角色,所有的事务消息都需要通过它来传达,所以消息中间件也需要支持高可用集群来确保事务消息不丢失;
- 根据业务逻辑的具体实现不同,还可能需要对消息中间件增加消息不重复、不乱序等其它要求。
适用场景
- 执行周期较长
-
四、TCC 事务
TCC 分别对应 Try、Confirm 和 Cancel 三种操作,这三种操作的业务含义如下:
Try:预留业务资源;
- Confirm:确认执行业务操作;
-
对比关系型数据库事务
对照关系型数据库事务的三种操作:DML、Commit 和 Rollback,会发现和 TCC 有异曲同工之妙。
在一个跨应用的业务操作中: Try 操作是先把多个应用中的业务资源预留和锁定住,为后续的确认打下基础;类似的,DML 操作要锁定数据库记录行,持有数据库资源。
- Confirm 操作是在 Try 操作中涉及的所有应用均成功之后进行确认,使用预留的业务资源,和 Commit类似。
- Cancel 则是当 Try 操作中涉及的所有应用没有全部成功,需要将已成功的应用进行取消(即 Rollback 回滚)。
TCC 操作内容
TCC 每项操作需要做的事情如下:
- Try:尝试执行业务,完成所有业务检查(一致性),预留必须业务资源(准隔离性);
- Confirm:确认执行业务,真正执行业务不做任何业务检查只使用 Try 阶段预留的业务资源;
Cancel:取消执行业务释放 Try 阶段预留的业务资源 。
TCC 事务参与方
一个完整的 TCC 事务参与方包括三部分:
主业务服务:主业务服务为整个业务活动的发起方;
- 从业务服务:从业务服务负责提供 TCC 业务操作,是整个业务活动的操作方。从业务服务必须实现 Try、Confirm 和 Cancel 三个接口,供主业务服务调用。由于 Confirm 和 Cancel 操作可能被重复调用,故要求 Confirm 和 Cancel 两个接口必须是幂等的。
- 业务活动管理器:业务活动管理器管理控制整个业务活动,包括记录维护 TCC 全局事务的事务状态和每个从业务服务的子事务状态,并在业务活动提交时确认所有的 TCC 型操作的 Confirm 操作,在业务活动取消时调用所有 TCC 型操作的 Cancel 操作。
整个 TCC 事务对于主业务服务来说是透明的,其中业务活动管理器和从业务服务各自干了一部分工作。
缺点
主要就一个:TCC 的 Try、Confirm 和 Cancel 操作功能需业务提供,开发成本高。
案例
一个案例理解 TCC:分别位于三个不同分库的帐户 A、B、C,A 和 B 一起向 C 转帐共 80 元:
Try
尝试执行业务。完成所有业务检查(一致性):检查 A、B、C 的帐户状态是否正常,帐户A的余额是否不少于 30 元,帐户 B 的余额是否不少于 50 元。
预留必须业务资源(准隔离性):帐户A的冻结金额增加 30 元,帐户 B 的冻结金额增加 50 元,这样就保证不会出现其他并发进程扣减了这两个帐户的余额而导致在后续的真正转帐操作过程中,帐户 A 和 B 的可用余额不够的情况。
Confirm
确认执行业务。真正执行业务:如果 Try阶段帐户 A、B、C 状态正常,且帐户 A、B 余额够用,则执行帐户 A 给账户 C 转账 30 元、帐户 B 给账户 C 转账 50 元的转帐操作。不做任何业务检查:这时已经不需要做业务检查,Try 阶段已经完成了业务检查。只使用 Try 阶段预留的业务资源:只需要使用 Try 阶段帐户 A 和帐户 B 冻结的金额即可。
Cancel
取消执行业务释放 Try 阶段预留的业务资源:如果 Try 阶段部分成功,比如帐户 A 的余额够用,且冻结相应金额成功,帐户 B 的余额不够而冻结失败,则需要对帐户 A 做 Cancel 操作,将帐户 A 被冻结的金额解冻掉。