分布式事务的解决方案

分布式事务是什么:

  分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。

为什么会产生分布式事务:

  当我们的单个数据库的性能产生瓶颈的时候,我们可能会对数据库进行分区,这里所说的分区指的是物理分区,分区之后可能不同的库就处于不同的服务器上了,这个时候单个数据库的ACID已经不能适应这种情况了,而在这种ACID的集群环境下,再想保证集群的ACID几乎是很难达到,或者即使能达到那么效率和性能会大幅下降,最为关键的是再很难扩展新的分区了,这个时候如果再追求集群的ACID会导致我们的系统变得很差。

  我们假设有如下一个架构,这是一个简单的电商架构平台,两个应用节点,一个数据库,一个负载均衡器。这个架构下,每天会产生将近 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 成功执行了事务操作,那么就反馈给 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 阶段提交的区别在于:

(1) 在协调者和参与者中引入了超时机制,2pc 只有在协调者拥有超时机制,协调者在一定时间内没受到参与者的信息则默认为失败;

(2) 把 2 阶段提交的第一个阶段拆分成了两个步骤。

  cancommit 阶段:协调者向参与者发送 commit 请求,参与者如果可以提交就返回 yes 的响应,否则返回 No 的响应。这一阶段主要是确定分布式事务的参与者是否具备了完成commit 的条件,并不会执行事务操作。

1. 询问参与者是否可以执行事务提交操作。

2. 正常情况下只要能够顺利执行事务,就返回 yes 的响应,并进入预备状态。

  precommit 阶段:事务协调者根据参与者的反馈情况来决定是否继续执行事务的 precommit 操作,在这一个阶段,会有两种可能性,第一种是,在 cancommit 阶段所有参与者都反馈的是 yes,则会进行事务预执行。

1. 协调者向参与者发送 precommit 请求。

2. 参与者收到 precommit 请求后,执行事务操作,并把事务的 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 是商业项目,后来开源。论坛比较活跃,有问题可以随时解决。Atomikos 与SpringBoot集成参照 https://www.cnblogs.com/wuzhenzhao/p/10315130.html

TCC两阶段补偿方案:

  TCC是Try-Confirm-Cancel, 比如在支付场景中,先冻结一笔资金,再去发起支付。如果支付成功,则冻结资金进行实际扣除;如果支付失败,则取消资金冻结

  1. Try阶段:完成所有业务检查(一致性),预留业务资源(准隔离性)。
  2. Confirm阶段:确认执行业务操作,不做任何业务检查,只使用Try阶段预留的业务资源。
  3. Cancel阶段:取消Try阶段预留的业务资源。Try阶段出现异常时,取消所有业务资源预留请求。

互联网行业的数据一致性问题解决方案:

  目前互联网领域里有几种流行的分布式解决方案,但都没有像之前所说的 XA 事务一样形成 X/OpenDTP 那样的工业规范,而是仅仅在具体的行业里获得较多的认可;大家熟知的CAP 和 BASE 理论,对于 CAP 来说,对于共享数据的系统,由于网络分区问题的存在,我们只能满足 AP 或者 CP;对于 BASE 理论,满足基本可用。所以其实我们在落地数据一致性解决方案是,基本上都会选择一个平衡点,也就是酸碱平衡理论,ACID 是酸、 BASE 是碱;ACID 是强一致性、BASE 是弱一致性。强一致性代表数据库本身不会出现不一致,每个事务是原子的,或者成功或者失败,事物间是隔离的,互相完全不影响,而且最终状态是持久落盘的,

  CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的,他指出WEB服务无法同时满足一下3个属性:

  • 一致性(Consistency) : 客户端知道一系列的操作都会同时发生(生效)
  • 可用性(Availability) : 每个操作都必须以可预期的响应结束
  • 分区容错性(Partition tolerance) : 即使出现单个组件无法可用,操作依然可以完成

  具体地讲在分布式系统中,在任何数据库设计中,一个Web应用至多只能同时支持上面的两个属性。显然,任何横向扩展策略都要依赖于数据分区。因此,设计人员必须在一致性与可用性之间做出选择

  因此,数据库会从一个明确的状态到另外一个明确的状态.; 而 BASE 体现的是最终一致性,允许出现中间状态。所以对于对于服务来说,有很多的方案去选择:

1. 提供查询服务确认数据状态、

2.基于消息队列实现幂等操作对于重发保证数据的安全性

3.补偿操作(提供回调机制等操作)

4.定期校对(如银行的定期对账系统)

5.既然是基于Base的最终一致性,那么就是允许出现中间状态,这里可以采用状态机(其实可以当成一个状态字段)的方式去做,通过状态驱动数据变化(通过状态去修改数据where操作)。

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

  这个方案就是把一个业务流程中需要在一个事务里执行的多个相关业务接口包装整合到一个事务中,比如我们可以讲 A/B/C 整合为一个服务 D 来实现单一事务的业务流程服务。

什么是幂等:

  简单来说:重复调用多次产生的业务结果与调用一次产生的业务结果相同; 在分布式架构中,我们调用一个远程服务去完成一个操作,除了成功和失败以外,还有未知状态,那么针对这个未知状态,我们会采取一些重试的行为; 或者在消息中间件的使用场景中,消费者可能会重复收到消息。对于这两种情况,消费端或者服务端需要采取一定的手段,也就是考虑到重发的情况下保证数据的安全性。一般我们常用的手段

  1. 状态机实现幂等
  2. 数据库唯一约束实现幂等
  3. 通过tokenid的方式去识别每次请求判断是否重复

基于消息的最终一致性方案实践:

  在最终一致性这个方案上,也有两种选择方案,一种是基于可靠消息中间件来实现异步的最终一致性、另一种就是通过 MQ 来实现最大努力通知型。这两种都比较常见,比如对接过支付宝支付的 api,当你调用支付支付成功以后,支付宝会提供一个异步回调,调用配置好的指定的接口地址。在这个接口中,你可以获得支付宝的支付结果并根据结果做相应的处理。最后必须要返回一个 ack 给到支付宝的回调 api,告诉他这边已经处理成功了。否则,支付宝的异步回调会不断重试,当然有重试次数,以及重试的间隔时间。通过异步消息执行方案的本质是,把两个事务转化成两个本地事务,然后依靠消息本身的可靠性,以及消息的重试机制达到最终一致性。

posted @ 2019-01-30 17:14 吴振照 阅读(...) 评论(...) 编辑 收藏