分布式之_3_分布式事务
前言
Github:https://github.com/HealerJean
前言
倘若限界上下文之间采用进程间通信,且遵循零共享架构,各个限界上下文访问自己专有的数据库,就会演变为微服务风格。微服务架构不能绕开的一个问题,就是如何处理分布式事务。
满足强一致性的分布式事务要解决的问题比本地事务复杂,因为它需要管理和协调所有分布式节点的事务资源,保证这些事务资源能够做到共同成功或者共同失败。为了实现这一目标,可以遵循
X/Open
组织为分布式事务处理制订的标准协议——X/A
协议。遵循X/A
协议的方案包括二阶段提交协议和基于它改进而来的三阶段提交协议
| 特性 | TCC
| 可靠事件模式 | Saga
-编排式 | Saga
-命令式 | 2PC
| 3PC
|
| :———-: | :————————————: | :————————: | :——————–: | :————————–: | :—————————-: | :———————————-: |
| 事务类型 | 柔性事务 | 柔性事务 | 柔性事务 | 柔性事务 | 刚性事务 | 刚性事务 |
| 一致性 | 最终一致性 | 最终一致性 | 最终一致性 | 最终一致性 | 强一致性 | 强一致性 |
| 实现方式 | Try
-Confirm
-``Cancel | 事件驱动+消息队列+数据标 | 事件驱动,无中心协调器 | 中心协调器负责协调 | 两阶段提交 | 三阶段提交 |
| **核心思想** | 预留资源,确认或取消 | 通过消息队列保证事件可靠性 | 服务之间通过事件通信 | 中心协调器控制事务流程 | 协调者与参与者协作提交或回滚 | 在
2PC 基础上引入预提交阶段 |
| **优点** | 高性能,适用于短事务 | 松耦合,易于扩展 | 松耦合,无单点故障 | 逻辑集中,易于调试 | 强一致性,适合高一致性场景 | 减少阻塞,提高容错性 |
| **缺点** | 需要设计
Try/
Confirm/
Cancel逻辑 | 消息队列可能成为瓶颈 | 逻辑分散,调试复杂 | 单点故障,协调器可能成为瓶颈 | 性能低,存在单点故障和阻塞问题 | 仍然存在单点故障,复杂度更高 |
| **适用场景** | 对一致性要求较低的短事务 | 异步处理,事件驱动场景 | 服务数量较少,逻辑简单 | 服务数量较多,逻辑复杂 | 对一致性要求极高的场景 | 对一致性要求高且需要更高容错性的场景 |
| **补偿机制** | 需要设计
Cancel 操作 | 通过重试或补偿消息实现 | 需要设计补偿事务 | 需要设计补偿事务 | 无补偿机制,直接回滚 | 无补偿机制,直接回滚 |
| **性能** | 较高 | 较高 | 较高 | 中等 | 较低 | 中等 |
| **扩展性** | 较好 | 较好 | 较好 | 中等 | 较差 | 中等 |
| **复杂度** | 较高(需设计
Try/
Confirm/
Cancel`) | 中等(需处理消息可靠性) | 较高(事件流跟踪复杂) | 较高(需实现协调器) | 高(需实现两阶段提交) | 更高(需实现三阶段提交) |
一、名词解释
1、XA
协议
XA
协议由X/Open
组织定义,旨在为分布式系统中的事务处理提供一个标准的规范,以确保在多个资源管理器(如数据库、消息队列等)参与的情况下,事务能够原子性地提交或回滚,从而保证数据的一致性。 但在实际应用中,需要根据系统的具体需求和性能要求来权衡是否使用。在一些高并发、对性能要求较高的场景下,可能会选择一些其他的分布式事务解决方案,如最终一致性方案等
X/A
协议。遵循X/A
协议的方案包括二阶段提交协议和基于它改进而来的三阶段提交协议
相关组件:
- 事务管理器(
Transaction
Manager
):负责协调全局事务的执行,包括启动事务、协调资源管理器的准备和提交操作、处理事务的提交或回滚等。 - 资源管理器(
Resource
Manager
):管理具体的资源,如数据库管理系统、文件系统等。它参与事务的执行,根据事务管理器的指令进行准备、提交或回滚操作,并向事务管理器汇报操作结果。 - 应用程序(
Application
Program
):发起事务请求,通过事务管理器来协调资源管理器完成事务操作。
2、BASE
理论
BASE
理论是对CAP
理论的延伸,是一种分布式系统的设计理念,主要用于处理大规模分布式系统中的数据一致性问题。BASE
理论通过牺牲强一致性来换取系统的高可用性和性能,适用于许多大规模分布式系统,如电商平台、社交媒体平台等。这些系统通常需要处理大量的并发请求和海量的数据,对系统的可用性和性能要求较高,而对数据的一致性要求相对较低。
- 基本概念:
BASE
是指基本可用(Basically
Available
)、软状态(Soft
State
)和最终一致性(Eventual
Consistency
)。它强调在分布式系统中,允许存在一定程度的不一致性,以换取系统的高可用性和性能。 - 基本可用:指分布式系统在出现故障时,允许损失部分可用性,但保证核心功能仍然可用。例如,在电商促销活动中,由于流量过大,系统可能会暂时关闭一些非核心功能,如商品评论功能,以保证商品购买等核心功能的正常运行。
- 软状态:允许系统中的数据存在中间状态,并且该状态不影响系统的整体可用性。即系统中的数据在一段时间内可能处于不一致的状态,但最终会达到一致。例如,在分布式数据库中,数据可能会在不同节点之间进行复制和同步,在同步过程中,数据可能处于不一致的软状态。
- 最终一致性:系统中的数据最终会达到一致状态。虽然在数据更新过程中可能会出现短暂的不一致,但随着时间的推移,数据会逐渐趋于一致。例如,在分布式缓存系统中,当数据在一个节点上更新后,其他节点上的缓存数据会在一定时间内被更新,最终达到一致状态。
3、刚性事务(Rigid
Transactions
)
定义:刚性事务是指严格遵循
ACID
(原子性、一致性、隔离性、持久性)原则的分布式事务。这类事务在分布式系统中确保数据的强一致性,即事务执行后,所有节点上的数据状态必须立即保持一致。
- 原子性(
Atomicity
):事务中的所有操作要么全部成功,要么全部失败,不会出现部分执行的情况。 - 一致性(
Consistency
):事务执行前后,系统状态必须保持一致,符合业务规则。 - 隔离性(
Isolation
):事务的执行过程不受其他事务的干扰,多个事务并发执行时,结果与串行执行一致。 -
持久性(
Durability
):事务一旦提交,其结果将永久保存在系统中,即使系统发生故障也不会丢失。 - 应用场景:刚性事务通常用于对数据一致性要求极高的场景,如金融交易、库存管理等,任何数据不一致都会导致严重的业务问题。
- 挑战:在分布式系统中,刚性事务的实现通常需要复杂的协调机制(如两阶段提交,
2PC
),这可能导致性能瓶颈和系统复杂性增加。
4、柔性事务(Flexible
Transactions
)
定义:柔性事务是指在分布式系统中,允许数据在一段时间内存在不一致状态,但最终会达到一致性的分布式事务。这类事务通常遵循
BASE
(基本可用、软状态、最终一致性)原则。
- 基本可用(
Basically Available
):系统在出现部分故障时,仍然能够提供基本的服务,保证系统的可用性。 - 软状态(
Soft State
):系统允许数据在事务执行过程中存在中间状态,即数据可以在不同节点上暂时不一致。 -
最终一致性(
Eventual Consistency
):系统不保证事务执行后立即达到一致性,但经过一段时间后,所有节点的数据会最终达到一致状态。 - 应用场景:柔性事务适用于对一致性要求相对宽松的场景,如社交网络、内容分发、日志处理等。在这些场景中,短暂的数据不一致是可以接受的,系统更注重高可用性和性能。
- 优势:柔性事务通过放宽一致性要求,简化了分布式事务的实现,提高了系统的可扩展性和性能。常见的实现方式包括补偿事务(
Saga
)、消息队列、事件溯源等。
特性 | 刚性事务 | 柔性事务 |
---|---|---|
一致性 | 强一致性(立即一致性) | 最终一致性(允许短暂不一致) |
实现复杂度 | 高(需要复杂的协调机制) | 低(通过异步、补偿等方式实现) |
性能 | 较低(事务协调开销大) | 较高(减少事务协调开销) |
可用性 | 较低(强一致性可能导致系统不可用) | 较高(允许部分故障时继续提供服务) |
适用场景 | 金融交易、库存管理等 | 社交网络、内容分发、日志处理等 |
二、柔性事物-可靠事件模式:
本地事件表 + 消息队列:参考秒杀文档
三、柔性事物-TCC
模式
TCC
(Try
-Confirm
-Cancel
)是一种用于实现分布式事务的补偿型事务模式。它通过将事务拆分为三个阶段(Try
、Confirm
、Cancel
)来保证分布式系统的最终一致性。国内开源的
ByteTCC
、Himly
、TCC-transaction
。TCC
分布式事务方案来保证各个接口的调用,要么一起成功,要么一起回滚,是比较合适的。
TCC
属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC
不太好定义及处理。
1、 TCC
模式的核心思想
TCC
模式通过业务层面的补偿机制来实现事务的最终一致性,而不是依赖数据库的事务特性。它将一个分布式事务拆分为三个阶段:
1)Try
阶段(尝试阶段)
目标:尝试执行业务操作,预留资源,确保后续操作可以完成
操作:执行业务逻辑的检查操作,并预留资源(如冻结账户余额、锁定库存等)。
特点:
Try
阶段的操作必须是幂等的,即多次执行结果相同。-
Try
阶段为后续的Confirm
或Cancel
阶段做准备。 - 示例:在电商系统中,
Try
阶段可以冻结用户的支付金额,并锁定库存。
2)Confirm
阶段(确认阶段)
目标:确认执行业务操作,提交资源,完成事务。
操作:基于Try
阶段的预留资源。所有参与者执行 Confirm
操作,执行业务逻辑的提交操作
- 如果所有
Try
操作成功,进入Confirm
阶段。 - 如果任意
Try
操作失败,进入Cancel
阶段。
特点:
Confirm
阶段的操作必须是幂等的。-
Confirm
阶段一旦执行成功,事务即完成,资源被正式占用。 - 示例:在电商系统中,
Confirm
阶段可以扣减用户的支付金额,并减少库存。
3) Cancel
阶段(取消阶段)
目标:取消业务操作,释放预留资源,回滚事务。
操作:如果 Try
阶段失败或事务需要回滚,所有参与者执行 Cancel操
作,释放资源。
特点:
Cancel
阶段的操作必须是幂等的。-
Cancel
阶段用于在Try
阶段失败或事务需要回滚时释放资源。 - 示例:在电商系统中,
Cancel
阶段可以解冻用户的支付金额,并释放库存。
2、TCC
模式的优点
高一致性:通过业务层面的补偿机制,TCC
模式可以实现分布式事务的最终一致性。
灵活性:TCC
模式不依赖数据库的事务特性,适用于异构系统。
性能优化:Try
阶段只预留资源,不会锁定资源过长时间,提高了系统的并发性能。
可扩展性:适用于复杂的分布式系统,能够处理跨服务的业务逻辑。
3、TCC
模式的缺点和问题
实现复杂:需要在业务层面实现 Try
、Confirm
、Cancel
三个阶段的逻辑,开发成本较高。
幂等性要求:每个阶段的操作必须是幂等的,增加了设计和实现的难度。
业务侵入性:TCC
模式需要业务代码显式支持,对业务逻辑有一定的侵入性。
补偿机制风险:如果 Cancel
阶段失败,可能会导致资源无法完全释放,需要额外的容错机制。
1)Confirm
失败怎么办(Cancel
也一样)
a、Confirm
阶段失败的原因
- 网络问题:网络延迟或中断导致
Confirm
请求无法到达服务。 - 服务故障:参与
Confirm
阶段的服务宕机或不可用。 - 资源冲突:
Confirm
操作所需的资源已被占用或不可用。 - 业务逻辑异常:
Confirm
阶段的业务逻辑执行失败。
b、 Confirm
阶段失败的后果
- 资源未提交:
Try
阶段预留的资源未被正式提交,可能导致资源浪费或数据不一致。 - 事务未完成:事务无法完成,影响业务的正确性。
- 需要回滚:如果
Confirm
阶段失败,需要触发Cancel
阶段以释放预留资源。
c. Confirm
阶段失败的解决方案
1. 重试机制
- 自动重试:在
Confirm
阶段失败后,系统可以自动重试Confirm操作,直到成功或达到最大重试次数。 - 幂等性:
Confirm
操作必须是幂等的,确保多次执行结果相同,避免重复提交资源。 - 定时任务:通过定时任务检查未完成的事务,并尝试重新执行
Confirm
或Cancel
操作。
2. 事务恢复机制
- 事务日志:记录每个事务的状态(
Try
、Confirm
、Cancel
),磁盘或者数据库都可以,以便在失败时恢复。
3. 人工干预
- 报警机制:在
Confirm
阶段失败时,触发报警通知运维人员。 - 手动处理:在自动重试和恢复机制无法解决问题时,运维人员可以手动干预,完成事务或回滚。
4. 回滚机制
- 触发
Cancel
阶段:如果Confirm
阶段失败且无法恢复,可以触发Cancel
阶段,释放Try
阶段预留的资源。 - 补偿事务:设计补偿事务,确保
Cancel
阶段能够正确回滚Try
阶段的操作。
d. Confirm
阶段失败的处理流程
以下是 Confirm
阶段失败时的典型处理流程:
- 确认失败:检测到
Confirm
阶段失败(如超时、异常等)。 - 重试
Confirm
:自动重试Confirm
操作,直到成功或达到最大重试次数。 - 检查状态:如果重试失败,检查事务日志,确认事务状态。
- 触发
Cancel
:如果Confirm
阶段无法完成,触发Cancel
阶段,释放Try
阶段预留的资源。 - 记录日志:记录事务的最终状态(失败或已回滚),以便后续处理。
- 报警通知:如果自动处理无法解决问题,触发报警通知运维人员。
e. Confirm
阶段失败的设计原则
- 幂等性:Confirm操作必须是幂等的,确保多次执行结果相同。
- 可恢复性:设计事务恢复机制,确保在失败时能够恢复事务。
- 容错性:考虑网络、服务故障等异常情况,设计容错机制。
- 监控与报警:实时监控事务状态,及时发现和处理失败情况。
f、示例
以电商系统中的订单支付为例:
Try
阶段:冻结用户账户中的支付金额,并锁定商品库存。
Confirm
阶段:扣减用户账户中的支付金额,并减少商品库存。
Confirm
阶段失败
- 重试:系统自动重试
Confirm
操作,直到成功。 - 回滚:如果重试失败,触发
Cancel
阶段,解冻用户账户中的支付金额,并释放商品库存。 - 报警:如果自动处理无法解决问题,触发报警通知运维人员。
4、TCC
模式与 2PC
的对比
特性 | TCC模式 | 2PC(两阶段提交) |
---|---|---|
一致性 | 最终一致性 | 强一致性 |
实现复杂度 | 高(需实现 Try 、Confirm 、Cancel 逻辑) |
中(依赖数据库的事务特性) |
性能 | 较高(Try 阶段只预留资源) |
较低(资源锁定时间长) |
业务侵入性 | 高(需业务代码支持) | 低(依赖数据库事务) |
5、案例
假设现在有一个电商系统,里面有一个支付订单的场景。那对一个订单支付之后,以下步奏要么一起成功,要么一起失败,必须是一个整体性的事务。
1)案例1
步奏 | TCC 效果 |
---|---|
1、更改订单的状态为“已支付” | [1] 订单服务-修改订单状态 |
2、扣减商品库存 | [2] 库存服务-扣减库存 |
3、给会员增加积分 | [3] 积分服务-增加积分 |
4、创建销售出库单通知仓库发货 | [4] 仓储服务-创建销售出库单 |
a、尝试阶段:Try
订单服务那儿,它的代码大致来说应该是这样子的
步奏 | try 阶段-锁资源 |
具体逻辑 |
---|---|---|
[1] 订单服务-修改订单状态 | 检查订单/修改订单状态 | 状态变更:修改中 |
[2] 库存服务-扣减库存 | 冻结库存 | 1、可销售库存 100-2 = 98 2、冻结库存 = 2 |
[3] 积分服务-增加积分 | 预增积分 | 1、保持积分为 1190 不变2、预增积分 = 10 |
[4] 仓储服务-创建销售出库单 | 预创建出库单 | 创建记录:创建销售出库单,但状态是 UNKNOWN |
b、确认阶段:Confirm
步奏 | Confirm 阶段-锁资源 |
具体逻辑 |
---|---|---|
[1] 订单服务-修改订单状态 | 检查订单/修改订单状态 | 状态变更:已支付 |
[2] 库存服务-扣减库存 | 冻结库存->减库存 | 1、冻结库存变成0 |
[3] 积分服务-增加积分 | 预增积分->生效积分 | 1、保持积分为 1190 + 10 =1200 不变2、预增积分 = 0 |
[4] 仓储服务-创建销售出库单 | 创建出库单 -> 已创建 | 出库单状态:已创建 |
c、取消阶段:Cancel
步奏 | Confirm 阶段-锁资源 |
具体逻辑 |
---|---|---|
[1] 订单服务-修改订单状态 | 检查订单/修改订单状态 | 状态变更:取消 |
[2] 库存服务-扣减库存 | 冻结库存->减库存 | 1、可销售库存 100 2、冻结库存 0 |
[3] 积分服务-增加积分 | 预增积分->生效积分 | 1、保持积分为 1190 不变2、预增积分 = 0 |
[4] 仓储服务-创建销售出库单 | 创建出库单 -> 已创建 | 出库单状态:已取消 |
d、整体流程
四、柔性事物-Saga
模式
Saga
模式是一种用于解决分布式事务问题的柔性事务模式,适用于长事务或跨多个服务的复杂业务流程。与TCC
模式不同,Saga
模式的核心思想是将一个长事务拆分为多个本地事务,每个本地事务执行后提交,并通过补偿事务来处理失败情况。如果某个本地事务失败,Saga
会触发已经提交的事务的补偿事务,回滚之前的所有操作。
Saga
模式是一种适用于分布式系统中长事务的柔性事务模式,通过将事务拆分为多个本地事务,并通过补偿机制实现最终一致性。它适用于对一致性要求较低、跨多个服务的复杂业务流程,但需要仔细设计补偿事务并处理数据不一致的风险。在实际应用中,可以根据业务需求选择编排式或命令式实现方式。
1、Saga
模式的两种实现方式
1)编排式(Choreography
)
无中心协调器:各个服务通过事件驱动的方式参与事务,服务之间通过发布和订阅事件来协调事务的执行。
松耦合:服务之间没有直接依赖,通过事件进行通信。
使用场景:
- 服务数量较少、逻辑简单的场景。
- 对松耦合和事件驱动架构有要求的场景。
a、实现流程
1、事务启动:初始服务(如订单服务)启动事务,执行本地事务(如创建订单),并发布一个事件(如“订单创建成功”)。
2、事件传播
- 其他服务(如库存服务、支付服务)订阅相关事件,并在接收到事件后执行自己的本地事务。
- 例如,库存服务接收到“订单创建成功”事件后,扣减库存并发布“库存扣减成功”事件。
3、事务执行:每个服务依次执行本地事务并发布事件,直到所有服务完成事务。
4、失败处理
- 如果某个服务执行本地事务失败,它会发布一个失败事件(如“库存扣减失败”)。
- 其他服务接收到失败事件后,依次触发补偿事务,回滚之前的操作。
5、事务完成:所有服务成功执行本地事务,事务完成。
b、优点
- 松耦合:服务之间没有直接依赖,通过事件通信。
- 无单点故障:没有中心协调器,系统更加健壮。
c、缺点
- 逻辑分散:事务逻辑分散在各个服务中,难以维护和调试。
- 复杂度高:事件流的跟踪和调试较为复杂。
d、案例
以电商系统为例:
- 订单服务:创建订单,发布“订单创建成功”事件。
- 库存服务:订阅“订单创建成功”事件,扣减库存,发布“库存扣减成功”事件。
- 支付服务:订阅“库存扣减成功”事件,完成支付,发布“支付成功”事件。
- 失败处理:如果支付失败,支付服务发布“支付失败”事件,库存服务和订单服务依次触发补偿事务。
2)命令式(Orchestration
)
有中心协调器:由一个中心协调器(
Orchestrator
)负责协调各个服务的本地事务,并触发补偿事务。集中控制:协调器负责管理事务的整个生命周期。
使用场景:
- 服务数量较多、逻辑复杂的场景。
- 对事务控制要求较高的场景。
a、实现流程
1、事务启动:协调器接收到事务请求,开始协调事务。
2、调用服务
- 协调器依次调用各个服务执行本地事务。
- 例如,协调器首先调用订单服务创建订单,然后调用库存服务扣减库存,最后调用支付服务完成支付。
3、事务执行:每个服务执行本地事务并返回结果给协调器。
4、失败处理:如果某个服务执行本地事务失败,协调器会依次调用之前已成功执行的服务,触发补偿事务,回滚操作。
5、事务完成:所有服务成功执行本地事务,协调器确认事务完成。
b、优点
- 逻辑集中:事务逻辑集中在协调器中,易于维护和调试。
- 可控性强:协调器可以灵活控制事务的执行顺序和失败处理。
c、缺点
- 单点故障:协调器可能成为系统的单点故障。
- 性能瓶颈:协调器可能成为性能瓶颈,尤其是在高并发场景下。
d、案例
- 协调器:启动事务,调用订单服务创建订单。
- 订单服务:创建订单,返回结果给协调器。
- 协调器:调用库存服务扣减库存。
- 库存服务:扣减库存,返回结果给协调器。
- 协调器:调用支付服务完成支付。
- 支付服务:完成支付,返回结果给协调器。
- 失败处理:如果支付失败,协调器依次调用库存服务和订单服务的补偿事务。
3)编排式与命令式的对比
特性 | 编排式(Choreography ) |
命令式(Orchestration ) |
---|---|---|
协调方式 | 事件驱动,无中心协调器 | 中心协调器负责协调 |
耦合度 | 松耦合 | 紧耦合 |
单点故障 | 无 | 有(协调器可能成为单点故障) |
逻辑集中度 | 逻辑分散在各个服务中 | 逻辑集中在协调器中 |
复杂度 | 事件流跟踪和调试复杂 | 事务逻辑集中,易于调试 |
适用场景 | 服务数量较少、逻辑简单的场景 | 服务数量较多、逻辑复杂的场景 |
4)Saga
模式与 TCC
模式的对比
特性 | Saga 模式 |
TCC 模式 |
---|---|---|
一致性 | 最终一致性 | 强一致性 |
事务类型 | 长事务 | 短事务 |
补偿机制 | 补偿事务 | Cancel 阶段 |
开发复杂度 | 较高(需设计补偿事务) | 较高(需实现 Try / Confirm / Cancel ) |
适用场景 | 跨多个服务的复杂事务 | 对一致性要求高的场景 |
无、刚性事物-两阶段提交 2PC
1、工作原理
1)准备阶段(Prepare
)
- 事务管理器向所有参与事务的资源管理器发送 “准备” 指令。
- 资源管理器执行事务操作,并将操作结果记录到本地的事务日志中。如果操作成功,资源管理器向事务管理器返回 “准备成功” 的响应;如果操作失败,则返回 “准备失败” 的响应。
2)提交 / 回滚阶段(Commit
/ Rollback
)
- 事务管理器根据所有资源管理器的响应来决定事务的最终结果。如果所有资源管理器都返回 “准备成功”,事务管理器向所有资源管理器发送 “提交” 指令,资源管理器收到指令后将事务正式提交,持久化数据更改。
- 如果有任何一个资源管理器返回 “准备失败”,或者在等待资源管理器响应过程中出现超时等错误情况,事务管理器向所有资源管理器发送 “回滚” 指令,资源管理器根据事务日志撤销之前执行的事务操作,将数据恢复到事务开始前的状态。
2、优缺点
1)优点:
1、能够严格保证分布式事务的 ACID
特性,即原子性、一致性、隔离性和持久性,确保数据在分布式环境中的强一致性。
2、实现比较简单,尽量保证了数据的强一致
2)缺点:
1、性能开销较大:因为在二阶段提交过程中,需要多次网络通信来协调各个资源管理器,并且在准备阶段需要锁定资源,可能会导致系统的并发性能下降。
2、系统的可用性有一定影响:如果某个资源管理器出现故障或网络分区,可能会导致整个事务的阻塞,影响系统的正常运行。
3、问题
问题1:如果提交阶段,有一个资源管理器返回失败咋整
-
触发回滚操作:事务管理器一旦接收到某个资源管理器提交失败的响应,就会马上判定整个分布式事务执行失败。接着,事务管理器会向所有参与事务的资源管理器发送回滚指令,这些资源管理器包含之前提交成功的资源管理器。
-
资源管理器执行回滚
- 各个资源管理器在收到事务管理器的回滚指令后,会依据之前记录在本地事务日志中的信息,撤销已经执行的事务操作。举例来说,在数据库资源管理器里,会将已执行的插入、更新、删除等操作反向执行,从而把数据恢复到事务开始前的状态。
- 资源管理器完成回滚操作后,会向事务管理器反馈回滚结果。
- 处理异常情况
- 部分回滚失败:在回滚过程中,也许会有部分资源管理器无法成功回滚,这可能是因为系统故障、数据损坏等原因。这时,事务管理器需要持续尝试向这些资源管理器发送回滚指令,或者采用一些补偿机制来处理。
- 网络故障:若在发送回滚指令或者接收回滚结果时出现网络故障,事务管理器需要进行重试,并且要记录日志以便后续排查问题。在重试多次依旧失败的情况下,可能需要人工介入处理。
- 记录错误日志:在回滚过程中,也许会有部分资源管理器无法成功回滚,这可能是因为系统故障、数据损坏等原因。这时,事务管理器需要持续尝试向这些资源管理器发送回滚指令,或者采用一些补偿机制来处理。
4、案例1
1)配置XA
:
ShardingSphere
默认的XA
事务管理器为Atomikos
,在项目的logs
目录中会生成xa_tx.log
, 这是XA
崩溃恢复时所需的日志,请勿删除。
<!-- 分表分库 ShardingShpere -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC2</version>
</dependency>
<!--XA事务必须配置如下,否则如下报错-->
<!--Caused by: java.lang.NullPointerException: Cannot find transaction manager of [XA]-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-xa-core</artifactId>
<version>4.0.0-RC2</version>
</dependency>
2)配置方法
配合
@Transactional
注解使用)
方式一:注解
@ShardingTransactionType(value = TransactionType.XA)
非常抱歉的是,我使用注解没有成功,所以我选择了第二种方式,在进入这个事务方法的时候,用代码控制
@Transactional(rollbackFor = Exception.class)
@ShardingTransactionType(value = TransactionType.XA)
@Override
public void dbTransactional(UserDTO userDTO, CompanyDTO companyDTO) {
System.out.println("----------------开始进入事务");
userService.insert(userDTO);
companyService.insert(companyDTO);
}
方式二:Java
代码
当然可以自己自定义一个注解,用来实现下面的
TransactionTypeHolder.set(TransactionType.XA);
3)正常流程
@Transactional(rollbackFor = Exception.class)
@ShardingTransactionType(value = TransactionType.XA)
@Override
public void dbTransactional(UserDTO userDTO, CompanyDTO companyDTO) {
System.out.println("----------------开始进入事务");
userService.insert(userDTO);
companyService.insert(companyDTO);
}
a、事务方法,刚进入开启事务
public abstract class AbstractConnectionAdapter
extends AbstractUnsupportedOperationConnection {
public final void setAutoCommit(final boolean autoCommit) throws SQLException {
this.autoCommit = autoCommit;
if (TransactionType.LOCAL == transactionType || isOnlyLocalTransactionValid()) {
setAutoCommitForLocalTransaction(autoCommit);
} else if (!autoCommit) {
shardingTransactionManager.begin();//事务管理器开始
}
}
}
public final class XAShardingTransactionManager implements ShardingTransactionManager {
@SneakyThrows
@Override
public void begin() {
xaTransactionManager.getTransactionManager().begin();
}
}
b、事务方法结束的时候,事务管理器提交事务,清除 XAResource
public final class XAShardingTransactionManager implements ShardingTransactionManager {
@SneakyThrows
@Override
public void commit() {
try {
xaTransactionManager.getTransactionManager().commit();//事务管理器提交,实现类为AtomikosTransactionManager
} finally {
enlistedXAResource.remove();
}
}
}
public final class AtomikosTransactionManager implements XATransactionManager {
}
4)异常流程
@Transactional(rollbackFor = Exception.class)
@Override
public void dbTransactional(UserDTO userDTO, CompanyDTO companyDTO) {
System.out.println("----------------开始进入事务");
userService.insert(userDTO);
companyService.insert(companyDTO);
int i = 1 / 0;
}
事务开启和上面正常流程一样,如果发了异常情况,就会回滚,具体执行操作,看下文
public final class ShardingConnection extends AbstractConnectionAdapter {
@Override
public void rollback() throws SQLException {
if (TransactionType.LOCAL == transactionType) {
super.rollback();
} else {
shardingTransactionManager.rollback();
}
}
}
public void rollback() {
try {
try {
this.xaTransactionManager.getTransactionManager().rollback();
} finally {
this.enlistedXAResource.remove();
}
} catch (Throwable var5) {
throw var5;
}
}
5)原理分析
a、Begin
(开启XA
全局事务)
通常收到接入端的
set autoCommit = 0
时,XAShardingTransactionManager
会调用具体的XA事务管理器
开启XA
的全局事务,通常以XID
的形式进行标记。
b、执行物理SQL
ShardingSphere
进行解析/优化/路由后,会生成逻辑SQL
的分片SQLUnit
,执行引擎为每个物理SQL
创建连接的同时,物理连接所对应的XAResource
也会被注册到当前XA事务
中,事务管理器会在此阶段发送XAResource.start
命令给数据库,数据库在收到XAResource.end
命令(个人可以理解为连接试探)之前的所有SQL
操作,会被标记为XA事务
。
XAResource1.start ## Enlist阶段执行
statement.execute("sql1"); ## 模拟执行一个分片SQL1
statement.execute("sql2"); ## 模拟执行一个分片SQL2
XAResource1.end
这里sql1和sql2将会被标记为XA事务。
c、Commit
/rollback
(提交XA事务
)
XAShardingTransactionManager
收到接入端的提交命令后,会委托实际的XA事务管理
进行提交动作,这时事务管理器会收集当前线程里所有注册的XAResource
,首先发送XAResource.end
指令,用以标记此
XA
事务的边界。 接着会依次发送prepare
指令,收集所有参与XAResource
投票,如果所有XAResource
的反馈结果都是OK
,则会再次调用commit
指令进行最终提交,
如果有一个XAResource
的反馈结果为No,则会调用rollback
指令进行回滚。 在事务管理器发出提交指令后,任何`XAResource`产生的异常都会通过`recovery`日志进行重试,来保证提交阶段的操作原子性,和数据强一致性。
XAResource1.prepare ## ack: yes
XAResource2.prepare ## ack: yes
XAResource1.commit
XAResource2.commit
XAResource1.prepare ## ack: yes
XAResource2.prepare ## ack: no
XAResource1.rollback
XAResource2.rollback
六、刚行事物-三阶段提交 3PC
三阶段提交(
3PC
,Three
-Phase
Commit
)是分布式事务协议的一种,是两阶段提交(2PC
,Two
-Phase
Commit
)的改进版本。3PC
通过引入一个预提交阶段(Pre
-Commit
Phase
),减少了2PC
的阻塞问题,提高了系统的容错性和可用性。以下是3PC
的详细介绍:
1、工作原理
3PC
的核心思想是将2PC
的提交过程分为三个阶段:通过引入预提交阶段,3PC
减少了2PC
中参与者长时间阻塞的问题,并提高了系统的容错性。
1)准备阶段(Prepare
Phase
)
准备阶段(
Prepare
Phase
):协调者询问所有参与者是否可以提交事务。
1、协调者向所有参与者发送 准备请求(Prepare
Request
),询问是否可以提交事务。
2、参与者执行本地事务的准备工作(如锁定资源),并记录事务日志。
3、参与者向协调者发送
4、准备响应(Prepare
Response
)
- 如果参与者可以提交事务,返回“同意(Yes)”。
- 如果参与者无法提交事务,返回“拒绝(No)”。
2)预提交阶段(Pre-Commit
Phase
):
预提交阶段(
Pre-Commit
Phase
):如果所有参与者都同意提交,协调者通知所有参与者进入预提交状态。
1、如果所有参与者都返回“同意”,协调者向所有参与者发送 预提交请求(Pre
-Commit
Request
),通知它们进入预提交状态。
2、参与者进入预提交状态,并记录预提交日志。
3、参与者向协调者发送预提交响应(Pre
-Commit
Response
),确认已进入预提交状态。
3)提交阶段(Commit
Phase
)
1、协调者向所有参与者发送提交请求(Commit Request),通知它们提交事务。
2、参与者提交事务,并释放资源。
3、参与者向协调者发送提交响应(Commit Response),确认事务已提交。
2、优缺点&问题
1)优点
- 减少阻塞:通过引入预提交阶段,减少了参与者在准备阶段长时间阻塞的问题。
- 预提交阶段:参与者在预提交阶段确认事务最终会被提交,因此不再需要长时间阻塞。
- 超时机制:如果参与者未收到协调者的提交指令,可以超时后自动提交事务,避免了长时间等待。
- 提高容错性:在协调者或参与者发生故障时,系统可以更好地恢复事务状态。
- 超时机制:在预提交阶段,如果参与者未收到协调者的提交请求,可以超时后自动提交事务。
- 状态恢复:如果协调者或参与者发生故障,系统可以根据事务日志恢复事务状态。
- 增强可用性:参与者可以在超时后自动提交事务,避免长时间等待。
2)缺点
- 复杂度更高:相比
2PC
,3PC
的实现更加复杂,需要更多的状态管理和日志记录。 - 仍然存在单点故障:协调者仍然是系统的单点故障,如果协调者发生故障,事务可能无法完成。
- 性能开销:由于增加了预提交阶段,
3PC
的性能开销比2PC
更大。
3)问题
问题1:为什么 3PC
减少了阻塞
2PC
的阻塞问题
Participant A
和Participant B
在准备阶段都返回“同意”。- 协调者在发送提交请求之前崩溃。
Participant A
和Participant B
会一直等待协调者的指令,无法继续执行事务或释放资源。
3PC
如何解决阻塞
Participant A
和Participant B
在准备阶段都返回“同意”。- 协调者发送预提交请求,
Participant A
和Participant B
进入预提交状态。 - 协调者在发送提交请求之前崩溃。
Participant A
和Participant B
根据预提交状态和超时机制,自动提交事务,避免了长时间等待。
3、3PC
与 2PC
的对比
特性 | 2PC | 3PC |
---|---|---|
阶段数 | 2个阶段(准备、提交) | 3个阶段(准备、预提交、提交) |
阻塞问题 | 参与者在准备阶段可能长时间阻塞 | 通过预提交阶段减少阻塞 |
容错性 | 较低 | 较高 |
复杂度 | 较低 | 较高 |
性能开销 | 较低 | 较高 |
适用场景 | 对一致性要求高的场景 | 对一致性和容错性要求高的场景 |
4、3PC
与 2PC
开源项目
强一致性::选择支持2PC
或3PC
的分布式数据库(如MySQL
、PostgreSQL
、CockroachDB
)。
事务性消息:选择支持 2PC
的消息队列(如 Apache Kafka
、Apache RocketMQ
)。
分布式事务管理:选择支持 2PC
或 3PC
的事务管理器(如 Atomikos
、Narayana
)。
分布式事务框架:选择支持 2PC
或 3PC
的框架(如 Seata
)。
2PC |
3PC |
|
---|---|---|
分布式数据库 | MySQL :支持2PC ,通过 XA 协议实现分布式事务 |
Google Spanner :支持分布式事务,基于2PC 和3PC 的改进版本。 |
分布式数据库 | CockroachDB :支持分布式事务,基于2PC 和3PC 的改进版本。 |
|
消息队列 | Apache Kafka :支持事务性消息,基于2PC 实现 |
|
消息队列 | Apache RocketMQ :支持分布式事务,基于2PC 实现。 |
|
事务管理器- | Atomikos :提供基于2PC 和 XA 协议的分布式事务管理。 |
|
事务管理器- | Narayana: 提供基于2PC 和 XA 协议的分布式事务管理。 |
: |
分布式事务框架 | Seata :支持2PC 的 AT 模式(Auto Transaction ) |
Seata :支持 3PC 的 TCC 模式(Try -Confirm -Cancel )。 |