一文搞定常见分布式事务实现

前言

一文带你理解什么是分布式事务 - 掘金中我们介绍了什么是分布式事务,这篇文章会深入介绍市面上常见的分布式事务实现方案,并探讨它们的优缺点,以及可能会产生的问题。

分布式事务的关键点

● 创建事务一定要一个唯一主键,一般是事务ID,然后基于这个事务ID,关联一些事务信息的存储和各个子任务

● 需要一个协调者来负责跟踪推进完整个事务,然后各参与者需要遵从一定的规范约束,基于事务ID,以幂等、对账能力为基础,实现相应的API

● 一致性要求高的场景,会有对资源做锁定或预留的做法,最终一致性要求的场景,则只需要符合预期即可。基于对资源要求的不同,会有一些常见的解决方案,例如多阶段协商提交、TCC、事务消息等

分布式事务常见解决方案

强一致性的几种方案

XA分布式事务协议(2PC)

XA分布式事务协议,大概分为两部分:事务管理器和本地资源管理器

其中本地资源管理器往往由数据库实现,比如Oracle、DB2都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。

在分布式事务中,涉及到多个独立的资源管理器和一个事务管理器,XA协议允许事务管理器协调多个资源管理器,以确保分布式事务的一致性和原子性

XA的基本原理是把分布式事务分解成两阶段提交的过程

● 第一阶段(Prepare阶段):事务管理器向所有的资源管理器发送Prepare请求,并等待它们的响应。资源管理器接收到Prepare请求之后,会检查是否能够执行该事务,并返回响应的准备状态。如果所有的资源管理器都返回正确响应,那么进入到第二阶段

● 第二阶段(Commit):事务管理器向所有的资源管理器发送Commit请求,并等待它们的响应。资源管理器接收到对应的Commit请求之后,会根据之前的准备状态执行事务的提交操作,并返回相应的提交状态。如果所有的资源管理器你都返回提交状态,那么分布式事务就被提交成功。

在这个过程中出错了怎么办?

我们可以探讨以下几种情况

  1. 第一阶段某资源管理器出错:该资源无法响应事务管理器的Prepare请求,事务管理器会在一段时间之后重发请求,如果在达到最大重试次数之后仍然无法正确响应,那么事务管理器会把该资源管理器标记为失败,进而终止整个事务
  2. 第二阶段某资源管理器出错:该资源无法响应事务管理器的Commit请求,事务管理器会在一段时间之后重发请求,如果在达到最大重试次数之后仍然无法正确响应,那么事务管理器会把该资源管理器标记为失败,会向其他所有的资源管理器发送Rollback请求进行数据回滚,保障数据的一致性
  3. 事务管理器出错:一般事务管理器是整个XA分布式事务策略的性能瓶颈,一旦出错,可能会导致整个分布式事务的终止;我们一般会采用备份和冗余的方式来保持事务管理器的高可用性,如果事务管理器出现故障,可以由备份的事务管理器接管工作

XA的优点是简单易用,应用广泛。缺点主要还是在性能上,可能有单点故障,而且需要阻塞所有的资源管理器。也有一定的可能造成数据不一致,即第二阶段完成之后,因为网络原因只有一部分参与者进行了commit操作

3PC三阶段提交

3PC:Three-phase Commit Protocol 是在2PC之上的扩展的提交协议,主要是为了解决两阶段提交协议的阻塞问题,从原来的两个阶段拓展为三个阶段,增加了超时机制。相比于2PC更加健壮和高效

3PC包含三个阶段:

● 准备阶段(CanCommit Phase):在准备阶段,事务管理器会向所有参与者发送CanCommit请求,询问它们是否可以进行事务提交。参与者在收到请求后,根据自身状态会做出对应的响应,可以是Yes/No

● 预提交阶段(PreCommit Phase):在预提交阶段,事务管理器根据收集到的所有参与者的响应来决定是否可以进行事务的预提交。当所有参与者都回送了Yes,那么此时协调者会向所有的参与者发送PreCommit请求以进行事务的预提交;参与者收到请求后会执行事务的预提交操作,并且将预提交状态返回给协调者

● 提交阶段(DoCommit Phase):在提交阶段,协调者根据收集到的所有参与者的预提交状态来决定是否最终提交事务。如果都为成功,协调者会向所有参与者发送DoCommit请求,表示最终提交事务;如果有任何参与者预提交状态为失败,那么则会发送DoAbort请求,表示事务终止

3PC较于2PC的优点在于引入了预提交阶段,以及在提交阶段引入了DoAbort请求。这样就可以在准备阶段就能知道是否有参与者不同意提交,避免了2PC中某个参与者失败导致的全阻塞问题,减少阻塞时间,提高分布式事务的效率和可用性

DTS方案

阿里有一个分布式事务框架DTS,用来保障在大规模分布式环境下事务的最终一致性。DTS从架构上分为xts-client和xts-server两部分,前者是一个签入客户端应用的JAR包,主要负责事务数据的写入和处理;后者是一个独立的系统,主要负责异常事务的恢复

最终一致性

TCC分段提交

实现分布式事务,最常用的方法就是二阶段提交协议和TCC,这两个算法的使用场景是不一样的,二阶段提交协议实现的是数据层面的事务 ,比如XA规范采用的就是二阶段提交;TCC实现的是业务层面的事务,比如当操作不仅仅是数据库操作,还涉及其他业务系统的访问操作时,就该考虑TCC了

