基于 Saga 的分布式事务调度落地
全文 4104 字,预计阅读时间 12 分钟。
一、背景
随着微服务架构的兴起,越来越多的公司都对自身的业务架构进行了微服务化。在微服务架构中,随着服务的逐渐拆分,数据库的私有化已成为业界不成文的规定。因此伴随着微服务拆分所带来的数据一致性的问题也愈发严重,如何解决该问题成为微服务架构落地过程中一个非常重要的问题。由此我们引出分布式事务这一概念,用来解决上述背景带来的问题。在介绍分布式事务之前先让我们回顾一下什么是事务。
二、事务
事务是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务具有 ACID 四大属性。
A(Atomic):原子性,构成事务的所有操作,要么都执行完成,要么全部不执行,不可能出现部分成功部分失败的情况。
C(Consistency):一致性,在事务执行前后,数据库的一致性约束没有被破坏。比如:数据库约束账户余额必须大于 0,所以设置为无符号数,在此约束下,A 只有 100 元,但要转出 200 元,此时数据库会保持对约束的一致性,触发执行回滚。
I(Isolation):隔离性,数据库中的事务一般都是并发的,隔离性是指并发的两个事务的执行互不干扰,一个事务不能看到其他事务的运行过程的中间状态。通过配置事务隔离级别可以比避免脏读、重复读问题。
D(Durability):持久性,事务完成之后,该事务对数据的更改会持久到数据库,且不会被回滚。
了解完了事务的基本概念,接着让我们看看什么是分布式事务。
三、分布式事务
在分布式系统中,一个应用系统拆分为独立部署的多个服务,因此需要服务与服务之间远程协作才能完成事务操作,这种分布式系统环境下由不同的服务之间通过网络远程协作完成的事务称为分布式事务。
从架构角度出发,分布式事务基本涉及到两大类。
第一类:一个事务请求只涉及单体服务,但是会操作多张数据库表,多个数据库表操作完成才表示最终完成。
第二类:一个事务涉及多个服务,同时每个服务可能连接着一个或者多个数据库,需要协同多个独立的服务访问多个数据存储最终才能完成。
我们常见的分布式事务来保证数据的一致性的方法分为两类:强一致性、最终一致性。
采用强一致性的分布式事务的方案:通常采用两段式提交协议 2PC、三段式提交协议 3PC。在微服务架构中,该种方式不太适合,原因如下:
由于微服务间无法直接进行数据访问,微服务间互相调用通常通过 RPC 或 Http API 进行,所以已经无法使用 TM 统一管理微服务的 RM
不同的微服务使用的数据源类型可能完全不同,如果微服务使用了 NoSQL 之类不原生支持事务的数据库,业务的事务很难实现
即使微服务使用的数据源都支持事务,那么如果使用一个大事务将许多微服务的事务管理起来,这个大事务维持的时间,将比本地事务长几个数量级。如此长时间的事务及跨服务的事务,将为产生很多锁及数据不可用,严重影响系统性能
因此我们一般采用最终一致性来保证分布式系统的一致性。
常见的最终一致性的分布式事务解决方案有:事件通知模式(本地异步事件服务模式、外部事件服务模式、MQ 事务消息模式、最大努力通知模式)、事务补偿模式(Saga、TCC)。我们通过调研上述解决方案总结出了以下特性:
下面我们通过一个业务场景来了解一下什么是分布式事务,并且我们创新行业是用什么方案来解决数据一致性问题的。
四、业务场景
假设有一个积分签到系统,里面有一个签到兑换积分从而兑换物品的功能场景。
我们要做的事情如下:
用户签到成功、增加用户积分
用户创建兑换物品订单,订单状态为已支付
扣除用户积分
扣减物品库存
创建物流出库单
针对以上的业务场景,我们应该如何实现分布式事务来满足业务需要。下面主要讲事务补偿模式来实现分布式事务。
4.1 TCC 模式
TCC 是将整体业务逻辑的每一个事务提交分成了 Try,Confirm,Cancel 三个操作。
Try:完成业务的准备工作
Confirm:完成业务的提交工作
Cancel:完成业务的回滚工作
针对上述业务场景,按照业务背景要做的事情,实现一个 TCC 的分布式事务。
如果要实现一个 TCC 的分布式事务,首先要做的是了解业务的主流程以及各个接口提供的业务含义,不直接完成这个业务操作,而是完成一个 Try(预处理)操作。
例如:给用户添加积分,我们不直接添加积分,先预处理添加积分。扣物品库存我们也不直接扣库存,先冻结将扣掉的库存。具体如下图
如果 Try 的逻辑都成功,TCC 开始执行业务的 Confirm 操作,完成整个事务流程。添加用户积分扣库存等操作。如果 Try 的逻辑部分成功,有部分有问题,那么开始执行 Cancel 操作,撤销之前执行的所有操作。
总体对于 TCC 模式来说,要做一个分布式事务,业务中的一个接口需要完成 3 个逻辑的改造,Try-Confirm-Cancel。
服务调用链路依次执行 Try 逻辑
如果都正常的话,执行 Confirm 逻辑,完成整个业务流程
如果部分服务的 Try 逻辑有问题,会执行 Cancel 逻辑,撤销之前执行的所有操作
TCC 模式对于业务的侵入性比较强,流程比较繁琐。各个业务侧都需要支持升级。对于我们创新行业来说,成本有点大,我们选择了另一种模式来实现分布式事务——Saga 模式。
4.2 Saga 模式
Saga 是一种纯业务补偿模式,其设计理念为,业务在调用的时候正常提交,当一个服务失败的时候,所有其依赖的上游服务都进行业务补偿操作。
Saga 的基本概念:
saga:长事务,long live transaction
每个本地事务有对应的补偿事务
执行情况
正常:T1 -> T2 -> T3 -> … -> Tn
异常:T1 -> T2 -> T3(异常)-> C3 -> C2 -> C1
Saga 两种恢复策略:
backward recovery,向后恢复,补偿所有已完成的事务(回滚操作)
forward recovery,向前恢复,重试失败的事务,假设每个子事务最终都会成功(重试操作)
Saga 事务的优缺点:
优点:模型比 TCC 更简单,只需业务方提供事务执行接口 transaction、事务取消补偿接口 cancel
缺点:直接执行事务执行接口 transaction,可能有副作用(无论是否回滚,都会执行事务接口的逻辑,举例:A 账号向 B 账号转账,T1 事务对 A 用户扣款,T2 事务对 B 用户加款,T1 执行成功,同时产生了一条扣款记录,T2 执行失败需要回滚 T2 和 T1,在这个过程中的副作用是 A 账号能感知到金额变化和扣款记录)
针对上述业务背景,我们对于业务侧只需要支持事务提交的接口( T )和失败补偿的接口( C )即可。具体流程如下图:
对于 Saga 事务来说只有完成跟未完成两种状态。无论是事务全部执行成功,还是全部补偿成功都视为完成状态。出现异常导致流程中断为未完成状态。我们针对于业务提交的流程的 Http Code 状态来区分是执行向前恢复(重试),还是向后补偿(回滚)。对于没有补偿 C 的业务,我们将采取向前恢复,直到成功。对于异常情况,使用离线补偿的方式对未完成的 Saga 事务进行重做,如长时间无法完成将触发报警,人工处理。
我们行创基于上述 saga 模型研发了 Saga 的事务协调器,具体执行流程如下:
业务方使用需要实现对应事务的执行方法和补偿方法,采用上报分布式事务的方式进行操作,事务上报的数据属性简介:
Name:本次流程的名称
OID:请求流程 ID,必须唯一
Process:每个子流程的属性简介(类型为 list,按照顺序执行)
Transaction:执行事务的流程
Call:服务调用的方式,HTTP 调用时传 GET、POST 等
ServiceName:服务名称
ServiceMethod:调用服务的方法名称,HTTP 传请求的 URL
Body:POST body,string 类型
Query:Get Query,map[string]interfase{}
oid:订单 ID, 必传,必须唯一,用于幂等性校验等
Compensate:执行失败的补偿流程
Call:服务调用的方式,HTTP 调用时传 GET、POST 等
ServiceName:服务名称
ServiceMethod:调用服务的方法名称,HTTP 传请求的 URL
Body:POST body,string 类型
Query:Get Query,map[string]interfase{}
oid:订单 ID, 必传,必须唯一,用于幂等性校验等
业务方按上述规则上报数据,该事务会最终保证数据一致性,要么全部成功,要么都失败回滚。
我们创新行业采用 Saga 模型来实现分布式事务,来解决日常微服务拆分带来的数据一致性的问题,满足了我们日常的产品功能的需要,解决了棘手的问题。
五、总结
针对分布式事务的解决方案都有各自的特点,没有一个最优的方案,需要结合实际的应用场景选择合适的模式。
2PC 和 3PC 是一种强一致性事务,不过还是有数据不一致,阻塞等风险,而且只能用在数据库层面。
Saga、TCC 是一种补偿性事务的思想,对业务入侵较大,需要业务方实现对应的方法。
本地消息、事务消息和最大努力通知其实都是最终一致性事务,因此适用于一些对时间不敏感的业务。
推荐阅读:
评论