分布式场景下,多个服务同时对服务一个流程,比如电商下单场景,需要支付服务进行支付、库存服务扣减库存、订单服务进行订单生成、物流服务更新物流信息等。如果某一个服务执行失败,或者网络不通引起的请求丢失,那么整个系统可能出现数据不一致的原因。

上述场景就是分布式一致性问题,追根到底,分布式一致性的根本原因在于数据的分布式操作,引起的本地事务无法保障数据的原子性引起。

分布式一致性问题的解决思路有两种,一种是分布式事务一种是尽量通过业务流程避免分布式事务。分布式事务是直接解决问题,而业务规避其实通过解决出问题的地方(解决提问题的人)。其实在真实业务场景中,如果业务规避不是很麻烦的前提,最优雅的解决方案就是业务规避。

分布式事务实现方案从类型上去分刚性事务柔型事务

  • 刚性事务满足CAP的CP理论
  • 柔性事务满足BASE理论(基本可用,最终一致)

img

  • 刚性事务: 通常无业务改造,强一致性,原生支持回滚/隔离性,低并发,适合短事务。

**原则:**刚性事务满足足CAP的CP理论。刚性事务指的是,要使分布式事务,达到像本地式事务一样,具备数据强一致性,从CAP来看,就是说,要达到CP状态。

刚性事务:XA 协议(2PC、JTA、JTS)、3PC,但由于同步阻塞,处理效率低,不适合大型网站分布式场景。

  • 柔性事务: 柔性事务指的是,不要求强一致性,而是要求最终一致性,允许有中间状态,也就是Base理论,换句话说,就是AP状态。

与刚性事务相比,柔性事务的特点为:有业务改造,最终一致性,实现补偿接口,实现资源锁定接口,高并发,适合长事务。

柔性事务分为:

  • 补偿型
  • 异步确保型
  • 最大努力通知型。

**柔型事务:**TCC/FMT、Saga(状态机模式、Aop模式)、本地事务消息、消息事务(半消息)。

刚性事务

XA模型或者X/Open DTP模型: X/Open DTP(Distributed Transaction Process) 是一个分布式事务模型。这个模型主要使用了两段提交(2PC - Two-Phase-Commit)来保证分布式事务的完整性。

在X/Open DTP(Distributed Transaction Process)模型里面,有三个角色:

  • AP: Application, 应用程序。也就是业务层。哪些操作属于一个事务,就是AP定义的。
  • TM: Transaction Manager, 事务管理器。接收AP的事务请求,对全局事务进行管理,管理事务分支状态,协调RM的处理,通知RM哪些操作属于哪些全局事务以及事务分支等等。这个也是整个事务调度模型的核心部分。
  • RM:Resource Manager, 资源管理器。一般是数据库,也可以是其他的资源管理器,如消息队列(如JMS数据源),文件系统等。

XA规范主要定义了(全局)事务管理器(Transaction Manager)和(局部)资源管理器(Resource Manager)之间的接口。XA接口是双向的系统接口,在事务管理器(Transaction Manager)以及一个或多个资源管理器(Resource Manager)之间形成通信桥梁。

XA之所以需要引入事务管理器是因为,在分布式系统中,从理论上讲(参考Fischer等的论文),两台机器理论上无法达到一致的状态,需要引入一个单点进行协调。事务管理器控制着全局事务,管理事务生命周期,并协调资源。资源管理器负责控制和管理实际资源(如数据库或JMS队列)。

img

用非常官方的话来说:

XA规范是X/Open组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。XA规范描述了全局的事务管理器与局部的资源管理器之间的接口。 XA规范的目的是允许的多个资源(如数据库,应用服务器,消息队列等)在同一事务中访问,这样可以使ACID属性跨越应用程序而保持有效。

XA规范使用两阶段提交(2PC,Two-Phase Commit)协议来保证所有资源同时提交或回滚任何特定的事务。

XA规范在上世纪90年代初就被提出。目前,几乎所有主流的数据库都对XA规范提供了支持。

XA规范(XA Specification) 是X/OPEN 提出的分布式事务处理规范。XA则规范了TM与RM之间的通信接口,在TM与多个RM之间形成一个双向通信桥梁,从而在多个数据库资源下保证ACID四个特性。目前知名的数据库,如Oracle, DB2,mysql等,都是实现了XA接口的,都可以作为RM。

XA是数据库的分布式事务,强一致性,在整个过程中,数据一直锁住状态,即从prepare到commit、rollback的整个过程中,TM一直把持折数据库的锁,如果有其他人要修改数据库的该条数据,就必须等待锁的释放,存在长事务风险。

以下的函数使事务管理器可以对资源管理器进行的操作:

  • xa_open,xa_close:建立和关闭与资源管理器的连接。
  • xa_start,xa_end:开始和结束一个本地事务。
  • xa_prepare,xa_commit,xa_rollback:预提交、提交和回滚一个本地事务。
  • xa_recover:回滚一个已进行预提交的事务。
  • ax_开头的函数使资源管理器可以动态地在事务管理器中进行注册,并可以对XID(TRANSACTION IDS)进行操作。
  • ax_reg,ax_unreg;允许一个资源管理器在一个TMS(TRANSACTION MANAGER SERVER)中动态注册或撤消注册。

XA各个阶段的处理流程:

img

XA协议的实现

  • 2PC/3PC协议

两阶段提交(2PC)协议是XA规范定义的 数据一致性协议。 三阶段提交(3PC)协议对 2PC协议的一种扩展。

  • JTA规范

作为java平台上事务规范 JTA(Java Transaction API)也定义了对XA事务的支持,实际上,JTA是基于XA架构上建模的,在JTA 中,事务管理器抽象为javax.transaction. TransactionManager接口,并通过底层事务服务(即JTS)实现。像很多其他的java规范一样,JTA仅仅定义了接口,具体的实现则是由供应商(如J2EE厂商)负责提供,目前JTA的实现主要由以下几种:

  1. J2EE容器所提供的JTA实现(JBoss)
  2. 独立的JTA实现:如JOTM,Atomikos.

这些实现可以应用在那些不使用J2EE应用服务器的环境里用以提供分布事事务保证。如Tomcat,Jetty以及普通的java应用。

  • JTS规范

事务是编程中必不可少的一项内容,基于此,为了规范事务开发,Java增加了关于事务的规范,即JTA和JTS

JTA定义了一套接口,其中约定了几种主要的角色:TransactionManager、UserTransaction、Transaction、XAResource,并定义了这些角色之间需要遵守的规范,如Transaction的委托给TransactionManager等。

JTS也是一组规范,上面提到JTA中需要角色之间的交互,那应该如何交互?JTS就是约定了交互细节的规范。

总体上来说JTA更多的是从框架的角度来约定程序角色的接口,而JTS则是从具体实现的角度来约定程序角色之间的接口,两者各司其职。

2PC协议

二阶段提交(Two Phase Commit),二阶段提交。广泛应用在数据库领域,为了使得基于分布式架构的所有节点可以在进行事务处理时能够保持原子性和一致性。绝大部分关系型数据库,都是基于2PC完成分布式的事务处理。

顾名思义,2PC分为两个阶段处理,

img

img

2PC方案比较适合单体应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景

2PC方案实际很少用,一般来说某个系统内部如果出现跨多个库的这么一个操作,是不合规的。我可以给大家介绍一下, 现在微服务,一个大的系统分成几百个服务,几十个服务。一般来说,我们的规定和规范,是要求每个服务只能操作自己对应的一个数据库。

如果你要操作别的服务对应的库,不允许直连别的服务的库,违反微服务架构的规范,你随便交叉胡乱访问,几百个服务的话,全体乱套,这样的一套服务是没法管理的,没法治理的,可能会出现数据被别人改错,自己的库被别人写挂等情况。

如果你要操作别人的服务的库,你必须是通过调用别的服务的接口来实现,绝对不允许交叉访问别人的数据库。2PC具有明显的优缺点。优点主要体现在实现原理简单;但同时,缺点比较多:

  • 2PC的提交在执行过程中,所有参与事务操作的逻辑都处于阻塞状态,也就是说,各个参与者都在等待其他参与者响应,无法进行其他操作;

  • 协调者是个单点,一旦出现问题,其他参与者将无法释放事务资源,也无法完成事务操作;

  • 数据不一致。当执行事务提交过程中,如果协调者向所有参与者发送Commit请求后,发生局部网络异常或者协调者在尚未发送完Commit请求,即出现崩溃,最终导致只有部分参与者收到、执行请求。于是整个系统将会出现数据不一致的情形;

  • 保守。2PC没有完善的容错机制,当参与者出现故障时,协调者无法快速得知这一失败,只能严格依赖超时设置来决定是否进一步的执行提交还是中断事务。

实际上分布式事务是一件非常复杂的事情,两阶段提交只是通过增加了事务协调者(Coordinator)的角色来通过2个阶段的处理流程来解决分布式系统中一个事务需要跨多个服务节点的数据一致性问题。但是从异常情况上考虑,这个流程也并不是那么的无懈可击。

3PC协议

针对2PC的缺点,研究者提出了3PC,即Three-Phase Commit。作为2PC的改进版,3PC将原有的两阶段过程,重新划分为CanCommit、PreCommit和do Commit三个阶段。

img

柔性事务

在电商领域等互联网场景下,刚性事务在数据库性能和处理能力上都暴露出了瓶颈。柔性事务有两个特性:基本可用柔性状态

  • 基本可用: 是指分布式系统出现故障的时候允许损失一部分的可用性。

  • 柔性状态: 是指允许系统存在中间状态,这个中间状态不会影响系统整体的可用性,比如数据库读写分离的主从同步延迟等。柔性事务的一致性指的是最终一致性

柔性事务主要分为补偿型和通知型,补偿型事务又分:TCC、Saga,通知型事务分:MQ事务消息、最大努力通知型。

通知型

补偿型

但是基于消息实现的事务并不能解决所有的业务场景,例如以下场景:某笔订单完成时,同时扣掉用户的现金。

这里事务发起方是管理订单库的服务,但对整个事务是否提交并不能只由订单服务决定,因为还要确保用户有足够的钱,才能完成这笔交易,而这个信息在管理现金的服务里。这里我们可以引入基于补偿实现的事务,其流程如下:

  • 创建订单数据,但暂不提交本地事务
  • 订单服务发送远程调用到现金服务,以扣除对应的金额
  • 上述步骤成功后提交订单库的事务
  • 以上这个是正常成功的流程,异常流程需要回滚的话,将额外发送远程调用到现金服务以加上之前扣掉的金额。

以上流程比基于消息队列实现的事务的流程要复杂,同时开发的工作量也更多:

  • 编写订单服务里创建订单的逻辑
  • 编写现金服务里扣钱的逻辑
  • 编写现金服务里补偿返还的逻辑

可以看到,该事务流程相对于基于消息实现的分布式事务更为复杂,需要额外开发相关的业务回滚方法,也失去了服务间流量削峰填谷的功能。但其仅仅只比基于消息的事务复杂多一点,若不能使用基于消息队列的最终一致性事务,那么可以优先考虑使用基于补偿的事务形态。

什么是补偿模式?

补偿模式使用一个额外的协调服务来协调各个需要保证一致性的业务服务,协调服务按顺序调用各个业务微服务,如果某个业务服务调用异常(包括业务异常和技术异常)就取消之前所有已经调用成功的业务服务。

补偿模式大致有TCC,和Saga两种细分的方案

  • TCC 事务模型
  • SAGA长事务模型

Sage模式

参见: 分布式事务:Saga模式 - 简书 (jianshu.com)

Seata

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

Seata AT使用了增强型二阶段提交实现。

Seata 分三大模块 :

  • TC : 事务协调者。负责我们的事务ID的生成,事务注册、提交、回滚等。
  • TM: 事务发起者。定义事务的边界,负责告知 TC,分布式事务的开始,提交,回滚。
  • RM: 资源管理者。管理每个分支事务的资源,每一个 RM 都会作为一个分支事务注册在 TC。

在Seata的AT模式中,TM和RM都作为SDK的一部分和业务服务在一起,我们可以认为是Client。TC是一个独立的服务,通过服务的注册、发现将自己暴露给Client们。

Seata 中有三大模块中, TM 和RM是作为Seata的客户端与业务系统集成在一起,TC 作为Seata的服务端独立部署。