分布式事务

分布式事务的解决方案

一。为什么会有分布式事务
假设有如下架构,两个应用节点,一个数据库,一个负载均衡器。在这个架构下,一个月的数据量就会超过 3000W,而随着数据量的不断扩大,对于表的相关查询操作的性能开销就越来越大,并且响应耗时也越来越长。这个时候需要考虑到数据库的优化问题,也就是对数据库进行分表分库,达到分摊数据库压力以及减少数据库单表数据量的目的。分库分表以后,一方面分担了单库带来的性能压力;另一方面,减少了单表的数据量。完美的解决了性能问题。但是,又有另外的问题。在数据库分库分表之前,所有数据都在同一个库里面,可以通过事务操作就很容易达到数据一致性的目的。但是在数据库做了拆分后,订单状态更新是属于订单的数据库,而库存扣减是属于库存的数据库。原本单库的事务操作就变成了多库的事务操作。但是每个库的事务只有自己知道,订单库并不知道库存库的事务执行结果,库存库也不知道订单库的修改结果。所以就造成了分布式事务的问题,也叫分布式数据一致性。
二。分布式事务的解决方法及原理。
1.经典的 X/OpenDTP 事务模型
(1)X/Open DTP(X/Open Distributed Transaction Processing Reference Model) 是X/Open 这个组织定义的一套分布式事务的标准,也就是定义了规范和 API 接口,由各个厂商进行具体的实现。这个标准提出了使用二阶段提交(2PC – Two-Phase-Commit)来保证分布式事务的完整性,后来 J2EE 也遵循了 X/OpenDTP 规范,设计并实现了 java 里的分布式事务编程接口规范-JTA。
(2)X/OpenDTP 角色
在 X/OpenDTP 事务模型中,定义了三个角色:
AP: application, 应用程序,也就是业务层。哪些操作属于一个事务,就是 AP 定义的。
RM: Resource Manager,资源管理器。一般是数据库,也可以是其他资源管理器,比如消息队列,文件系统。
TM: Transaction Manager ,事务管理器、事务协调者,负责接收来自用户程序。
(3)原理
AP发起的 XA 事务指令,并调度和协调参与事务的所有 RM(数据库),确保事务正确完成在分布式系统中,每一个机器节点虽然都能够明确知道自己在进行事务操作过程中的结果是成功还是失败,但却无法直接获取到其他分布式节点的操作结果。因此当一个事务操作需要跨越多个分布式节点的时候,为了保持事务处理的 ACID 特性,就需要引入一个“协调者”(TM)来统一调度所有分布式节点的执行逻辑,这些被调度的分布式节点被称为 AP。TM 负责调度 AP 的行为,并最终决定这些 AP 是否要把事务真正进行提交到(RM)。
在这里插入图片描述
(1) 参与分布式事务的应用程序(AP)先到 TM 上注册全局事务
(2) 然后各个 AP 直接在相应的资源管理器(RM)上进行事务操作
(3) 操作完成以后,各个 AP 反馈事务的处理结果给到 TM
(4) TM 收到所有 AP 的反馈以后,通过数据库提供的 XA 接口进行数据提交或者回
滚操作
数据库属于资源管理器,资源管理器不属于数据库,资源管理器还有其他更多的一些资源,比如消息中间件用来存储消息。
2.2pc
2pc 提交(two -phaseCommit),在 X/OpenDTP 模型中,一个分布式事务所涉及的 SQL 逻辑都执行完成,并到了(RM)要最后提交事务的关键时刻,为了避免分布式系统所固有的不可靠性导致提交事务意外失败,TM 果断决定实施两步走的方案,这个就称为二阶提交。二阶段提交,是计算机网络尤其是在数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务处理过程中能够保持原子性和一致性而设计的一种算法。通常,二阶段提交协议也被认为是一种一致性协议,用来保证分布式系统数据的一致性。目前,绝大部分的关系型数据库都是采用二阶段提交协议来完成分布式事务处理的,利用该协议能够非常方便地完成所有分布式事务 AP 的协调,统一决定事务的提交或回滚,从而能够有效保证分布式数据一致性,因此 2pc 也被广泛运用在许多分布式系统中。
基于2PC协议的事务是强一致性的,而在zookeeper里面只要有半数以上的节点通过,就可以提交事务属于弱一致性。
第一阶段
(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 请求的参与者将无法提交事务,也就可能导致数据不一致的问题。

3.3PC
3PC 协议主要用来解决 2PC 的同步阻塞问题的一种优化方案,3pc 分为 3 个阶段
分别为:cancommit、Precommit、doCommit。
和 2 阶段提交的区别是:
(1) 在协调者和参与者中引入了超时机制,2pc 只有在协调者拥有超时机制,协调者在一定时间内没收到参与者的信息则默认为失败;
(2) 把 2 阶段提交的第一个阶段拆分成了两个步骤。
cancommit 阶段
协调者向参与者发送 commit 请求,参与者如果可以提交就返回 yes 的响应,否则返回 No 的响应。这一阶段主要是确定分布式事务的参与者是否具备了完成commit 的条件,并不会执行事务操作。
(1) 询问参与者是否可以执行事务提交操作
(2) 正常情况下只要能够顺利执行事务,就返回 yes 的响应,并进入预备状态。
precommit 阶段
a.事务协调者根据参与者的反馈情况来决定是否继续执行事务的 precommit 操作,在这一个阶段,会有两种可能性,第一种是,在 cancommit 阶段所有参与者都反馈的是 yes,则会进行事务预执行
(1) 协调者向参与者发送 precommit 请求
(2) 参与者收到 precommit 请求后,执行事务操作,并把事务的 undo 和 redo 信
息记录到事务日志中
(3) 返回事务的执行结果给到协调者,并等待最终的提交指令。
b.如果任意一个事务参与者在第一阶段返回了 no,则执行事务中断请求
(1) 向所有事务参与者发送事务中断请求
(2) 对于事务参与者来说,无论是收到协调者的中断请求,还是等待协调者新的指
令之前出现超时,参与者都会中断事务
doCommit 阶段
a.这个阶段同样存在两种情况,正常情况下,precommit 都响应了 ack 给到协调者,那么协调者会发起事务提交请求。
(1) 协调者向所有参与者发送 docommit 请求
(2) 参与者收到 docommit 请求后,执行事务提交操作,并释放所有事务资源
(3) 事务提交以后返回 ack 给到协调者
(4) 协调者收到所有参与者的响应后,完成事务
b.如果在 precommit 阶段,有参与者没有发送 ack 给到协调者,那么则执行事务中断指令
(1) 协调者向所有参与者发送中断事务的请求
(2) 参与者收到请求以后,利用在第二个阶段记录的 undo 信息来执行事务回滚操作
(3) 向协调者发送 ack 消息,协调者收到消息以后,执行事务中断操作。
3PC在实际的应用中应用很少,几乎看不到,会在一些中间件中应用,2PC用的也不多,用的多的是基于他们做一些优化。有时候,在某些领域,如支付就需要强一致性。3PC没有解决2PC中数据一致性的问题,只是针对2PC做了一些优化。

三。分布式事务一致性

在 java 中,分布式事务主要的规范是 JTA/XA 。 JTA 是 java 的事务管理器规范,JTA 全称为 Java Transaction API,JTA 定义了一组统一的事务编程的接口,基于X/OpenDTP 规范设计的分布式事务编程接口规范。XA 是工业标准的 X/Open DTP规范,基于 JTA 规范的第三方分布式事务框架有 Jotm 和 Atomikos。
1.JOTM
JOTM (java open transaction manager)是 ObjectWeb 的一个开源 JTA 实现,提供 JTA 分布式事务的功能但是 JOTM 存在一个问题,在使用中不能自动 rollback,无论什么情况都 commit。
2.Atomikos(用的比较多)
与 JOTM 相比,Atomikos 更加稳定,原本 Atomikos 是商业项目,后来开源。论坛比较活跃,可以随时解决互联网行业的数据一致性问题解决方案。目前互联网领域里有几种流行的分布式解决方案,但都没有像之前所说的 XA 事务一样形成 X/OpenDTP 那样的工业规范,而是仅仅在具体的行业里获得较多的认可。
对于 CAP 来说,对于共享数据的系统,由于网络分区问题的存在,我们只能满足 AP 或者 CP;对于 BASE 理论,满足基本可用。所以在落地数据一致性解决方案基本上都会选择一个平衡点,ACID 是强一致性、BASE 是弱一致性。强一致性代表数据库本身不会出现不一致,每个事务是原子的,或者成功或者失败,事务间是隔离的,互相完全不影响,而且最终状态是持久落盘的,因此,数据库会从一个明确的状态到另外一个明确的状态.; 而 BASE 体现的是最终一致性,允许出现中间状态。在互联网领域中,大部分公司会尽量避免分布式事务,还有一小部分避免不了的,都采用的是最终一致性事务。所以对于对于服务来说,有很多的方案去选择:
(1) 提供查询服务确认数据状态、
(2)幂等操作对于重发保证数据的安全性、
(3)TCC事务操作、
(4)补偿操作、
(5)定期校对。

四。业务接口整合,避免分布式事务

这个方案就是把一个业务流程中需要在一个事务里执行的多个相关业务接口包装整合到一个事务中,比如我们可以将 A/B/C 整合为一个服务 D 来实现单一事务的业务流程服务最终一致性方案。
eBay 在 2008 年公布了一个关于 BASE 准则提到一个分布式事务解决方案。eBay的方案其实是一个最终一致性方案,它主要采用消息队列来辅助实现事务控制流程,方案的核心是将需要分布式处理的任务通过消息队列的方式来异步执行,如果事务失败,则可以发起人工重试的纠正流程。人工重试被更多的应用于支付场景,通过对账系统对事后问题进行处理,比如某个用户产生了一笔交易,那么需要在交易表中增加记录,同时需要修改用户表的金额(余额),由于这两个表属于不同的远程服务,所以就会涉及到分布式事务与数据一致性的问题。

五。状态机

状态机是一种特殊的组织代码的方式,用这种方式能够确保对象随时都知道自己所处的状态以及所能做的操作。它也是一种用来进行对象行为建模的工具,用于描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。我们在编写相关业务逻辑的时候经常会需要处理各种事件和状态的切换,比如 switch、if/else。所以我们一直在跟状态机打交道,再比如 TCP 协议的状态机。在处理一些业务逻辑比较复杂的需求时,可以先看看是否适合用一个有限状态机来描述,如果可以把业务模型抽象成一个有限状态机,那么代码就会逻辑特别清晰,结构特别规整。比如以支付为例简单描述一个订单,一笔订单可能会有等待支付、支付中、已支付等状态,那么就可以先去把可能出现的状态以及状态的流程画出来。
在这里插入图片描述
状态机的几个作用:
(1) 实现幂等
(2) 通过状态驱动数据的变化
(3) 业务流程以及逻辑更加清晰,特别是应对复杂的业务场景

什么是幂等
简单来说:重复调用多次产生的业务结果与调用一次产生的业务结果相同; 在分布式架构中,我们调用一个远程服务去完成一个操作,除了成功和失败以外,还有未知状态,那么针对这个未知状态,我们会采取一些重试的行为; 或者在消息中间件的使用场景中,消费者可能会重复收到消息。对于这两种情况,消费端或者服务端需要采取一定的手段,也就是考虑到重发的情况下保证数据的安全性。一般我们常用的手段有,
(1) 状态机实现幂等
(2) 数据库唯一约束实现幂等(同一条数据再次插入库中时会报错)
(3) 通过 tokenid 的方式去识别每次请求判断是否重复基于消息的最终一致性方案实践。
在最终一致性这个方案上,也有两种选择方案,一种是基于可靠消息中间件来实现异步的最终一致性、另一种就是通过 MQ 来实现最大努力通知型。这两种都比较常见,比如对接支付宝支付的 api时,当你调用支付宝支付成功以后,支付宝会提供一个异步回调,调用配置好的指定的接口地址。在这个接口中,你可以获得支付宝的支付结果并根据结果做相应的处理。最后必须要返回一个 ack 给到支付宝的回调 api,告诉他这边已经处理成功了。否则,支付宝的异步回调会不断重试,当然有重试次数,以及重试的间隔时间。
在这里插入图片描述
通过异步消息执行方案的本质是,把两个事物转化成两个本地事务,然后依靠消息本身的可靠性,以及消息的重试机制达到最终一致性。

六。tcc事务

在这里插入图片描述
TCC(应用层) 事务解决方案本质上是一种补偿的思路,它把事务运行过程分成 Try、Confirm/cancel 两个阶段,每个阶段由业务代码控制,这样事务的锁力度可以完全自由控制。
需要注意的是,TCC 事务和 2pc 的思想类似,但并不是 2pc 的实现,TCC 不再是两阶段提交,而只是它对事务的提交/回滚是通过执行一段 confirm/cancel 业务逻辑来实现,并且也并没有全局事务来把控整个事务逻辑。
每个应用服务里面都要实现try,confirm,cancel这三个接口,当业务应用(客户端)调用服务的时候,会先调用try接口(事务询问),再调用confirm(确认),cancel(回滚)。try会做一些资源的预处理。这里的事务协调者不是数据库层面的协调者了,而是应用层面的协调者决定提交还是回滚,而且他里面还有个最大化重试机制,里面有个定时任务,如果调用了cancel接口,回滚失败的话,协调器里面有个事件表,会在一个事件点内再次去调cancel。在支付领域,如果采用强一致性事务会影响性能,就可以采用tcc事务(最终一致性),先调用try接口冻结资产,再执行提交释放冻结资产或回滚冻结资产。实际中,不一定要完全按照他的标准来,可以自己根据业务需求封装,决定应用层调用服务的时候去具体调用接口的顺序。

上一篇:高并发场景下的限流策略
下一篇:session共享问题

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值