消息一致性方案是通过消息中间件保证上下游应用数据操作的一致性。基本思路是将本地操作和发送消息放在一个本地事务中,保证本地操作和消息发送要么两者都成功或者都失败。下游应用向消息系统订阅该消息,收到消息后执行相应操作。
消息最终一致方案从本质上讲是将分布式事务转换为两个本地事务,然后依靠下游业务的重试机制达到最终一致性。基于消息的最终一致性方案对应用侵入性也很高,应用需要进行大量业务改造,成本非常高。
入侵代码的方案是基于现有情形“迫不得已”才推出的解决方案,实际上它们实现起来非常不优雅,比如TCC,一个事务的调用通常伴随而来的是对该事务接口增加一系列的反向操作,提交逻辑必然伴随着回滚的逻辑,这样的代码会使得项目非常臃肿,维护成本高。
针对上面所说的分布式事务解决方案的痛点,很显然,我们理想的分布式事务解决方案肯定是性能要好而且要对业务无侵入,业务层无需关心分布式事务机制的约束,做到事务与业务分离,也就是本文所重点推荐的非侵入事务。
非侵入事务方案
a. 典型架构
非侵入事务典型架构如下图所示:
事务核心组件包括:
Transaction Coordinator (TC): 事务协调器,分布式事务大脑,产生和维护全局事务、分支事务,推进事务提交与回滚的二阶段处理。TC Server以集群形式提供事务协调能力。
Transaction Manager (TM): 定义全局事务的边界,与事务协调器通信以开启、提交或回滚全局事务。
Resource Manager (RM): 资源管理器,管理分支事务处理的资源,与事务协调器通信以开启、结束事务分支,并接收事务协调器指令完成二阶段分支事务提交或回滚。
Lock Server (LS): 分布式锁服务器,可以通过它对进行中的分布式事务所操作的资源查询、加锁、放锁。
一个分布式事务称为一个全局事务,下面挂若干个分支事务,一个分支事务是一个满足 ACID 的本地事务。非侵入事务的核心思想是资源管理器拦截业务SQL,对其解析并做额外的一些数据处理,产生undo log并保存,一旦发生全局事务回滚,通过各个分支事务对应的undo log完成所有分支事务回滚。
大家很容易想到,两个全局事务并行修改了相同数据,可能会造成根据undo log完成回滚产生数据错误。解决的方法是通过Lock Server对事务所修改数据加锁,全局事务提交后立即放锁,全局事务回滚则等待分支事务回滚完成放锁。
b. 典型流程
典型分布式事务主要执行步骤如下:
TM请求TC开始新的全局事务,TC创建全局事务并返回全局事务ID(XID)。
根据XID构建事务上下文,通过微服务的调用链传播。
RM发现自己处于事务上下文,得到全局事务ID并解析SQL,产生undo log和分布式事务锁数据,请求TC创建分支事务。
TC 通过LS加锁,加锁成功后创建分支事务ID并返回。
RM 把分支事务ID与undo log关联,与业务原始SQL在一个本地事务内提交。
重复3~5,为全局事务范围内的每个本地事务创建一个分支事务。
如果全局事务边界内没有任何异常,则TM请求TC提交全局事务;如果有异常,则TM请求TC回滚全局事务。
TC标记全局事务状态,如果为提交则立即通过LS放锁。推进XID所对应全局事务下的所有分支事务进行二阶段处理,发送请求到RM。
RM完成分支事务的提交或回滚,并返回状态到TC。
TC对完成回滚的分支通过LS放锁。所有分支完成后,返回全局事务处理结果到TM。
二阶段事务处理比较关键,在此重点说明一下。
c. 分支事务提交
如果全局事务状态为提交,则对每个分支发起分支提交,流程如下图所示: