本文从以下几个方面介绍分布式事务的解决方案:
- 为什么会有分布式事务
- 分布式事务经典模型
- 分布式事务解决方案
为什么会有分布式事务
假设有如下一个架构,这是一个简单的电商架构平台,两个应用节点,一个数据库,一个负载均衡器。这个架构下,每天会产生将近 100W 的订单量。那么一个月的数据量就会超过 3000W。而随着数据量的不断扩大,对于订单表的相关查询操作的性能开销就越来越大。并且响应耗时也越来越长。这个时候我们需要考虑到数据库的优化问题。也就是对数据库进行分表分库,达到分摊数据库压力以及减少数据库单表数据量的目的。
分库分表带来的问题
分库分表以后,一方面分担了单库带来的性能压力;另一方面,减少了单表的数据量。完美的解决了我们遇到的性能问题。但是,随着而来的又有另外的问题。
比如有这样一个场景,订单支付成功以后需要扣减库存。在数据库分库分表之前,所有数据都在同一个库里面,可以通过事务操作就很容易达到数据一致性的目的。但是在数据库做了拆分后,订单状态更新是属于订单的数据库,而库存扣减是属于库存的数据库。原本单库的事务操作就变成了多库的事务操作。
但是每个库的事务只有自己知道,订单库并不知道库存库的事务执行结果,库存库也不知道订单库的修改结果。所以就造成了分布式事务的问题。其实也叫分布式数据一致性。
认识分布式事务
既然存在分布式事务的问题,那就一定有成熟的解决方案,接下来了解下业内的常用解决方法及原理:
X/OpenDTP 事务模型
X/Open DTP(X/Open Distributed Transaction Processing Reference Model) 是X/Open 这个组织定义的一套分布式事务的标准,也就是定义了规范和 API 接口,由各个厂商进行具体的实现。
这个标准提出了使用二阶段提交(2PC – Two-Phase-Commit)来保证分布式事务的完整性。后来 J2EE 也遵循了 X/OpenDTP 规范,设计并实现了 java 里的分布式事务编程接口规范JTA。
X/OpenDTP 角色
在 X/OpenDTP 事务模型中,定义了三个角色:
AP: application, 应用程序,也就是业务层。哪些操作属于一个事务,就是 AP 定义的;
RM: Resource Manager,资源管理器。一般是数据库,也可以是其他资源管理器,比如消息队列,文件系统;
TM: Transaction Manager ,事务管理器、事务协调者,负责接收来自用户程序(AP)发起的 XA 事务指令,并调度和协调参与事务的所有 RM(数据库),确保事务正确完成。
在分布式系统中,每一个机器节点虽然都能够明确知道自己在进行事务操作过程中的结果是成功还是失败,但却无法直接获取到其他分布式节点的操作结果。因此当一个事务操作需要跨越多个分布式节点的时候,为了保持事务处理的 ACID 特性,就需要引入一个“协调者”(TM)来统一调度所有分布式节点的执行逻辑,这些被调度的分布式节点被称为 AP。TM 负责调度 AP 的行为,并最终决定这些 AP 是否要把事务真正进行提交到(RM)。
1、参与分布式事务的应用程序(AP)先到 TM 上注册全局事务
2、 然后各个 AP 直接在相应的资源管理器(RM)上进行事务操作
3、 操作完成以后,各个 AP 反馈事务的处理结果给到 TM
4、 TM 收到所有 AP 的反馈以后,通过数据库提供的 XA 接口进行数据提交或者回滚操作
2pc(two -phaseCommit)
在 X/OpenDTP 模型中,一个分布式事务所涉及的 SQL 逻辑都执行完成,并到了RM最后要提交事务的关键时刻,为了避免分布式系统所固有的不可靠性导致提交事务意外失败,TM 实施两步走的方案,这个就称为二阶提交。
二阶段提交,是计算机网络尤其是在数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务处理过程中能够保持原子性和一致性而设计的一种算法。通常,二阶段提交协议也被认为是一种一致性协议,用来保证分布式系统数据的一致性。目前,绝大部分的关系型数据库都是采用二阶段提交协议来完成分布式事务处理的,利用该协议能够非常方便地完成所有分布式事务 AP 的协调,统一决定事务的提交或回滚,从而能够有效保证分布式数据一致性,因此 2pc 也被广泛运用在许多分布式系统中。
第一阶段
1、事务询问
TM 向所有的 AP 发送事务内容,询问是否可以执行事务提交操作,并开始等待各AP 的响应。
2、执行事务
各个 AP 节点执行事务操作,并将 Undo 和 Redo 信息记录到事务日志中,尽量把提交过程中所有消耗时间的操作和准备都提前完成确保后面 100%成功提交事务。
3、各个 AP 向 TM 反馈事务询问的响应
如果各个 AP 成功执行了事务操作,那么就反馈给 TM yes 的响应,表示事务可以执行;如果 AP 没有成功执行事务,就反馈给 TM no 的响应,表示事务不可以执行。
上面这个阶段有点类似 TM 组织各个 AP 对一次事务操作的投票表态过程,因此2pc 协议的第一个阶段称为“投票阶段”,即各 AP 投票表名是否需要继续执行接下去的事务提交操作。
第二阶段
在这个阶段,TM 会根据各 AP 的反馈情况来决定最终是否可以进行事务提交操作,正常情况下包含两种可能:
提交事务:
1、发送提交请求
TM 向所有 AP 节点发出 commit 请求
2、事务提交
AP 接收到 Commit 请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的事务资源
3、反馈事务提交结果
AP 在完成事务提交之后,向 TM 发送 Ack 消息
4、完成事务
TM 接收到所有 AP 反馈的 ack 消息后,完成事务
事务回滚:
假设任何一个 AP 向 TM 反馈了 NO 的响应,或者在等待超时之后,TM 无法接收到所有 AP 的反馈响应,那么就会中断事务。
1、发送回滚请求
TM 向所有 AP 发出 rollback 请求
2、 事务回滚
AP 收到 rollback 请求后,会利用在第一阶段记录的 Undo 信息来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源
3、 反馈事务回滚结果
各 AP 在完成事务回滚之后,向 TM 发送 Ack 消息
4、中断事务
TM接收到所有 AP 反馈的 ack 消息后,完成事务中断
二阶段提交将一个事务的处理过程分为投票和执行两个阶段。二阶段提交的优点在于,它充分考虑到了分布式系统的不可靠因素,并且采用非常简单的方式(两阶段提交)就把由于系统不可靠从而导致事务提交失败的概率降到最小。
假如一个事务的提交过程总共需要 30 秒的操作,其中 prepare 阶段需要 28 秒(主要是确保事务日志落地磁盘等各种耗时的 I/O 操作),真正的 commit 阶段只需要花费两秒,那么 Commit 阶段发生错误的概率与 Prepare 阶段相比,只是它的2/28(<10%),也就是说,如果 Prepare 阶段成功了,则 Commit 阶段由于时间非常短,失败概率小,会大大增加分布式事务成功的概率。
2pc 的优缺点
1、原理简单,实现很方便
2、 每一个阶段都是同步阻塞,会造成性能损耗
3、协调者存在单点问题,如果协调者在第二阶段出现故障,那么其他参与者会一直处于锁定状态
4、太过保守,任意一个节点失败都会导致数据回滚
5、数据不一致问题: 在阶段二中,当协调者向所有的参与者发送 commit 请求后,发生了网络异常导致协调者在尚未发完 commit 请求之前崩溃,可能会导致只有部分的参与者接收到 commit 请求,剩下没收到 commit 请求的参与者将无法提交事务,也就可能导致数据不一致的问题
3PC
3PC 主要用来解决 2PC 的同步阻塞问题的一种优化方案,3pc 分为 3 个阶段分别为:cancommit、precommit、doCommit。
和 2PC提交的区别在于:
(1) 在协调者和参与者中引入了超时机制(2pc 只有在协调者拥有超时机制,协调者在一定时间内没受到参与者的信息则默认为失败);
(2) 把 2 阶段提交的第一个阶段拆分成了两个步骤。
cancommit 阶段
协调者向参与者发送cancommit请求,参与者如果可以提交就返回 yes 的响应,否则返回 No 的响应。这一阶段主要是确定分布式事务的参与者是否具备了完成commit 的条件,并不会执行事务操作。
precommit 阶段
事务协调者根据参与者的反馈情况来决定是否继续执行事务的 precommit 操作,在这一个阶段,会有两种可能性,第一种是,在 cancommit 阶段所有参与者都反馈的是 yes,则会进行事务预执行。另一种是,任意一个事务参与者在第一阶段返回了 no,则执行事务中断请求。
docommit 阶段
同2PC docommit一致
JTA 事务模型
在 java 中,分布式事务主要的规范是 JTA。JTA 是 java 的事务管理器规范,JTA 全称为 Java Transaction API,JTA 定义了一组统一的事务编程的接口,基于X/OpenDTP 规范设计的分布式事务编程接口规范。基于 JTA 规范的第三方分布式事务框架有JOTM 和 Atomikos。
JOTM
JOTM (java open transaction manager)是 ObjectWeb 的一个开源 JTA 实现,提
供 JTA 分布式事务的功能。但是 JOTM 存在一个问题,在使用中不能自动 rollback,无论什么情况都 commit。
Atomikos
与 JOTM 相比,Atomikos 更加稳定,原本 Atomikos 是商业项目,后来开源。论坛比较活跃,有问题可以随时得到解决。
其他分布式事务解决方案
目前互联网领域里有几种流行的分布式解决方案,但都没有像之前所说的 XA 事务一样形成 X/OpenDTP 那样的工业规范,而是仅仅在具体的行业里获得较多的认可。
我们知道,对于 CAP 来说,对于共享数据的系统,由于网络分区问题的存在,我们只能满足 AP 或者 CP;对于 BASE 理论,满足基本可用。所以其实我们在落地数据一致性解决方案是,基本上都会选择一个平衡点,也就是酸碱平衡理论,ACID 是酸、 BASE 是碱;ACID 是强一致性、BASE 是弱一致性。强一致性代表数据库本身不会出现不一致,每个事务是原子的,或者成功或者失败,事物间是隔离的,互相完全不影响,而且最终状态是持久落盘的,因此,数据库会从一个明确的状态到另外一个明确的状态.; 而 BASE 体现的是最终一致性,允许出现中间状态。
所以对于服务来说,有很多的方案去选择:
1、提供查询服务确认数据状态;2、幂等操作对于重发保证数据的安全性;3、TCC事务操作;4、补偿操作;5、定期校对等
TCC
TCC 事务解决方案本质上是一种补偿的思路,它把事务运行过程分成 Try、Confirm/Cancel 两个阶段,每个阶段由业务代码控制,这样事务的锁力度可以完全自由控制。需要注意的是,TCC 事务和 2pc 的思想类似,但并不是 2pc 的实现,TCC 不再是两阶段提交,而只是它对事务的提交/回滚是通过执行一段 confirm/cancel 业务逻辑来实现,并且也并没有全局事务来把控整个事务逻辑。
业务接口整合,避免分布式事务
这个方案就是把一个业务流程中需要在一个事务里执行的多个相关业务接口包装整合到一个事务中,比如我们可以讲 A/B/C 整合为一个服务 D 来实现单一事务的业务流程服务。
最终一致性方案
eBay 在 2008 年公布了一个关于 BASE 准则提到一个分布式事务解决方案。eBay的方案其实是一个最终一致性方案,它主要采用消息队列来辅助实现事务控制流程,方案的核心是将需要分布式处理的任务通过消息队列的方式来异步执行,如果事务失败,则可以发起人工重试的纠正流程。人工重试被更多的应用于支付场景,通过对账系统对事后问题进行处理。
关于状态机
在使用最终一致性的方案时,一定要提到的一个概念是状态机。
什么是状态机?是一种特殊的组织代码的方式,用这种方式能够确保你的对象随时都知道自己所处的状态以及所能做的操作。它也是一种用来进行对象行为建模的工具,用于描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。
以支付为例,一笔订单可能会有等待支付、支付中、已支付等状态,那么我们就可以先去把可能出现的状态以及状态的流程画出来。
状态机的两个作用:
1、实现幂等
2、 通过状态驱动数据的变化
什么是幂等
简单来说就是重复调用多次产生的业务结果与调用一次产生的业务结果相同。 在分布式架构中,我们调用一个远程服务去完成一个操作,除了成功和失败以外,还有未知状态,那么针对这个未知状态,我们会采取一些重试的行为; 或者在消息中间件的使用场景中,消费者可能会重复收到消息。对于这两种情况,消费端或者服务端需要采取一定的手段,也就是考虑到重发的情况下保证数据的安全性。一般我们常用的手段:
1、状态机实现幂等
2、数据库唯一约束实现幂等
3、通过 id 的方式去识别每次请求判断是否重复