本文是基于SAGA分布式事务论文的实现。
1、缩略语和关键术语定义
MQ(Message Queue,消息队列):在消息的传输过程中保存消息的容器。
ACID(Atomicity原子性、Consistency一致性、Isolation隔离性、Durability持久性的缩写):指数据库管理系统在写入或更新资料的过程中,为保证事务是正确可靠的,所必须具备的四个特性。
BASE(Basically Available基本可用、Soft State软状态、Eventually Consistent最终一致性的缩写):是指分布式应用无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。
2、背景技术方案
2.1 详细介绍背景技术方案
事务是指由一个或多个资源管理操作构成的操作序列,它具有原子性、一致性、隔离性和持久性,即ACID特征。分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。对一致性时效要求不是特别高的场景,通常使用柔性事务(满足BASE特征)替代严苛的刚性事务(满足ACID特征),即不追求数据的强一致性保证其最终一致性。随着当前的数据库几乎都已支持事务,这使得单个服务节点具备事务能力(即本地事务)已逐渐成为标配。目前许多分布式柔性事务都是基于本地事务,主要代表为消息事务方式。
消息事务方式是将业务数据和本地事务状态共同存放到本地数据库,同时提供本地事务状态的查询接口。请求方先预提交请求,然后执行本地事务,事务成功则再次提交确认,MQ将消息传递给订阅方,若事务执行失败MQ将丢弃预提交请求。下图所示为消息事务的流程,具体步骤如下:
S101:消息请求方先将请求提交到MQ,但MQ暂不能投递该消息,需要收到发送方对该消息的二次确认才会投递。这种被标记成“暂不能投递”状态的消息,又叫半消息,通常采用RocketMQ来处理这种特殊消息;
S102:MQ向消息发送方确认消息是否接收成功;
S103:消息请求方收到S102的确认后,执行本地事务,更新本地事务状态为成功或失败;
S104:若本地事务执行成功,则再次向MQ确认提交,反之则告知MQ已提交的消息需要回滚;
S105:若MQ未收到S104步骤的消息,将回查消息请求方的本地事务状态查询接口,判断事务执行成功还是失败;
S106:消息请求方查询本地事务执行状态,根据事务状态选择提交确认还是回滚;
S107:将提交确认或回滚消息发送给MQ;
S108:若是提交确认,MQ将消息投递给消息订阅方;
S109:若是回滚消息,MQ将S101预提交的消息丢弃。
从以上步骤可知,消息事务其实是通过本地事务来保证分布式事务的,且依赖具备半消息处理能力的RocketMQ。从示意图可以看到,本地事务数据和业务数据是耦合在一起,且由服务节点各自管理,一旦本地事务数据丢失或不可用,整个事务将无法回滚。
2.2 本技术要解决的技术问题
1)、该分布式事务方案依赖特殊的MQ,不具备很好的通用性和移植性。
2)、事务数据与业务数据高耦合,致使事务的独立自治变得困难。
3)、事务一旦失败就立即回滚,缺乏合理的重试机制。
3、技术的创新点
1)、设计了一种分布式事务协调管理器,不依赖MQ特性,对事务数据独立自治,通过插拔式的接口实现通用性。
2)、设计了一种基于决策矩阵的事务调度算法。
3)、通过合理重试失败事务,提升分布式事务的执行成功率。
4、本技术方案及有益效果
4.1 分布式事务协调管理装置
本技术设计了一种独立于服务节点、应用自治的插拔式分布式协调管理装置。对任意具有N个服务节点(编号从1到N)的分布式系统,每个服务节点都对应着一个本地事务和一个事务补偿操作,所有本地事务构成完整的分布式事务,即:
以上公示中的为全局分布式事务,表示第i个服务节点的本地事务及其对应的补偿操作。补偿操作本质就是事务回滚,对于具有本地事务的节点来说是天然具备的,因为在数据库层面事务与回滚几乎都能同时保证。即将节点的回滚操作封装到接口中,可由外部调用触发。
图1 可回滚和重试的分布式事务协调管理器
图1所示为本技术设计的分布式协调管理器(以下简称管理器),当用户请求进入服务节点后,会经过事务拦截器(如图2所示),拦截器将事务上报给管理器,节点执行完本地事务后,拦截器再次上报执行状态,整个过程对业务处理是无感的。当分布式中的某个节点事务执行失败,经管理器的决策算法判断是下发回滚还是重试指令,若是回滚会不断触发调用链上的节点进行回滚。其中,分布式节点的事务状态都由管理器存储、加工和计算,实现事务数据与业务数据的松散耦合。
图2为事务拦截器的示意图,根据事务处理的事前、事中和事后原则进行设计,每个步骤的含义为:
S201:事前步骤,服务节点接受到服务请求,将事务初始化信息上报管理器;
S202:事中步骤,该步骤为业务处理,执行对应的本地事务;
S203:事后步骤,本地事务执行完成后,拦截器将本地事务状态上报管理器。
图2 事务拦截器装置结构图
4.2 分布式事务协调管理过程
在上一节的装置结构基础上,本节主要介绍管理器的执行过程。图3所示为本技术的分布式事务协调管理过程:
图3 分布式事务协调管理执行过程
S301:服务节点在接入统一的分布式事务管理之前,需要向管理器注册事务节点,提供本地事务的补偿方法接口;
S302:管理器为该节点创建一个记录其事务状态的事务表,并生成全局唯一的事务ID值,该ID值与事务表名一一对应,目的是避免多节点共用同一张事务表出现性能问题。同时,管理器还会为补偿方法生成对应的补偿ID;
S303:管理器创建事务表成功后,将对应的事务ID值分配给服务节点,至此完成事务管理服务的注册过程;
S304:当服务请求进入后,拦截器上报事务的初始状态,上传信息包括:当前服务节点ID、调用方服务节点ID、补偿接口ID;
S305:管理器收到上报的事务后,根据唯一事务ID寻址对应的事务表,在事务表中插入一条初始化的事务数据,同时根据服务节点ID和调用方服务节点ID关系,利用Zipkin算法[1]生成分布式调用链路;
S306:服务节点的本地事务执行步骤;
S307:将本地事务执行的状态(成功或失败)上报管理器;
S308:通过管理器的调度算法决策,决定事务前向重试或后向补偿,同时更新对应事务流水的状态;
S309:若本地事务是执行成功的,则流程将顺利执行到下一个服务节点;
S310:若本地事务执行状态为失败,经决策需要后向补偿,根据管理器生成的分布式调用链,从该节点开始,执行后向事务补偿,若需要重试,则执行事务重试;
S311:执行本地事务补偿,若补偿失败会不断重试直到成功,该步骤由人工干预兜底保证。如果是事务重试,则会重试一定次数再决定是否启动补偿。
S312:本地事务补偿成功后将结果上报管理器;
S313:管理器根据调用链,调度上一个服务节点,传送事务回滚的指令,重复S310-S312步骤直到所有补偿都完成则流程结束;
S314:本地事务重试成功,上报事务结果;
S315:流程顺利执行到下一个服务节点。
以上步骤可以总结为,服务节点要接入管理器,首先需要注册节点事务ID,有了这个ID则表示管理器将该节点的事务纳入统一管理。服务节点侧,当请求进入服务节点后,通过事务拦截装置先上报事务,节点执行完事务后再将执行结果上报。管理器侧,通过上报的事务执行结果决策整个事务应该回滚还是有限次数的局部重试,最终实现分布式事务的统一管理。
本技术以APP用户注销场景为例,将操作案例贯穿整个流程进行说明。假设有这样一个APP,用户注销账户时,须将用户的积分兑换为话费充值到用户的手机号,完成积分兑换清零后,才注销用户状态及删除必要的用户信息。具体的分布式服务流程如图4所示:
图4 APP用户注销分布式调用流程
如图4中所示,注销流程共有5个分布式服务节点,用户发起的请求调用链按时间顺序为:[服务节点编号#5]-> [服务节点编号#4]-> [服务节点编号#3]-> [服务节点编号#2]-> [服务节点编号#1],而分布式服务执行是调用链的逆过程,即图中所示的顺序。下面是对应流程:
- 为每个服务节点注册一个事务ID。比如充值话费服务节点,注册后由管理器分配的事务ID为0002,并为该节点创建事务表,本案例中事务表自动命名规则为:tx前缀+服务节点ID+事务ID。因此服务节点2的事务表名为:tx_2468_0002,该方式不仅保证了事务表的全局唯一性,管理器在路由寻表时也非常简单高效。
- 服务节点向管理器提供服务补偿接口,管理器为其生成补偿接口ID,如节点3积分清零服务,提供了积分复原补偿接口。管理器为其生成的事务补偿ID为:0003001,即按事务ID+自增序号规则生成。
- 用户点击注销账户,服务从左向右逐级驱动,以节点3积分清零服务为例,若服务成功,则管理器更新事务表tx_3089_0003中该条请求的状态为成功,流程走到节点4用户信息状态注销,假设该步骤失败,管理器更新事务表tx_3124_0004中该条请求状态为回滚中,同时调用节点4的回滚接口:0004001,若回滚成功成功(用户状态激活)则更新状态为补偿成功,并向节点3发送回滚指令。
- 当节点4失败时,若经过决策矩阵判断可以重试3次,且重试过程中完成了服务,则流程顺利推进到节点5客户端本地用户表信息删除,若节点5执行成功,则完成账户注销流程。
以上即为分布式事务协调管理的宏观过程,通过APP账户注销流程进行了演示说明。
4.3 分布式事务调度算法
本技术还设计了一种提高分布式事务成功率的事务调度算法,它内置于管理器装置,算法流程如图5所示。
图5 分布式事务调度算法流程图
假设分布式系统有N个服务节点,用i表示服务节点编号。流程开始后管理器监听节点i上报的事务,标记其事务状态为开始。节点i开始执行本地事务:
- 若节点i的本地事务执行成功,管理器记录节点i的事务状态为成功,继续执行下一个节点(i=i+1),当所有节点都执行成功(i=N),流程结束;
- 若节点i的本地事务执行失败,则通过决策矩阵进行决策:
- 若决策结果为重试,则继续监听节点i并下发重试指令,该步骤对应图中的P1前向重试模块,该模块通过有限次数的合理重试,避免任务一失败就立即回滚,从而提升整个事务的成功率。
- 若决策结果为开启事务补偿,管理器记录节点i的状态为补偿开始,并下发补偿指令,节点i开始执行自己的补偿操作,若执行成功,更新节点i的事务状态为补偿成功,并调度下一个节点(i=i-1),直到所有节点都已完成补偿(i=0),整个分布式事务得以回滚。当然,补偿操作不一定一次就能执行成功,如果补偿失败,需要系统自动重试,如果重试超过配置的次数,需加入告警,由人工干预来保证节点的补偿执行成功。整个步骤对应图中的 P2后向补偿模块。
在以上调度算法中,决策矩阵是一个重要的创新点,本技术中提出的决策矩阵的设计如图6所示:
图6 决策矩阵
在决策矩阵中,第一列表示节点事务执行失败时的要素,第二、三列表示对应的选择:回滚或重试,第三、四列为重试时的可选方案和对应的可配置的最大重试次数(R1-R5)。重试最大次数R1-R5通常是根据实际生产运行监控数据总结出的经验值。决策矩阵中的各项内容为:
- 当出现业务层的参数错误时,这种场景通常重试是无法完成服务的,因此及时回滚并抛出异常,让用户根据错误纠正输入才是更加明智的做法;
- 写数据库失败,对于多实例的服务,可以将请求转发到同构化的节点上帮忙处理,也可以在本地节点定时重试;
- 读数据库失败,对于主从结构的数据库,通常遵循“写主读从”的模式,如果当前的从节点读数失败,可自动切换读取其他从节点,直到读取到相同的数据副本,也可以本地节点定时重试;
- 网络异常,首先采用本地节点重试,如果重试一定次数后仍不成功,可将请求转发到其他节点,不断轮询可提供服务的节点;
- 服务超时,除了实例宕机这种特殊情况外,更可能的情况是请求积压导致超负荷,此时如果继续在当前节点重试,只能增大负荷,情况会更加恶化。因此可以采用指数退避(Exponential Backoff)方式重试,即重试的时间间隔呈指数增长。若仍然失败,可转发到同构的服务节点去尝试处理,但在负载均衡正常的情况下,其他节点的请求量也不一定少,因此只能作为一种备选方案去尝试;
- 当前节点无补偿接口时,若流程跨过该节点后一旦出现回滚,将在此处出现回滚断点,整个分布式回滚将失败。因此分布式链路上如果存在无补偿接口的节点,优先考虑提供补偿接口,若不能提供,服务在该节点及其之前发生失败时,都应该选择回滚。比如图4中节点2充值话费服务,所依赖的第三方接口如果不支持撤销支付,一旦流程越过节点2则无法回滚到节点1。因此在节点1或节点2执行失败,都应该优先回滚;
- 调用链路均有补偿接口的情况,若失败节点在调用链路上的位置已经过半,此时优先选择重试,可以是本地重试也可以是转发重试。比如用户注销时,执行流程在节点4失败,由于此时执行链路已经过半,应优先选择重试;
- 如果该服务节点允许有损服务,可选择本地重试也可以服务降级通过。比如用户注销时,目的是将用户的积分兑换为话费为用户充值,以提高用户对APP的好感度,如果话费充值成功但积分清零步骤失败,由于可通过用户状态来判断未清零的积分是否有效,因此该失败步骤可选择降级通过,从而提升成功率。
4.5 分布式事务装置
本技术的分布式事务装置的全局结构如图7所示,每个模块的功能为:
图7 分布式事务协调管理装置结构图
S401:监听单元,管理器负责监听服务节点上报事务的功能单元;
S402:通信接口单元,负责管理器与分布式集群之间的数据通信;
S403:事务上报单元,负责事务执行前后的拦截上报;
S404:用户接口,提供给服务节点的插拔式接口;
S405:事务指令执行单元,接受到管理器的事务执行指令(补偿或重试)后,由该单元负责执行;
S406:指令单元,管理器根据决策结果,生成调度指令,对应的指令集如图8所示。指令集共有六种指令,覆盖了回滚/重试当前节点、回滚/重试指定服务节点、停止回滚/重试一定的时间,指令的使用示例如表中最后一列所示。
图8 分布式事务调度指令集
以用户注销场景为例,当节点2充值话费服务失败,管理器发送的指令如下:
- {“cmd”:“ROLLBACK”},管理器根据请求中携带的节点ID(2468),指定节点2的所有补偿接口都执行回滚,即回滚当前节点事务;
- {“cmd”:“RETRY”},管理器根据请求中携带的节点ID(2468),指定节点2重试,即重试当前节点;
- {“cmd”: “ROLLBACK_WITH_SERVICE_ID”,“service_id”: “2468”,“rollback_id”:[“0002001”,“0002002”]},管理器指定节点2执行撤销充值(0002001)和撤销支付(0002002)补偿操作。该命令可以细化补偿接口的粒度,通过rollback_id列表来指定必要的补偿操作;
- {“cmd”: “RETRY_WITH_SERVICE_ID”,“service_id”: “2468” },管理器指定节点2468执行重试;
- {“cmd”: “PAUSE_RETRY_SECOND”,“time”: “60” },该命令用于控制重试时间,避免超时。比如用户注销时前端提示10分钟内完成注销,但是由于节点重试时间太久,导致注销超时。为了避免这一场景,需要限定重试允许的最大时间;
- {“cmd”: “PAUSE_ROLLBACK_SECOND”,“time”: “60” },该命令用于指定回滚允许的最大时间,如果超时则先告知用户注销失败,然后通过定时任务来兜底完成回滚。
S407:事务注册器,管理器中负责服务节点事务管理注册的单元;
S408:处理器,指中央处理器CPU,负责管理器的计算任务;
S409:调度引擎,内置了本技术的调度算法,负责实时决策事务节点的调度任务;
S410:存储器,用于管理器中的数据存储,包括但不限于关系型数据库、非关系型数据库以及磁盘日志文件。
通过上述的管理过程、调度算法、管理装置、调度指令等一系列的技术设计,可以实现一套可方便移植、应用自治、成功率高的分布式柔性事务解决方案。
5、最优方案的有益效果
(1)通过本技术的分布式协调管理器,可以实现事务处理与业务处理的解耦,这种插拔式的管理器技术栈无关,具有通用性。
(2)通过本技术的分布式事务调度算法,能提升分布式事务的成功率。
(3)从服务节点单独管理事务数据,变为由管理器统一管理,避免了节点管理的事务数据不可用时全局事务的不可回滚。
6、本技术的替代方案
(1)本技术的分布式事务协调管理器,它是基于服务节点具有事务补偿能力,因此还有一种替代方案:
如果分布式系统中存在没有事务补偿能力的服务节点,为了提高分布式事务的执行成功率,在服务编排时,应尽量将具有本地事务补偿能力的节点安排在前面。对于没有本地事务补偿能力的节点,管理器可采用数据库快照方式(数据量最小化),记录节点数据库业务执行前的状态,生成回滚日志(即undo log)。当需要回滚没有事务补偿能力的节点时,由管理器代为补偿执行。
(2)对于本技术中使用的事务拦截器,可用过滤器或java 中的切面技术代替。
(3)对于决策矩阵中“调用链路节点均有补偿接口,且链路已过半”情况,对应的条件为i>N/2,其中i为节点编号(1到N),N为服务节点个数。除了i>N/2外,还可以根据后续节点的可用率均值进行调整,如果后续节点的可用率均值都比较高,条件可放宽到i>N/3、i>N/4等,依次类推。
7、参考文献
[1] jcchavezs. Openzipkin分布式链路生成算法[EB/OL]. https:// github.com/openzipkin/zipkin,2018-1-25/2022-02-02.
[2] https://microservices.io/patterns/data/saga.html