分布式事务

分布式事务、2pc、3pc、TCC

单数据源事务

单数据源事务也可以叫做单机事务或本地事务。可利用数据库提供的事务机制 ACID 即可保证事务一致性。 在分布式场景下,一个系统有多个子系统构成, 每个子系统有自己的数据源,此时仅靠本地事务机制就无法保证全局的事务一致性,也就无法保证数据一致性。 分布式事务模型

  • 事务参与者:例如每个数据库就是一个事务参与者
  • 事务协调者:访问多个数据源的服务程序,例如 shopping-service 就是事务协调者
  • 资源管理器(Resource Manager, RM):通常与事务参与者同义
  • 事务管理器(Transaction Manager, TM):通常与事务协调者同义

在分布式事务模型中,一个 TM 管理多个 RM,即一个服务程序访问多个数据源;TM 是一个全局事务管理器,协调多方本地事务的进度,使其共同提交或回滚,最终达成一种全局的 ACID 特性。 二将军问题

  • B 军队被两支 A 军队围在山谷,A1 军队将军要通知另一支 A2 军队,约定时间攻击 B 军队,因此 A1 军队要派信使去传递消息,但是信使要经过山谷,可能被 B 军队俘虏。那么如果信使没有返回 A1,如下两种情况:1.信使还没到 A2,被俘虏,此时 A2 不知道进攻时间,A1 也不知道 A2 有没有收到通知。2.信使已告知 A2,但是返回途中被俘虏,A1 还是不知道 A2 有没有收到通知。
  • 类似的问题在计算机网络中普遍存在,例如发送者给接受者发送一个 HTTP 请求,或者 MySQL 客户端向 MySQL 服务器发送一条插入语句,然后超时了没有得到响应。请问服务器是写入成功了还是失败了?消息发送者不知道,因此往往要重复发送消息直到收到响应。例如电商系统中订单模块调用支付模块扣款的时候,如果网络故障导致二将军问题出现,扣款请求重复发送,产生的重复扣款结果显然是不能被接受的。因此要保证一次事务中的扣款请求无论被发送多少次,接收方有且只执行一次扣款动作,这种保证机制叫做接收方的幂等性。

分布式事务解决方案

2PC

2pc 是解决分布式事务的最简单的模型,分为 2 个阶段:

  1. 准备阶段:事务协调者向各个事务参与者发询问请求,通知即将执行全局事务,各自做好资源准备,即各自执行本地事务到待提交阶段。各个事务参与者准备好后响应 ACK 或 no 或协调者等待超时。
  2. 提交/回滚阶段:如果所有事务参与者响应 ACK,则由事务协调者通知进行全局事务最终的提交阶段。如果有一个参与者 no 或协调者等待超时,则要回滚阶段。

要实现 2PC,所有的参与者都要实现三个接口:

  • Prepare():TM 调用该接口询问各个本地事务是否就绪

  • Commit():TM 调用该接口要求各个本地事务提交

  • Rollback():TM 调用该接口要求各个本地事务回滚

可以将这三个接口简单地(但不严谨地)理解成 XA 协议。

XA 协议是 X/Open 提出的分布式事务处理标准。MySQL、Oracle、DB2 这些主流数据库都实现了 XA 协议,因此都能被用于实现 2PC 事务模型。 > 2PC 存在问题

  • 性能差:准备阶段要等所有事务参与者响应才能进入提交回滚阶段,这期间参与者的相关资源会被锁住,影响各个参与者的本地事务并发度;
  • 如果准备阶段完成,协调者挂了,那么所有参与者都收不到提交或回滚指令,导致所有参与者会一直阻塞直到协调者恢复,参与者没有超时机制,导致长时间资源锁定。

3PC

3PC 的出现是为了解决 2PC 的一些问题,相比于 2PC 它在参与者中也引入了超时机制 ,并且新增了一个阶段使得参与者可以利用这一个阶段统一各自的状态。3pc 分为三个阶段:

  • 准备阶段:协调者只是询问参与者的自身状况。
  • 预提交阶段:和 2pc 的准备阶段一样。
  • 提交阶段:提交阶段和 2PC 的一样。

3PC 缺点

3PC 相对于 2PC 做了一定的改进:引入了参与者超时机制,并且增加了预提交阶段使得故障恢复之后协调者的决策复杂度降低,但只能让协调者知道该如何做,但不能保证这样做一定对,但整体的交互过程更长了,性能有所下降,并且还是会存在数据不一致问题。一般都需要有定时扫描补偿机制。

协调者故障问题及解决

故障时机

  • 假设协调者在发送准备命令之前挂了,还行等于事务还没开始。
  • 假设协调者在发送准备命令之后挂了,此时参与者都执行了处于事务资源锁定的状态。事务执行不下去,还会因为锁定了一些公共资源而阻塞系统其它操作。
  • 假设协调者在发送回滚事务命令之前挂了,那么事务也是执行不下去,且在第一阶段那些准备成功参与者都阻塞着。
  • 假设协调者在发送回滚事务命令之后挂了,这个还行,至少命令发出去了,很大的概率都会回滚成功,资源都会释放。但是如果出现网络分区问题,某些参与者将因为收不到命令而阻塞着。
  • 假设协调者在发送提交事务命令之前挂了,这个不行,傻了!这下是所有资源都阻塞着。
  • 假设协调者在发送提交事务命令之后挂了,这个还行,也是至少命令发出去了,很大概率都会提交成功,然后释放资源,但是如果出现网络分区问题某些参与者将因为收不到命令而阻塞着。

解决

因为协调者单点问题,因此通过选举等操作选出一个新协调者来顶替。然而每个参与者的情况只有协调者和参与者本身知道,协调者发送了回滚命令,此时第一个参与者收到了并执行,然后协调者和第一个参与者都挂了。那么新上任的协调者是不知道挂了的参与者是否还活着,那么此时就可以通过让协调者记录日志,但就算协调者知道自己该发提交请求,那么在参与者也一起挂了的情况下没用,因为协调者不知道参与者在挂之前有没有提交事务。所以说极端情况下还是无法避免数据不一致问题

PC 缺点

  • 尽量保证强一致性的分布式事务
  • 又因为同步阻塞,所以效率低
  • 存在单点故障问题,导致数据不一致

TCC

2PC 和 3PC 都是数据库层面的,而 TCC 是业务层面的分布式事务,分布式事务不仅仅包括数据库的操作,还包括发送短信等。此时 TCC 就有用了。

TCC 就是一种解决多个微服务之间的分布式事务问题的方案。TCC 是 Try、Confirm、Cancel 三个词的缩写,其本质是一个应用层面上的 2PC,分为两个阶段:

  • 准备阶段:协调者调用所有微服务提供的 try 接口,将整个全局事务涉及到的资源锁定住,若锁定成功 try 接口向协调者返回 yes。
  • 提交阶段:若所有的服务的 try 接口在阶段一都返回 yes,则进入提交阶段,协调者调用所有服务的 confirm 接口,各个服务进行事务提交。如果有任何一个服务的 try 接口在阶段一返回 no 或者超时,则协调者调用所有服务的 cancel 接口。

TCC 有两个问题:

  • 既然 TCC 是一种服务层面上的 2PC,它是如何解决 2PC 无法应对宕机 问题的缺陷的呢?答案是不断重试。由于 try 操作锁住了全局事务涉及的所有资源,保证了业务操作的所有前置条件得到满足,因此无论是 confirm 阶段失败还是 cancel 阶段失败都能通过不断重试直至 confirm 或 cancel 成功(所谓成功就是所有的服务都对 confirm 或者 cancel 返回了 ACK)。
  • 在不断重试 confirm 和 cancel 的过程中(二将军问题)有可能重复进行了 confirm 或 cancel,因此还要再保证 confirm 和 cancel 操作具有幂等性,也就是整个全局事务中,每个参与者只进行一次 confirm 或者 cancel。实现 confirm 和 cancel 操作的幂等性,有很多解决方案,例如每个参与者可以维护一个去重表(可以利用数据库表实现也可以使用内存型 KV 组件实现),记录每个全局事务(以全局事务标记 XID 区分)是否进行过 confirm 或 cancel 操作,若已经进行过,则不再重复执行。

事务状态表/本地消息表

类似 TCC 的事务解决方案,借助事务状态表来实现。假设要在一个分布式事务中实现调用 repo-service 扣减库存、调用 order-service 生成订单两个过程。在这种方案中,协调者 shopping-service 维护一张如下的事务状态表,初始状态为 1,每成功调用一个服务则更新一次状态,最后所有的服务调用成功,状态更新到 3

基于消息中间件的解决方案

无论是 2PC & 3PC 还是 TCC、事务状态表,基本都遵守 XA 协议的思想,即这些方案本质上都是事务协调者协调各个事务参与者的本地事务的进度,使所有本地事务共同提交或回滚,最终达成一种全局的 ACID 特性。在协调的过程中,协调者需要收集各个本地事务的当前状态,并根据这些状态发出下一阶段的操作指令。

但是这些全局事务方案由于操作繁琐、时间跨度大,或者在全局事务期间会排他地锁住相关资源,使得整个分布式系统的全局事务的并发度不会太高。这很难满足电商等高并发场景对事务吞吐量的要求。

消息队列

RocketMQ 就支持消息事务,RocketMQ 的发送方会提供一个反查事务状态接口,如果一段时间内半消息没有收到任何操作请求,那么 Broker 会通过反查接口得知发送方事务是否执行成功,然后执行 Commit 或者 RollBack 命令。

Seata

由阿里巴巴集团开发并开源,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

核心组件

Transaction Coordinator (TC)

事务协调器,负责维护全局事务的状态,驱动全局事务的提交或回滚。

Transaction Manager (TM)

事务管理器,负责定义全局事务的范围,开始、提交或回滚全局事务。

Resource Manager (RM)

资源管理器,负责管理分支事务处理的资源,与 TC 交互以注册分支事务和报告分支事务的状态,并驱动分支事务的提交或回滚。

事务模式

AT 模式

无侵入的分布式事务解决方案,通过拦截业务 SQL,解析 SQL 语义,生成前后镜像数据,生成回滚日志,并在本地事务中提交业务 SQL 和回滚日志。

TCC 模式

支持 TCC 模式并可与 AT 模式混用,提供更高的灵活度。

SAGA 模式

为长事务提供有效的解决方案,支持编排式与注解式开发模式。

XA 模式

基于 X/Open 组织定义的分布式事务处理标准,利用事务资源对 XA 协议的支持来管理分支事务。

工作流程
  1. TM 向 TC 申请开启一个全局事务,TC 生成一个全局唯一的 XID。
  2. XID 通过微服务的调用链传播到其他微服务。
  3. RM 向 TC 注册分支事务,并将其纳入 XID 对应的全局事务中。
  4. TM 根据所有分支事务的执行结果,向 TC 发起全局提交或回滚请求。
  5. TC 调度所有分支事务完成提交或回滚。
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计