TCC是一个分布式事务的处理模型,将事务拆解成Try Confirm Cancel三个步骤 ,在保证强一致性的同时,最大限度提高了系统的可伸缩性和可用性,又称为补偿事务。它的核心思想是针对每个操作都要注册一个与其对应的确认操作和补偿操作

确认操作和补偿操作必须是幂等的,因为这两个操作可能会失败重试。TCC不依赖于数据库的事务,而是在业务中实现了分布式事务,这样能减轻数据库的压力,一个业务操作需要实现Try、Confrim和Cancel的三个方法,对业务代码的侵入性也更强,实现的复杂度也越高。

本质上是一种乐观锁的方式进行的分布式事务实现,通过三个阶段的操作来确保分布式事务的一致性,而不像2PC那样使用阻塞的方式来实现

TCC的三个阶段分别是

● Try:在Try阶段,协调者尝试预留所有参与者需要的资源,相当于一种试探机制,事务发起者会检查所有参与者的资源是否可用,并进行资源的预留。如果资源都可用,协调者会执行正常的业务操作,但并不提交事务

● Confirm:在Confirm阶段,协调者会向所有的参与者发出确认请求,要求提交事务。各个参与者会真正执行之前预留的业务操作,并将操作结果提交

● Cancel:如果任何一个参与者在Confirm阶段失败,或者在指定时间内没有收到Confirm请求,协调者会向所有参与者发出Cancel请求,参与者进行事务的回滚操作,取消之前的预留操作

与2PC的区别

  1. 阻塞与非阻塞:2PC是阻塞的,在准备阶段和提交阶段都可能阻塞;TCC是非阻塞的,在预留资源字段并不提交事务,只有所有资源都预留成功后才提交确认,这样能够提高事务执行效率
  2. 使用场景:基于乐观锁的TCC更适合业务的一致性,2PC更适合保证数据的一致性;且TCC的可用性要高于2PC

MQ实现分布式事务

MQ方案也称为非事务消息,这种方式比较常见,一个是由于市面上很多这种成熟的非事务消息的解决方案,一个是由于这些MQ的性能和吞吐量都比较好,可以满足大部分的业务场景

一个典型的流程如下就是:生产者先执行本地事务并将消息落库,状态标记为待发送,然后发送消息。如果发送成功,则将消息改为发送成功;如果发送失败则不修改标记

然后会起一个定时任务,定是从数据库捞取在一定时间内待发送的消息并将消息发送。为确保消息一定能消费,消费者一般采用手动ACK机制,并且最好需要支持幂等

在消费者端可能面临的问题是

1.消费者消费到消息之后,消费者要保证对应的业务操作要执行成功之后才能主动ACK。如果业务执行失败,消息不能失效或者丢失,这个可以用消息队列的持久化机制+备份的思想进行解决

2.消费者消费消息要能够在业务层面保持幂等,因为消费可能会失败,因此只有具有幂等性才能不影响业务,具体的方案可以采用唯一主键来解决

SAGA长流程分布式事务

SAGA用于处理有序的一长串的长流程的事务,相对来说,性能更好,没有资源锁定,无流程阻塞,但是不保证事务间的隔离性和原子性,需要业务侧根据需要处理可能的问题

SAGA的每个子事务都有一个补偿的接口,如果执行到某个阶段失败之后,则对已经成功的子事务按照栈顺序一次进行补偿操作。采用的就是将一个分布式事务拆成多个小的本地事务的思想,来保证分布式系统中的数据一致性。每个步骤都是一个本地事务,每个本地事务都是幂等的,即使在失败和重试的情况下,也不会产生额外的影响

Saga模式通常由以下几个步骤组成

● 发起者:事务发起者,负责启动和协调整个Saga事务的执行

● 局部事务:一个小的事务,每个局部事务对应一个本地操作或者服务

● 补偿:每个局部事务都有一个对应的补偿操作,用于回滚或者撤销之前的操作,如果一个局部事务失败,Saga将执行补偿操作来回滚已经执行的操作

● 协调器:负责控制整个Saga事务的执行流程,包括局部事务的提交和回滚

Saga分布式事务的优缺点

可靠性较强,而且将一个大的事务分成多个本地小事务,更加灵活;同时,Saga需要设计每个本地事务的逻辑和对应的回退操作,更加复杂,也需要精确的定义每个步骤的执行顺序,增加系统的复杂性

结语

本文介绍了强一致性与最终一致性实现分布式事务的几种方案,其中最为推荐的是SAGA长流程实现。

创作不易,如果有收获欢迎点赞、评论、收藏,您的支持就是我最大的动力。

相关推荐
凌冰_5 分钟前
IDEA2023 SpringBoot整合MyBatis(三)
spring boot·后端·mybatis
码农飞飞14 分钟前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货15 分钟前
Rust 的简介
开发语言·后端·rust
monkey_meng1 小时前
【遵守孤儿规则的External trait pattern】
开发语言·后端·rust
Estar.Lee1 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
新知图书2 小时前
Rust编程与项目实战-模块std::thread(之一)
开发语言·后端·rust
盛夏绽放2 小时前
Node.js 和 Socket.IO 实现实时通信
前端·后端·websocket·node.js
Ares-Wang2 小时前
Asp.net Core Hosted Service(托管服务) Timer (定时任务)
后端·asp.net
uzong3 小时前
7 年 Java 后端,面试过程踩过的坑,我就不藏着了
java·后端·面试
一只爱撸猫的程序猿3 小时前
简单实现一个系统升级过程中的数据平滑迁移的场景实例
数据库·spring boot·程序员