分布式事务的解决方案

在分布式系统中,事务的ACID特性(原子性、一致性、隔离性、持久性)面临着巨大的挑战。传统的关系型数据库事务在分布式场景下往往难以适用,因此,各种分布式事务解决方案应运而生


两阶段提交(2PC)

  • 两阶段提交是一种分布式事务协议,用于确保分布式系统中的事务一致性。它通过协调参与者节点的操作来实现分布式事务的提交或回滚
  • 在第一阶段(准备阶段),事务协调器向所有参与者节点发送准备请求,并等待它们的响应。参与者节点接收到准备请求后,会执行事务的预提交操作,并将自己的状态(已准备或已中止)返回给事务协调器
  • 在第二阶段(提交阶段),事务协调器根据参与者节点的状态决定最终提交或回滚事务

2PC的缺点

  • 同步阻塞问题:执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态,效率低下。
  • 单点故障:由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
  • 数据不一致:在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这会导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作,但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据不一致性的现象。
  • 不确定性:协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。

示例代码

1
2
3
4
5
6
public abstract class BaseTwoPhaseCommit {
// 提交事务的方法,具体实现由子类完成
public abstract void commit();
// 回滚事务的方法,具体实现由子类完成
public abstract void rollback();
}


三阶段提交(3PC)

  • 三阶段提交是一种分布式事务协议,用于确保分布式系统中的事务一致性。与2PC相比,3PC引入了一个额外的阶段(预提交阶段)来解决2PC的阻塞问题。
  • 在第一阶段(准备阶段),事务协调器向所有参与者节点发送准备请求,并等待它们的响应。参与者节点接收到准备请求后,会执行事务的预提交操作,并将自己的状态(已准备、已中止或未知)返回给事务协调器。
  • 在第二阶段(预提交阶段),事务协调器检查所有参与者节点的状态,并根据情况决定是继续进行提交还是回滚操作。
  • 在第三阶段(提交阶段),事务协调器根据参与者节点的响应决定最终提交或回滚事务

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class TransactionCoordinator {
private List<Participant> participants; // 参与者列表

public TransactionCoordinator(List<Participant> participants) {
this.participants = participants; // 初始化参与者列表
}

public boolean executeTransaction() {
// 阶段1:准备
for (Participant participant : participants) {
boolean prepared = participant.prepare(); // 让每个参与者准备事务
if (!prepared) {
// 如果有参与者没有准备好,那么回滚事务
rollback();
return false;
}
}

// 阶段2:预提交
boolean canCommit = true;
for (Participant participant : participants) {
CommitState commitState = participant.preCommit(); // 让每个参与者预提交事务
if (commitState == CommitState.ABORTED) {
// 如果有参与者预提交失败,那么回滚事务
canCommit = false;
break;
} else if (commitState == CommitState.UNKNOWN) {
// 处理未知状态(可选)
}
}

// 阶段3:提交或回滚
if (canCommit) {
for (Participant participant : participants) {
participant.commit(); // 让每个参与者提交事务
}
} else {
rollback(); // 回滚事务
}

return canCommit;
}

private void rollback() {
for (Participant participant : participants) {
participant.rollback(); // 让每个参与者回滚事务
}
}
}

2PC和3PC之间对比

  • 性能方面的比较:
    • 2PC: 2PC具有简单直接的特点,只包含准备阶段和提交阶段两个阶段,因此在理论上看起来性能较高。但是,2PC在准备阶段需要协调者等待所有参与者的响应,这可能导致阻塞,从而降低了整体的性能表现。
    • 3PC: 3PC相较于2PC引入了一个额外的预提交(PreCommit)阶段,这一阶段在某些情况下可以减少阻塞。预提交阶段使得参与者在确认能够提交事务之前,不会阻塞协调者的请求。这种方式可以提高系统的并发性能,减少阻塞的可能性,但也会引入额外的通信开销。
  • 容错能力的对比:
    • 2PC: 2PC存在单点故障的风险,如果协调者在提交阶段发生故障,可能导致参与者无法确定事务的最终状态,从而需要采取额外的措施来处理异常情况。
    • 3PC: 3PC相较于2PC具有更好的容错能力。引入了预提交阶段之后,即使在协调者在这一阶段发生故障,参与者仍然可以继续执行事务的提交或回滚操作,从而降低了整个系统因单点故障而出现的风险。
  • 适用场景的区别:
    • 2PC: 2PC适用于对一致性要求较为严格的场景,例如金融交易等。由于它的简单性,2PC更容易实现和部署,适用于需要快速搭建分布式事务处理的场景。
    • 3PC: 3PC适用于对性能要求较高、同时对一致性要求也较高的场景。虽然3PC引入了额外的阶段,但它可以在一定程度上提高系统的并发性能,同时保证了事务的一致性,因此适用于对系统性能和一致性要求都比较高的场景。


