分布式事务,一文帮你讲透!

嗨,你好啊,我是猿java

在微服务化的时代,分布式事务是一个重要的技术点,这篇文章我们将深入的分析分布式事务。

什么是事务?

事务(Transaction)是数据库管理系统(DBMS)中用于确保数据一致性和可靠性的一组操作。

事务具有以下四个关键特性,通常称为 ACID特性:

  • 原子性(Atomicity):事务中的所有操作要么全部成功,要么全部失败。原子性确保事务是一个不可分割的单元,任何部分的失败都会导致整个事务的回滚,恢复到事务开始前的状态。
  • 一致性(Consistency):事务执行前后,数据库必须从一个一致状态转换到另一个一致状态。任何事务的执行都不能破坏数据库的完整性约束。
  • 隔离性(Isolation):并发执行的事务之间互不干扰,一个事务的中间状态对其他事务不可见。隔离性确保事务在并发执行时不会产生冲突。
  • 持久性(Durability):一旦事务提交,其结果是永久性的,即使系统崩溃也不会丢失。持久性通过将事务日志写入持久性存储设备来实现,例如磁盘。

什么是分布式事务?

分布式事务是指在分布式系统中,跨越多个独立的节点(例如,不同的数据库、微服务、存储系统等)进行的事务操作。分布式事务的目标是确保这些节点上的操作能够像单个事务一样,满足ACID(原子性、一致性、隔离性、持久性)特性,确保系统在面对网络分区、节点故障等复杂环境时,数据仍然保持一致。

分布式事务解决方案

二阶段提交

原理

二阶段提交(2PC, Two-Phase Commit)协议分为两个阶段:

  1. 准备阶段(Prepare Phase):协调者向所有参与者发送准备请求,参与者执行事务并将状态记录到日志中,但不提交。
  2. 提交阶段(Commit Phase):如果所有参与者都准备好了,协调者向所有参与者发送提交请求,参与者提交事务;如果有任何参与者未准备好,协调者发送回滚请求,参与者回滚事务。

整个过程如下图所示:

img

优点

  • 简单:实现相对简单,适用于小规模的分布式系统。
  • 一致性:可以确保数据的一致性。

缺点

  • 性能瓶颈:协调者成为单点瓶颈,影响系统性能。
  • 阻塞问题:如果协调者崩溃,参与者会一直等待,导致系统阻塞。
  • 脑裂风险:网络分区或节点故障可能导致数据不一致。

三阶段提交

原理

三阶段提交(3PC, Three-Phase Commit)协议是在二阶段提交的基础上,增加了一个阶段以减少阻塞风险:

  1. 准备阶段(CanCommit Phase):协调者向所有参与者发送准备请求,参与者返回是否可以准备。
  2. 预提交阶段(PreCommit Phase):如果所有参与者都可以准备,协调者发送预提交请求,参与者执行事务并记录状态,但不提交。
  3. 提交阶段(DoCommit Phase):协调者发送提交请求,参与者正式提交事务。

整个过程如下图所示:

img

优点

  • 减少阻塞:比二阶段提交更少的阻塞风险。
  • 更高的容错性:在网络分区和故障情况下更健壮。

缺点

  • 复杂性增加:实现复杂度增加。
  • 性能开销:多了一个阶段,性能开销增大。

本地消息表

本地消息表(Local Message Table)是一种用于实现分布式事务的设计模式,特别适用于需要跨多个系统或服务进行数据一致性保证的场景。该模式通过将消息记录到本地数据库中的消息表中,确保消息的可靠传递和处理,从而实现最终一致性。

工作原理

本地消息表的基本原理是将业务操作和消息发送操作结合在一个本地事务中,确保这两个操作要么同时成功,要么同时失败。具体步骤如下:

  1. 业务操作与消息记录:在一个本地事务中,执行业务操作(如数据库更新、插入等)并将需要发送的消息记录到本地消息表中。
  2. 提交事务:如果业务操作和消息记录都成功,提交事务。否则,回滚事务。
  3. 消息发送:一个独立的消息发送程序(通常是一个定时任务或后台线程)定期扫描本地消息表,将未发送的消息发送到消息队列或目标系统。
  4. 消息确认:目标系统处理消息后,发送确认回执,消息发送程序根据回执更新本地消息表中的消息状态,标记为已发送或已处理。

