分布式事务的解决方案

分布式事务是什么:

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

为何会产生分布式事务:

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

  咱们假设有以下一个架构,这是一个简单的电商架构平台,两个应用节点,一个数据库,一个负载均衡器。这个架构下,天天会产生将近 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 里的分布式事务编程接口规范-JTAapi

  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集成参照 http://www.noobyard.com/article/p-enubewmz-bn.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,告诉他这边已经处理成功了。不然,支付宝的异步回调会不断重试,固然有重试次数,以及重试的间隔时间。经过异步消息执行方案的本质是,把两个事务转化成两个本地事务,而后依靠消息自己的可靠性,以及消息的重试机制达到最终一致性。