认识分布式事务
-
经典的 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 事务模型中,定义了三个角色
- AP: application, 应用程序,也就是业务层。哪些操作属于一个事务,就是 AP 定义的
- RM: Resource Manager,资源管理器。一般是数据库,也可以是其他资源管理器,比如消息队列,文件系统
- TM: Transaction Manager ,事务管理器、事务协调者,负责接收来自用户程序(AP)发起的 XA 事务指令,并调度和协调参与事务的所有 RM(数据库),确保事务正确完成,
- ”(TM)来统一调度所有分布式节点的执行逻辑,这些被调度的分布式节点被称为 AP。TM 负责调度 AP 的行为,并最终决定这些 AP 是否要把事务真正进行提交到(RM)
- 1 参与分布式事务的应用程序(AP)先到 TM 上注册全局事务 2 然后各个 AP 直接在相应的资源管理器(RM)上进行事务操作 3 操作完成以后,各个 AP 反馈事务的处理结果给到 TM 4 TM 收到所有 AP 的反馈以后,通过数据库提供的 XA 接口进行数据提交或者回滚操作
-
2pc
-
在 X/OpenDTP 模型中,一个分布式事务所涉及的 SQL 逻辑都执行完成,并到了(RM)要最后提交事务的关键时刻,为了避免分布式系统所固有的不可靠性导致提交事务意外失败,TM 果断决定实施两步走的方案,这个就称为二阶提交,二阶段提交,是计算机网络尤其是在数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务处理过程中能够保持原子性和一致性而设计的一种算法。
-
二阶段提交协议也被认为是一种一致性协议,用来保证分布式系统数据的一致性
-
目前,绝大部分的关系型数据库都是采用二阶段提交协议来完成分布式事务处理的,利用该协议能够非常方便地完成所有分布式事务 AP 的协调,统一决定事务的提交或回滚,从而能够有效保证分布式数据一致性,因此 2pc 也被广泛运用在许多分布式系统中
-
-
第一阶段:
-
1 事务询问:
-
TM 向所有的 AP 发送事务内容,询问是否可以执行事务提交操作,并开始等待各AP 的响应
-
2 执行事务:
-
各个 AP 节点执行事务操作,并将 Undo 和 Redo 信息记录到事务日志中,尽量把提交过程中所有消耗时间的操作和准备都提前完成确保后面 100%成功提交事务
-
3各个 AP 向 TM 反馈事务询问的响应:
-
如果各个 AP 成功执行了事务操作,那么就反馈给 AP yes 的响应,表示事务可以执行;如果 AP 没有成功执行事务,就反馈给 TM no 的响应,表示事务不可以执行
-
上面这个阶段有点类似 TM 组织各个 AP 对一次事务操作的投票表态过程,因此2pc 协议的第一个阶段称为“投票阶段”,即各 AP 投票表名是否需要继续执行接下去的事务提交操作。
-
第二阶段
-
事务提交
-
在这个阶段,TM 会根据各 AP 的反馈情况来决定最终是否可以进行事务提交操作,正常情况下包含两种可能
假如 TM 从所有参与者获得的反馈都是 yes 响应,那么就会执行事务提交 -
1 发送提交请求:
-
TM 向所有 AP 节点发出 commit 请求
-
2 事务提交:
-
AP 接收到 Commit 请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的事务资源
-
3 反馈事务提交结果
-
AP 在完成事务提交之后,向 TM 发送 Ack 消息
-
4 完成事务:
-
TM 接收到所有 AP 反馈的 ack 消息后,完成事务
-
事务回滚:
-
如果第一个阶段中的某一个资源预提交失败,那么第二个阶段就回滚第一阶段已经预提交成功的资源,假设任何一个 AP 向 TM 反馈了 NO 的响应,或者在等待超时之后,TM 无法接收到所有 AP 的反馈响应,那么就会中断事务
-
1 发送回滚请求:
-
TM 向所有 AP 发出 abort 请求
-
2 事务回滚:
-
AP 收到 abort 请求后,会利用在第一阶段记录的 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。
- 和 2 阶段提交的区别在于:
- 在协调者和参与者中引入了超时机制,{2pc 只有在协调者拥有超时机制,协调者在一定时间内没受到参与者的信息则默认为失败};
- 把 2 阶段提交的第一个阶段拆分成了两个步骤。
- cancommit 阶段
- 协调者向参与者发送 commit 请求,参与者如果可以提交就返回 yes 的响应,否则返回 No 的响应。这一阶段主要是确定分布式事务的参与者是否具备了完成commit 的条件,并不会执行事务操作。
1 询问参与者是否可以执行事务提交操作
2 正常情况下只要能够顺利执行事务,就返回 yes 的响应,并进入预备状态。 - precommit 阶段
- 事务协调者根据参与者的反馈情况来决定是否继续执行事务的 precommit 操作,在这一个阶段,会有两种可能性,第一种是,在 cancommit 阶段所有参与者都反馈的是 yes,则会进行事务预执行
- 1 协调者向参与者发送 precommit 请求
- 2 参与者收到 precommit 请求后,执行事务操作(还没commit),并把事务的 undo 和 redo 信息记录到事务日志中
- 3 返回事务的执行结果给到协调者,并等待最终的提交指令如果任意一个事务参与者在第一阶段返回了 no,则执行事务中断请求
- 中断请求:
- 1 向所有事务参与者发送事务中断请求
- 2 对于事务参与者来说,无论是收到协调者的中断请求,还是等待协调者新的指令之前出现超时,参与者都会中断事务
- doCommit 阶段
- 这个阶段同样存在两种情况,正常情况下,precommit 都响应了 ack 给到协调者, 那么协调者会发起事务提交请求。
- 1 协调者向所有参与者发送 docommit 请求
- 2 参与者收到 docommit 请求后,执行事务提交操作,并释放所有事务资源
- 3 事务提交以后返回 ack 给到协调者
- 4 协调者收到所有参与者的响应后,完成事务
- 如果在 precommit 阶段,有参与者没有发送 ack 给到协调者,那么则执行事务中断指令:
- 1 协调者向所有参与者发送中断事务的请求
- 2 参与者收到请求以后,利用在第二个阶段记录的 undo 信息来执行事务回滚操作
- 3 向协调者发送 ack 消息,协调者收到消息以后,执行事务中断操作
分布式事务一致性
- 在 java 中,分布式事务主要的规范是 JTA/XA . JTA 是 java 的事务管理器规范,JTA 全称为 Java Transaction API, JTA 定义了一组统一的事务编程的接口,基于X/OpenDTP 规范设计的分布式事务编程接口规范
- XA 是工业标准的 X/Open DTP规范
- 基于 JTA 规范的第三方分布式事务框架有 Jotm 和 Atomikos
- JOTM
- JOTM (java open transaction manager)是 ObjectWeb 的一个开源 JTA 实现,提供 JTA 分布式事务的功能
但是 JOTM 存在一个问题,在使用中不能自动 rollback,无论什么情况都 commit。 - Atomikos
- 与 JOTM 相比,Atomikos 更加稳定,原本 Atomikos 是商业项目,后来开源。论坛比较活跃,有问题可以随时解决
互联网行业的数据一致性问题解决方案
- 目前互联网领域里有几种流行的分布式解决方案,但都没有像之前所说的 XA 事务一样形成 X/OpenDTP 那样的工业规范,而是仅仅在具体的行业里获得较多的认可;
- CAP 和 BASE 理论:
- 对于 CAP 来说,对于共享数据的系统,由于网络分区问题的存在,我们只能满足 AP 或者 CP;
- 对于 BASE 理论,满足基本可用。所以其实我们在落地数据一致性解决方案是,基本上都会选择一个平衡点,也就是酸碱平衡理论,
ACID 是酸、 BASE 是碱;
- 强一致性代表数据库本身不会出现不一致,每个事务是原子的,或者成功或者失败,事物间是隔离的,互相完全不影响,而且最终状态是持久落盘的,因此,数据库会从一个明确的状态到另外一个明确的状态.;
- 而 BASE 体现的是最终一致性,允许出现中间状态。
- 所以对于对于服务来说,有很多的方案去选择:
- 1 提供查询服务确认数据状态、
- 2 幂等操作对于重发保证数据的安全性、
- 3.TCC事务操作、
- 4.补偿操作、
- 5.定期校对。
- 业务接口整合,避免分布式事务
- 这个方案就是把一个业务流程中需要在一个事务里执行的多个相关业务接口包装整合到一个事务中,比如我们可以讲 A/B/C 整合为一个服务 D 来实现单一事务的业务流程服务
最终一致性方案
- eBay 在 2008 年公布了一个关于 BASE 准则提到一个分布式事务解决方案。eBay的方案其实是一个最终一致性方案,它主要采用消息队列来辅助实现事务控制流程,方案的核心是将需要分布式处理的任务通过消息队列的方式来异步执行,如果事务失败,则可以发起人工重试的纠正流程。
- 人工重试被更多的应用于支付场景,通过对账系统对事后问题进行处理
- 比如一个很常见的场景:某个用户产生了一笔交易,那么需要在交易表中增加记录,同时需要修改用户表的金额(余额),由于这两个表属于不同的远程服务,所以就会涉及到分布式事务与数据一致性的问题
关于状态机
-
什么是状态机?是一种特殊的组织代码的方式,用这种方式能够确保你的对象随时都知道自己所处的状态以及所能做的操作。它也是一种用来进行对象行为建模的工具,用于描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。
-
状态机这个概念大家都不陌生,比如 TCP 协议的状态机。同时我们在编写相关业务逻辑的时候经常也会需要处理各种事件和状态的切换,比如 switch、if/else。
-
所以我们其实一直在跟状态机打交道,只是可能没有意识到而已。在处理一些业务逻辑比较复杂的需求时,可以先看看是否适合用一个有限状态机来描述,如果可以把业务模型抽象成一个有限状态机,那么代码就会逻辑特别清晰,结构特别规整。比如我们来简单描述一个订单我们以支付为例,一笔订单可能会有等待支付、支付中、已支付等状态,那么我们就可以先去把可能出现的状态以及状态的流程画出来。
-
状态机的作用
- 1 实现幂等
- 2 通过状态驱动数据的变化
- 3 业务流程以及逻辑更加清晰,特别是应对复杂的业务场景
什么是幂等
- 简单来说:重复调用多次产生的业务结果与调用一次产生的业务结果相同; 在分布式架构中,我们调用一个远程服务去完成一个操作,除了成功和失败以外,还有未知状态,那么针对这个未知状态,我们会采取一些重试的行为; 或者在消息中间件的使用场景中,消费者可能会重复收到消息。对于这两种情况,消费端或者服务端需要采取一定的手段,也就是考虑到重发的情况下保证数据的安全性。一般我们常用的手段
- 1 状态机实现幂等
- 2 数据库唯一约束实现幂等
- 3 通过 tokenid 的方式去识别每次请求判断是否重复
基于消息的最终一致性方案实践
-
在最终一致性这个方案上,也有两种选择方案,一种是基于可靠消息中间件来实现异步的最终一致性、另一种就是通过 MQ 来实现最大努力通知型。
-
这两种都比较常见,比如大家如果对接过支付宝支付的 api,就应该能知道,当你调用支付支付成功以后,支付宝会提供一个异步回调,调用配置好的指定的接口地址。在这个接 口中,你可以获得支付宝的支付结果并根据结果做相应的处理。最后必须要返回一个 ack 给到支付宝的回调 api,告诉他这边已经处理成功了。否则,支付宝的异步回调会不断重试,当然有重试次数,以及重试的间隔时间。那接下来,我们通过第一种方案来实现数据的最终一致性
-
通过异步消息执行方案的本质是,把两个事物转化成两个本地事务,然后依靠消息本身的可靠性,以及消息的重试机制达到最终一致性。
代码实践
github上开源的tcc-transaction,可以很好解决分布式事务问题
- TCC 事务解决方案本质上是一种补偿的思路,它把事务运行过程分成 Try、Confirm/cancel 两个阶段,每个阶段由业务代码控制,这样事务的锁力度可以完全自由控制。
- 需要注意的是,TCC 事务和 2pc 的思想类似,但并不是 2pc 的实现,TCC 不再是两阶段提交,而只是它对事务的提交/回滚是通过执行一段 confirm/cancel 业务逻辑来实现,并且也并没有全局事务来把控整个事务逻辑。