优点

  1. 简化分布式事务:通过将业务操作和消息记录放在一个本地事务中,避免了复杂的分布式事务协调问题。
  2. 提高可靠性:即使系统崩溃或网络中断,消息记录仍然保存在本地消息表中,保证消息不会丢失。
  3. 最终一致性:通过异步消息发送和重试机制,确保系统最终达到一致状态。

缺点

  1. 实现复杂度:需要额外的消息表、消息发送程序和消息确认机制,增加了系统的复杂性。
  2. 延迟问题:由于消息发送是异步的,可能会引入一定的延迟,导致一致性不是即时的,而是最终一致性。
  3. 幂等处理:目标系统需要处理消息的幂等性,确保重复消息不会导致数据不一致。

实现步骤

1.创建消息表

创建一个本地消息表,用于记录需要发送的消息。消息表通常包含以下字段:

  • 消息ID(唯一标识消息)
  • 消息内容(实际要发送的消息)
  • 消息状态(未发送、已发送、已确认等)
  • 创建时间和更新时间

2.业务操作与消息记录

在业务操作的同时,将需要发送的消息记录到本地消息表中,确保这两个操作在同一个本地事务中执行。

1
2
3
4
5
6
7
8
9
10
11
BEGIN TRANSACTION;

-- 执行业务操作
UPDATE account SET balance = balance - 100 WHERE account_id = 1;
UPDATE account SET balance = balance + 100 WHERE account_id = 2;

-- 记录消息到本地消息表
INSERT INTO local_message_table (message_id, message_content, message_status, create_time)
VALUES ('msg-123', 'Transfer 100 from account 1 to account 2', 'PENDING', NOW());

COMMIT;

3.消息发送程序

实现一个独立的消息发送程序,定期扫描本地消息表,将未发送的消息发送到消息队列或目标系统。

4.消息确认机制

实现目标系统的消息确认机制,处理完消息后发送确认回执,消息发送程序根据回执更新本地消息表中的消息状态。

整个过程如下图所示:

img

适用场景

本地消息表模式特别适用于以下场景:

  1. 跨系统或服务的数据一致性:需要确保多个系统或服务之间的数据一致性,如订单系统与库存系统的同步。
  2. 异步操作:需要将某些操作异步化,以提高系统的响应速度和吞吐量。
  3. 高可靠性要求:需要确保消息不丢失,即使在系统故障或网络中断的情况下。

基于消息的最终一致性

原理

基于消息的最终一致性(Message-based Eventually Consistent),通过消息队列实现异步操作,确保最终一致性。事务操作被拆分为多个步骤,通过消息队列进行协调,保证每一步最终都能成功。

优点

  • 高性能:异步操作减少了同步等待时间。
  • 高可用性:没有单点故障,系统更具弹性。

缺点

  • 一致性延迟:不能保证强一致性,只能保证最终一致性。
  • 复杂性:需要处理消息的幂等性和重复消费问题。

TCC

原理

TCC(Try-Confirm/Cancel)模式将事务分为三个步骤:

  1. Try阶段:尝试执行事务,预留资源。
  2. Confirm阶段:确认执行事务,真正提交。
  3. Cancel阶段:取消事务,释放预留资源。

整个过程如下图所示:

img

优点

  • 灵活性高:可以根据业务需求自定义事务逻辑。
  • 性能较好:相比2PC和3PC,阻塞时间短。

缺点

  • 实现复杂:需要业务系统支持并实现Try、Confirm和Cancel逻辑。
  • 代码入侵式:需要业务系统支持并实现Try、Confirm和Cancel逻辑,有代码入侵。
  • 一致性风险:需要严格管理资源预留和释放,避免资源泄露。

Saga模式

原理

Saga模式将长事务拆分为一系列有序的小事务,每个小事务都有对应的补偿操作。如果某个小事务失败,则按顺序执行补偿操作。

Saga 整个过程如下图所示:

img

优点

  • 高可用性:没有全局锁,系统更具弹性。
  • 易于扩展:适合微服务架构。

缺点

  • 一致性较弱:只能保证最终一致性。
  • 补偿逻辑复杂:需要设计和实现补偿操作。

总结

分布式事务的实现方式各有优缺点,选择哪种方式取决于具体的业务需求和系统架构设计。对于要求强一致性的场景,2PC和3PC是常见选择,但需要权衡性能和可用性。对于追求高性能和高可用的系统,基于消息的最终一致性、TCC和Saga模式更为适用,但需要业务系统支持和复杂的补偿逻辑。

学习交流

如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注公众号:猿java,持续输出硬核文章。

drawing