补偿事务(TCC)

TCC事务是一种基于补偿机制的分布式事务解决方案。它包括Try、Confirm和Cancel三个阶段。在Try阶段,进行业务检查和资源预留;在Confirm阶段,如果Try阶段成功,则进行事务确认提交;在Cancel阶段,如果Try阶段失败,则进行事务回滚


示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SagaTransaction {
private List<TransactionStep> steps; // 事务步骤列表

public SagaTransaction() {
this.steps = new ArrayList<>(); // 初始化事务步骤列表
}

public void addStep(TransactionStep step) {
steps.add(step); // 添加事务步骤
}

public void execute() {
for (TransactionStep step : steps) {
try {
step.execute(); // 执行每个事务步骤
} catch (Exception e) {
step.rollback(); // 如果执行失败,那么回滚事务
break;
}
}
}
}

  • 适用场景:TCC框架适用于需要强一致性保证的分布式事务,且参与事务的资源管理器(如数据库)支持XA协议。TCC框架基于两阶段提交(2PC)协议,适合于跨多种数据库或消息队列等资源的分布式事务。
  • 优点:TCC框架可以提供跨多种资源的强一致性事务保证。TCC框架的第一阶段(执行业务SQL并自动提交)不涉及全局事务协调器的参与,因此可以更快地完成事务操作,释放数据库资源,从而提高性能。
  • 缺点:TCC框架的实现相对复杂,需要编写和维护compensating和confirming操作,以及确保正确的状态迁移。TCC框架的事务具有软状态,确认和取消操作可能出现问题,需要考虑如何处理失败情况以保证最终一致性


Saga

  • Saga是一种分布式事务解决方案,它适用于业务流程长、业务流程多的场景,特别是针对参与事务的服务是遗留系统服务。此类服务无法提供TCC模式下的三个接口,就可以采用Saga模式。其适用于的业务场景有,金融机构对接系统 (需要对接外部系统)、渠道整合 (流程长)、分布式架构服务等。
  • Saga模式是通过状态机来实现的,它使用状态图定义服务调用流程并生成json状态语言定义文件,状态图的节点可以是一个服务,也可以是补偿节点。
  • Saga事务有两种恢复策略:向前恢复 (forward recovery),也就是“勇往直前”。对于执行不通过的事务,会尝试重试事务,这里有一个假设就是每个子事务最终都会成功。这种方式适用于必须要成功的场景。向后恢复 (backward recovery),在执行事务失败时,补偿所有已完成的事务,是“一退到底”的方式。
  • Saga的实现会依赖于消息队列或事件总线来解耦服务间的调用,并通过分布式事务框架来管理补偿逻辑和事务状态,例如Seata等。考虑到网络分区和服务故障的可能性,确保补偿逻辑的幂等性也非常重要,这样即使在迫使补偿操作重试的情况下,也不会引起不一致的状态。
  • Saga有两种协调模式,编排 (Choreography)和控制 (Orchestration)。编排模式是一种去中心化的模式,参与者之间通过消息机制进行沟通,通过监听器的方式监听其他参与者发出的消息,从而执行后续的逻辑处理。控制模式则是由Saga提供一个控制类,其方便参与者之前的协调工作。事务执行的命令从控制类发起,按照逻辑顺序请求Saga的参与者,从参与者那里接受到反馈以后,控制类在发起向其他参与者的调用。所有Saga的参与者都围绕这个控制类进行沟通和协调工作。

Saga的三种事务类型

  • 可补偿性事务:所谓可补偿性事务,也就是可以使用、需要使用补偿事务来回滚数据的事务,比如说下订单,就需要删除订单的补偿事务,因此下订单就是一个可补偿性事务
  • 关键性事务:关键性事务是saga执行的关键点,如果关键性事务运行成功,则saga将一直运行到完成。关键性事务不一定是个可补偿性事务或者可重复性事务,但是他可以是最后一个可补偿的事务或第一个可重复的事务
    • 我们知道关键性事务的定义从结构上理解,是处于可补偿性事务和可重复性事务的中间。
    • 具体把哪个事务定义为关键性事务,还要根据具体的业务情况而定,我们可以通过以下标准来判断
      • 从结构上是否处于可补偿事务和可重复事务之间
      • 从业务上该事务是否能表示整个业务执行成功的转折点
  • 可重复性事务
    关键性事务之后的事务就是可重复性事务,不需要回滚,并且保证能够执行完成。所以我们会通过一些机制来保证这类事务一定能执行成功,比如重试机制
image-20240630195541206

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SagaTransaction {
private List<TransactionStep> steps; // 事务步骤列表

public SagaTransaction() {
this.steps = new ArrayList<>(); // 初始化事务步骤列表
}

public void addStep(TransactionStep step) {
steps.add(step); // 添加事务步骤
}

public void execute() {
for (TransactionStep step : steps) {
try {
step.execute(); // 执行每个事务步骤
} catch (Exception e) {
step.rollback(); // 如果执行失败,那么回滚事务
break;
}
}
}
}


本地消息表

本地消息表是一种通过在本地数据库中记录消息状态来实现分布式事务的方法。其核心思想是将业务操作和消息记录放在同一个本地事务中,确保它们要么同时成功,要么同时失败。然后,通过一个独立的消息调度器异步地将消息发送到消息队列中,从而实现跨服务的事务一致性


示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 定义消息表的数据结构
public class Message {
private String id; // 消息的唯一标识
private String content; // 消息的内容
private String status; // 消息的状态
private Date createTime; // 消息的创建时间
private Date updateTime; // 消息的更新时间
}

// 在业务操作中,将消息记录插入本地消息表
public class BusinessService {
@Transactional
public void doBusinessOperation() {
// 执行业务操作...

// 创建消息记录
Message message = new Message();
message.setId(generateUniqueId());
message.setContent(generateMessageContent());
message.setStatus("NEW");
message.setCreateTime(new Date());
message.setUpdateTime(new Date());

// 将消息记录插入本地消息表
messageTable.insert(message);
}
}

// 消息调度器,用于异步发送消息
public class MessageScheduler {
public void sendMessage() {
// 从本地消息表中获取未发送的消息
List<Message> messages = messageTable.getUnsentMessages();

for (Message message : messages) {
// 将消息发送到消息队列
messageQueue.send(message);

// 更新消息的状态为已发送
message.setStatus("SENT");
message.setUpdateTime(new Date());
messageTable.update(message);
}
}
}

  • 适用场景:本地消息表框架适用于需要异步更新数据,并且对数据的实时性要求较低的场景。
  • 优点:本地消息表框架建设成本比较低,实现了可靠消息的传递确保了分布式事务的最终一致性。
  • 缺点:本地消息表框架与业务耦合在一起,难于做成通用性,不可独立伸缩。本地消息表框架只适用于最终一致的业务场景。


Seata

Seata是由阿里巴巴开源的一款分布式事务解决方案。它提供了一套完整的分布式事务解决方案,包括事务管理、事务协调和分布式锁等功能。Seata能够保证分布式环境下的事务一致性和数据一致性,大大简化了分布式系统的开发和维护工作

官网:http://seata.io/zh-cn/

下载地址:https://github.com/seata/seata/releases


Seata的三大角色

  • TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。
  • TM (Transaction Manager) - 事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。
  • RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

Seata提供了AT、TCC、SAGA和XA四种事务模式,可以根据业务需求选择合适的模式

  • AT模式
    • AT模式使用起来对代码的侵入性很小,能够快速地使项目具备分布式事务的能力。Seata的AT模式是两阶段提交协议的演变:一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。二阶段:提交异步化,非常快速地完成。回滚通过一阶段的回滚日志进行反向补偿。在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作
  • TCC模式
    • TCC(Try-Confirm-Cancel)是一种基于补偿机制的分布式事务解决方案框架,通过预备和提交两个阶段来保证事务的一致性
  • SAGA模式
    • Saga模式是通过状态机来实现的,它使用状态图定义服务调用流程并生成json状态语言定义文件,状态图的节点可以是一个服务,也可以是补偿节点
  • XA模式
    • XA模式是一个经典的分布式事务协议,通过两阶段提交(2PC)协议实现分布式系统中的事务管理和一致性

Seata的设计亮点

相比与其它分布式事务框架,Seata架构的亮点主要有几个:

  • 应用层基于SQL解析实现了自动补偿,从而最大程度的降低业务侵入性
  • 将分布式事务中TC(事务协调者)独立部署,负责事务的注册、回滚
  • 通过全局锁实现了写隔离与读隔离

Seata具体的启动使用过程可以参考http://t.csdnimg.cn/Ac2LS


参考资料

https://cloud.tencent.com/developer/article/1995289?shareByChannel=link

http://t.csdnimg.cn/kVB8o

http://t.csdnimg.cn/PGr24

https://www.cnblogs.com/lukairui/p/17444189.html

http://t.csdnimg.cn/v2ozm

http://t.csdnimg.cn/NCqW7

http://t.csdnimg.cn/Ac2LS


分布式事务的解决方案
https://lzhengjy.github.io/2024/06/14/分布式事务的解决方案/
作者
Zheng
发布于
2024年6月14日
许可协议