微服务架构技巧篇:分布式事务
你好,我是华仔。
上一节我们讲解了微服务架构设计中“服务分布式”带来的技术挑战,以及各种应对技巧。这一节,我们将继续讲解如何应对微服务架构另一个特点“数据分布式”带来的技术挑战。
“数据分布式”带来的技术挑战,本质上都可以归因于原来可以在单个关系数据库上完成的复杂操作,因为微服务拆分后就没法用了。
行业技术老兵应该都见过“存储过程”这种典型的面向数据库的开发模式。我曾见过长达几百上千行的存储过程,无论是看代码、调试、定位,还是修改、重用、移植等,对技术人员来说都是地狱级的难度。即便有的企业给出了存储过程使用的各种规范和限制,但只要没有严格的审核和监督,开发人员就会逐步滥用存储过程。很多时候,单次开发用存储过程确实很方便和快速,某些场景下性能也更高,因为不需要从数据库读取数据到应用程序中,处理后又将数据写入数据库。
当单体或者SOA系统拆分成微服务架构后,单个微服务职责简化,管理的数据范围缩小,微服务之间只能通过接口访问,可以说从根源上改变了复杂存储过程生存的土壤,而简单的存储过程相比代码来说没有什么优势,所以微服务中使用存储过程其实没有必要。
微服务架构虽然不需要“存储过程”这种开发模式了,但这也带来了一个新的技术挑战:多个微服务如何保证数据一致性。接下来,我们按照不同的拆分模式简单分析一下。
第一种模式:各个微服务还继续使用关系数据库。即便如此,原来单个关系数据库系统上简单方便的事务模式也不可行了。
第二种模式:各个微服务根据数据特点使用不同的数据存储系统。例如用户微服务使用MongoDB,交易微服务使用MySQL。这种模式下,很明显更不可能实现传统单个关系数据库上满足ACID要求的那种事务了。
应对微服务“数据一致性”挑战的核心方案就是“分布式事务”和“全局幂等”。这一节我将为你讲解“分布式事务”,下一节再来讲解分布式事务依赖的关键技术“全局幂等”。
业务级分布式事务
分布式事务是指在分布式系统中,为了保证数据的一致性,跨多个独立的资源管理器(如数据库、微服务等)执行一系列操作时所使用的事务。在微服务架构中,为了区分数据库层面的分布式事务(例如XA事务),我们把微服务层面实现的分布式事务称为“业务级分布式事务”。
业务级分布式事务可以基于接口调用来实现,也可以基于消息队列来实现。业界经过长时间的摸索和实践,已经逐步形成了四种成熟的业务级分布式事务模式:本地事务消息、MQ事务消息、TCC和SAGA。接下来我们分别讲解。
本地事务消息
本地事务消息的英文叫做Transaction Outbox,顾名思义就是在本地记录一条事务相关的消息,然后通过事务消息推动分布式事务的协作和执行。其基本原理如下图:
结合上图我们可以看到,本地事务消息方案的正常处理流程分4步。
第1步,A服务作为分布式事务的发起者,先进行正常的业务逻辑处理,然后启动一个本地数据库事务,在事务中写入业务数据(如上图1.1步骤)。A服务在同一个事务中完成业务数据的写入后,写入一条事务消息到本地的事务消息表(如上图1.2步骤)。
事务消息一般包括事务全局ID、子事务ID、事务操作(commit或rollback)、事务发起时间和事务的关键参数(例如用户ID、交易ID)以及事务执行状态等。
第2步,A服务本地事务执行成功后,可以通过接口调用或者消息队列的方式,将上面步骤中记录的事务消息发给需要执行分布式事务的B服务。
第3步,B服务收到A服务的事务消息后,读取事务消息中的关键信息然后执行本地事务,同样在一个事务中既写入业务数据,又写入事务消息数据(如上图3.1和3.2步骤)。
第4步,B服务执行完本地事务后,通过接口或者消息队列返回执行结果给A服务。A服务收到B服务的执行结果后,更新本地事务消息表中的状态。
对于以上流程,常见的异常处理逻辑如下:
- 如果步骤2失败,A服务需要重试发送事务消息。
- 如果步骤4失败,A服务没有收到B服务事务处理的结果,A服务也会重试发送事务消息。
- 步骤3中,B服务收到事务消息后首先要判断本地事务消息表是否有对应的事务消息,如果有的话说明之前已经执行过了,B服务直接读取事务消息的状态然后返回给A服务;如果没有则说明是第一次收到事务消息,正常执行事务即可。
- 如果A服务决定回滚整个事务,则可以读取本地事务消息表中事务消息记录的相关信息,向B服务发送rollback事务消息。
总的来说,本地事务消息方案由于记录了分布式事务执行所需要的相关信息,在故障恢复和人工订正的时候非常方便。不足之处在于异常处理分支多,整体逻辑比较复杂。
MQ事务消息
Apache RocketMQ实现的分布式事务消息功能,是在普通消息基础上支持二阶段的提交能力,将二阶段提交和本地事务绑定,实现全局提交结果的一致性。其设计的初衷是由MQ来完成事务消息的处理流程,以降低事务消息的实现复杂度和业务开发的难度,提供高性能、可扩展、易使用的事务消息方案。
RocketMQ事务消息的基本原理如下:
结合上图,具体的处理步骤可以分7步。
- Sender(事务发起者)发送一条特殊的Half消息给Apache RocketMQ服务端。
- RocketMQ将消息持久化成功之后,向生产者返回Ack确认消息已经发送成功,此时消息被标记为“暂不能投递”,这种状态下的消息即为半事务(Half)消息。
- Sender开始执行本地事务。
- Sender根据本地事务执行结果向RocketMQ提交二次确认结果:Commit 或 Rollback。如果RocketMQ一直没有收到Sender的二次确认结果,则会回查事务状态(如图中4.x步骤)。
- RocketMQ收到二次确认结果后,如果是Commit,RocketMQ将半事务消息标记为可投递,并投递给Subscriber(分布式事务执行者);如果是Rollback,RocketMQ将回滚事务,不会将半事务消息投递给Subscriber(如图中5.x步骤)。
- Subscriber收到事务消息后执行本地事务。
- Subscriber根据事务执行结果提交消费结果,包括事务执行成功或者失败。
需要注意的是,RocketMQ事务消息保证本地主分支事务和下游消息发送事务的一致性,但不保证消息消费结果和上游事务的一致性。因此需要下游业务分支自行保证消息正确处理,需要消费端做好消费重试,如果是短暂失败可以利用重试机制保证最终处理成功;如果因为异常导致消费一直失败,则可能需要人工处理。
TCC
TCC是应用层面符合2PC协议的分布式事务解决方案,全称为Try-Confirm-Cancel,其具体含义如下:
- Try:资源预留,对业务资源检查并预留。
- Confirm:确认操作,对业务处理进行提交,即commit操作。只要Try成功,该步骤就一定成功。
- Cancel:取消操作,对业务处理进行取消,即rollback操作,该步骤会释放Try预留的资源。
TCC的基本原理如下:
结合上图,具体的处理步骤如下:
- 主业务应用准备发起分布式事务,向事务协调器申请启动一个事务。
- 主业务应用分别调用下游多个服务的Try接口,预留或者锁定需要保证一致性的相关资源。
- 主业务应用根据多个Try接口调用的结果,判断分布式事务是提交还是回滚,然后向事务协调器发出“提交”或者“回滚”的请求。
- 事务协调器调用下游多个服务的Confirm接口提交分布式事务,或者调用Cancel接口回滚分布式事务。
总的来说,TCC是一种侵入式的分布式事务解决方案,Try/Confirm/Cancel三个操作都需要业务系统实现且对外提供接口,对业务系统有非常大的入侵性,设计相对复杂。优点是TCC完全不依赖数据库,能够实现跨存储系统、跨应用资源管理。对这些不同数据访问,通过侵入式的编码方式实现一个原子操作,更好地解决了各种复杂业务场景下的分布式事务问题。
SAGA
SAGA可以算是业务级分布式事务方案中的“元老”,SAGA理论出自Hector Garcia Molina和Kenneth Salem于1987年发表的论文《SAGAS》,全称是Sequence of Atomic Transactions for Global Applications(全局应用的原子事务序列)。它是一种用于处理分布式事务的模式,主要用于解决复杂分布式系统中长事务(Long - Running Transactions)的问题。
SAGA本质上是一种补偿协议,它将一个长事务拆分成一系列的子事务,每个子事务都有对应的补偿事务。这些子事务可以看作是一个个独立的本地事务,它们按照一定的顺序依次执行。其基本原理如下图所示:
分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败(如上图的T3),那么分布式事务会退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。
SAGA模式非常适合处理复杂的、包含多个步骤的业务流程。它将长事务分解为多个小的、易于管理的子事务,使得开发人员可以针对每个子事务和其补偿事务进行单独的设计和实现,在业务需求发生变化或者需要增加新业务环节时,很容易对事务序列进行修改和扩展。
小结
这一节课,我们重点讲解了业务级分布式事务常用的4种模式。其中,本地事务消息方案虽然不依赖第三方,但整体实现比较复杂。而MQ事务消息方案由RocketMQ提供分布式事务的处理机制,简化了业务开发的难度和复杂度。TCC模式可以支持跨异构存储系统实现分布式事务,但是对应用的侵入很大。SAGA模式是分布式事务的“元老”,适合长事务的分布式一致性处理。
为了更好地掌握分布式事务方案的实践技术细节,我推荐你关注开源方案Apache Seata,它是一款由阿里巴巴开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务,提供了AT、TCC、SAGA和XA事务模式,非常适合全面、深入学习和应用。
思考题
以上就是今天的全部内容,最后留一道思考题给你吧:微服务之间为什么不通过数据库层面的分布式事务协议XA来实现分布式一致性?
欢迎你把答案写到留言区,和我一起讨论。相信经过深度思考的回答,也会让你对知识的理解更加深刻。
- vooc 👍(5) 💬(1)
微服务之间为什么不通过数据库层面的分布式事务协议 XA 来实现分布式一致性? 优点:业务无侵入,数据库支持比较广泛 缺点:阻塞时间长,性能差
2025-01-02 - return 👍(1) 💬(1)
“Confirm:确认操作,对业务处理进行提交,即 commit 操作。只要 Try 成功,该步骤就一定成功。” 请教华哥, 只要 Try 成功, Confirm 一定成功, 这个怎么理解。 如果Try 成功后, 这个机器正好挂了,后面 Confirm 就不行吧。
2025-01-07