Seata 意为:Simple Extensible Autonomous Transaction Architecture,是一套一站式分布式事务解决方案,提供了 AT、TCC、Saga 和 XA 事务模式,本文详解其中的 Saga 模式。
项目地址:https://github.com/seata/seatahtml
本文做者:屹远(陈龙),蚂蚁金服分布式事务核心研发 。java
分布式系统有一个比较明显的问题就是,一个业务流程须要组合一组服务。这样的事情在微服务下就更为明显了,由于这须要业务上的一致性的保证。也就是说,若是一个步骤失败了,那么要么回滚到之前的服务调用,要么不断重试保证全部的步骤都成功。---《左耳听风-弹力设计之“补偿事务”》git
而在金融领域微服务架构下的业务流程每每会更复杂,流程很长,好比一个互联网微贷业务流程调十几个服务很正常,再加上异常处理的流程那就更复杂了,作过金融业务开发的同窗会颇有体感。github
因此在金融分布式应用开发过程当中咱们面临一些痛点:spring
咱们接触到的大多数业务(好比在渠道层、产品层、集成层的系统),为了保障业务最终一致性,每每会采用“补偿”的方式来作,若是没有一个协调器来支持,开发难度是比较大的,每一步都要在 catch 里去处理前面全部的“回滚”操做,这将会造成“箭头形”的代码,可读性及维护性差。或者重试异常的操做,若是重试不成功可能要转异步重试,甚至最后转人工处理。这些都给开发人员带来极大的负担,开发效率低,且容易出错。数据库
业务实体不少、实体的状态也不少,每每作完一个业务活动后就将实体的状态更新到了数据库里,没有一个状态机来管理整个状态的变迁过程,不直观,容易出错,形成业务进入一个不正确的状态。express
服务的幂等性是分布式环境下的基本要求,为了保证服务的幂等性每每须要服务开发者逐个去设计,有用数据库惟一键实现的,有用分布式缓存实现的,没有一个统一的方案,开发人员负担大,也容易遗漏,从而形成资损。apache
业务的执行状况监控通常经过打印日志,再基于日志监控平台查看,大多数状况是没有问题的,可是若是业务出错,这些监控缺少当时的业务上下文,对排查问题不友好,每每须要再去数据库里查。同时日志的打印也依赖于开发,容易遗漏。对于补偿事务每每须要有“差错守护触发补偿”、“工人触发补偿”操做,没有统一的差错守护和处理规范,这些都要开发者逐个开发,负担沉重。json
一些场景下,咱们对数据有强一致性的需求时,会采用在业务层上须要使用“两阶段提交”这样的分布式事务方案。而在另一些场景下,咱们并不须要这么强的一致性,那就只须要保证最终一致性就能够了。数组
例如蚂蚁金服目前在金融核心系统使用的就是 TCC 模式,金融核心系统的特色是一致性要求高(业务上的隔离性)、短流程、并发高。
而在不少金融核心以上的业务(好比在渠道层、产品层、集成层的系统),这些系统的特色是最终一致便可、流程多、流程长、还可能要调用其它公司的服务(如金融网络)。这是若是每一个服务都开发 Try、Confirm、Cancel 三个方法成本高。若是事务中有其它公司的服务,也没法要求其它公司的服务也遵循 TCC 这种开发模式。同时流程长,事务边界太长会影响性能。
对于事务咱们都知道 ACID,也很熟悉 CAP 理论最多只能知足其中两个,因此,为了提升性能,出现了 ACID 的一个变种 BASE。ACID 强调的是一致性(CAP 中的 C),而 BASE 强调的是可用性(CAP 中的 A)。咱们知道,在不少状况下,咱们是没法作到强一致性的 ACID 的。特别是咱们须要跨多个系统的时候,并且这些系统还不是由一个公司所提供的。BASE 的系统倾向于设计出更加有弹力的系统,在短期内,就算是有数据不一样步的风险,咱们也应该容许新的交易能够发生,然后面咱们在业务上将可能出现问题的事务经过补偿的方式处理掉,以保证最终的一致性。
因此咱们在实际开发中会进行取舍,对于更多的金融核心以上的业务系统能够采用补偿事务,补偿事务处理方面在30年前就提出了 Saga 理论,随着微服务的发展,近些年才逐步受到你们的关注。目前业界比较也公认 Saga 是做为长事务的解决方案。
https://github.com/aphyr/dist-sagas/blob/master/sagas.pdf
http://microservices.io/patterns/data/saga.html
Camel 是实现 EIP(Enterprise Integration Patterns)企业集成模式的一款开源产品,它基于事件驱动的架构,有着良好的性能和吞吐量,它在2.21版本新增长了 Saga EIP。
Saga EIP 提供了一种方式能够经过 camel route 定义一系列有关联关系的 Action,这些 Action 要么都执行成功,要么都回滚,Saga 能够协调任何通信协议的分布式服务或本地服务,并达到全局的最终一致性。Saga 不要求整个处理在短期内完成,由于它不占用任何数据库锁,它能够支持须要长时间处理的请求,从几秒到几天,Camel 的 Saga EIP 是基于 Microprofile 的 LRA(Long Running Action),一样也是支持协调任何通信协议任何语言实现的分布式服务。
Saga 的实现不会对数据进行加锁,而是在给操做定义它的“补偿操做”,当正常流程执行出错的时候触发那些已经执行过的操做的“补偿操做”,将流程回滚掉。“补偿操做”能够在 Camel route 上用 Java 或 XML DSL(Definition Specific Language)来定义。
下面是一个 Java DSL 示例:
XML DSL 示例:
Eventuate Tram Saga 框架是使用 JDBC / JPA 的 Java 微服务的一个 Saga 框架。它也和 Camel Saga 同样采用了 Java DSL 来定义补偿操做:
ServiceComb Saga 也是一个微服务应用的数据最终一致性解决方案。相对于 TCC 而言,在 try 阶段,Saga 会直接提交事务,后续 rollback 阶段则经过反向的补偿操做来完成。与前面两种不一样是它是采用 Java 注解+拦截器的方式来进行“补偿”服务的定义。
Saga 是由 alpha 和 omega 组成,其中:
下图展现了 alpha,omega 以及微服务三者的关系:
蚂蚁金服内部大规模在使用 TCC 模式分布式事务,主要用于金融核心等对一致性要求高、性能要求高的场景。在更上层的业务系统由于流程多流程长,开发 TCC 成本比较高,大都会权衡采用 Saga 模式来到达业务最终一致性,因为历史的缘由不一样的 BU 有本身的一套“补偿”事务的方案,基本上是两种:
社区和业界的解决方案通常是两种,一种基本状态机或流程引擎经过 DSL 方式编排流程程和补偿定义,一种是基于 Java 注解+拦截器实现补偿,那么这两种方案有什么优缺点呢?
方式 | 优势 | 缺点 |
---|---|---|
状态机+DSL | 1.能够用可视化工具来定义业务流程,标准化,可读性高,可实现服务编排的功能 2.提升业务分析人员与程序开发人员的沟通效率 3. 业务状态管理:流程本质就是一个状态机,能够很好的反映业务状态的流转 4.提升异常处理灵活性:能够实现宕机恢复后的“向前重试”或“向后补偿” 5.自然可使用 Actor 模型或 SEDA 架构等异步处理引擎来执行,提升总体吞吐量 | 1.业务流程实际是由 JAVA 程序与 DSL 配置组成,程序与配置分离,开发起来比较繁琐 2.若是是改造现有业务,对业务侵入性高 3.引擎实现成本高 |
拦截器+java 注解 | 1.程序与注解是在一块儿的,开发简单,学习成本低 2.方便接入现有业务 3.基于动态代理拦截器,框架实现成本低 | 1.框架没法提供 Actor 模型或 SEDA 架构等异步处理模式来提升系统吞吐量 2.框架没法提供业务状态管理 3.难以实现宕机恢复后的“向前重试”,由于没法恢复线程上下文 |
Seata Saga 的简介能够看一下《Seata Saga 官网文档》。
Seata Saga 采用了状态机+DSL 方案来实现,缘由有如下几个:
在不保证隔离性的状况下:业务流程设计时要遵循“宁肯长款, 不可短款”的原则,长款意思是客户少了线机构多了钱,以机构信誉能够给客户退款,反之则是短款,少的线可能追不回来了。因此在业务流程设计上必定是先扣款。
注意: 异常发生时是否进行补偿也可由用户自定义决定
假设有一个业务流程要调两个服务,先调库存扣减(InventoryService),再调余额扣减(BalanceService),保证在一个分布式内要么同时成功,要么同时回滚。两个参与者服务都有一个 reduce 方法,表示库存扣减或余额扣减,还有一个 compensateReduce 方法,表示补偿扣减操做。以 InventoryService 为例看一下它的接口定义:
这个业务流程对应的状态图:
对应的 JSON:
状态语言在必定程度上参考了 AWS Step Functions。
Type:"状态" 的类型,好比有:
更多详细的状态语言解释请看《Seata Saga 官网文档》。
状态机引擎的设计主要分红三层, 上层依赖下层,从下往上分别是:
Eventing 层:
ProcessController 层:
基于以上两层理论上能够自定义扩展任何"流程"引擎。这两层的设计是参考了内部金融网络平台的设计。
StateMachineEngine 层:
下面是实践中总结的在 Saga 模式下微服务设计的一些经验,固然这是推荐作法,并非说必定要 100% 遵循,没有遵循也有“绕过”方案。
好消息:Seata Saga 模式对微服务的接口参数没有任务要求,这使得 Saga 模式可用于集成遗留系统或外部机构的服务。
出现缘由:
因此服务设计时须要容许空补偿,即没有找到要补偿的业务主键时返回补偿成功并将原业务主键记录下来。
出现缘由:
因此要检查当前业务主键是否已经在空补偿记录下来的业务主键中存在,若是存在则要拒绝服务的执行。
不少时候咱们不须要强调强一性,咱们基于 BASE 和 Saga 理论去设计更有弹性的系统,在分布式架构下得到更好的性能和容错能力。分布式架构没有银弹,只有适合特定场景的方案,事实上 Seata Saga 是一个具有“服务编排”和“Saga 分布式事务”能力的产品,总结下来它的适用场景是: