分布式事务

Created at 2019-09-10 Updated at 2020-07-29 Category 分布式 Tag 分布式事务

柔性事务(CAP、BASE)分布式管理

TCC是二阶段协议的一种,并做了优化,2PC 一开始资源就被锁定了,TCC 的第一阶段只锁定了一部分资源

XA是事务管理器和资源之间的通信协议

XA 规范定义了两阶段提交协议中需要使用到的接口

关于分布式事务,主要讨论的是强一致性和最终一致性的解决方案。

  • 两阶段提交
  • 事件队列方案
  • TCC 补偿模式
  • 缓存数据最终一致性

一致性理论

CAP

  • 一致性(Consistency) : 客户端知道一系列的操作都会同时发生(生效)
  • 可用性(Availability) : 每个操作都必须以可预期的响应结束
  • 分区容错性(Partition tolerance) : 即使出现单个组件无法可用,操作依然可以完成
  1. 要想让数据有高可用性,就得写多份数据。

  2. 写多份的问题会导致数据一致性的问题。

  3. 数据一致性的问题又会引发性能问题

CAP 原理是指,这三个要素最多同时只能实现两点。分区容错性是必须要保证的,对于大多数 web 应用,并不需要强一致,所以牺牲一致性换取高可用性,最求最终一致性,是目前多数分布式数据库产品的方向。

CP :Zookeeper

AP:最终一致性(BASE)

Base 理论

  • Basically Available(基本可用):指分布式系统在出现故障时,允许损失部分的可用性来保证核心可用
  • Soft state(软状态):指允许分布式系统存在中间状态(比如订单:待付款、已付款、已发货),该中间状态不会影响到系统的整体可用性
  • Eventually consistent(最终一致性):指分布式系统中的所有副本数据经过一定时间后,最终能够达到一致的状态

一致性模型

  • 强一致性:数据更新成功后,任意时刻所有副本中的数据都是一致的,一般采用同步的方式实现。
  • 弱一致性:数据更新成功后,系统不承诺立即可以读到最新写入的值,也不承诺具体多久之后可以读到。
  • 最终一致性:弱一致性的一种形式,数据更新成功后,系统不承诺立即可以返回最新写入的值,但是保证最终会返回上一次更新操作的值。

分布式事务解决方案

消息队列、两阶段提交、TCC

两阶段提交(Two Phase Commit)

两阶段提交又称为 2PC,是一个 强一致、中心化的原子提交协议,这种解决方案属于牺牲了一部分可用性来换取的一致性。

在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时,为了保持事务的 ACID 特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)

第一阶段(投票阶段)

  1. 事务询问:协调者会问所有的参与者节点,是否可执行提交操作(Prepare)
  2. 执行本地事务:各个参与者开始事务执行的准备工作:如:为资源上锁,预留资源,写 undo/redo log……
  3. 各参与者向协调者反馈事务询问的响应:参与者响应协调者,如果事务的准备工作成功,则回应“可以提交”,否则回应“拒绝提交”。

第二阶段(提交/执行阶段)

  1. 如果所有的参与者都回应“可以提交”,那么,协调者向所有的参与者发送“正式提交”的命令。参与者完成正式提交,并释放所有资源,然后回应“完成”,协调者收集各结点的“完成”回应后结束这个 Global Transaction。
  2. 如果有一个参与者回应“拒绝提交”,那么,协调者向所有的参与者发送“回滚操作”,并释放所有资源,然后回应“回滚完成”,协调者收集各结点的“回滚”回应后,取消这个 Global Transaction。

2PC 缺点

  1. 性能问题:所有参与者资源和协调者资源都是被锁住的,只有当所有节点准备完毕,协调者才会通知进行全局提交,参与者进行本地事务提交后才会释放资源。这样的过程会比较漫长,对性能影响比较大。
  2. 单节点故障:一旦协调者发生故障,参与者会一直阻塞下去。

2PC 出现单点问题的三种情况

  1. 协调者正常,参与者宕机:​引入超时机制,如果协调者在超过指定的时间还没有收到参与者的反馈,事务就失败,向所有节点发送终止事务请求。

  2. 协调者宕机,参与者正常:引入协调者备份,同时协调者需记录操作日志.当检测到协调者宕机一段时间后,协调者备份取代协调者,并读取操作日志,向所有参与者询问状态。

  3. 协调者和参与者都宕机

    1. 发生在第一阶段: 因为第一阶段,所有参与者都没有真正执行commit,所以只需重新在剩余的参与者中重新选出一个协调者,新的协调者再重新执行第一阶段和第二阶段就可以了。

    2. 发生在第二阶段 并且 挂了的参与者在挂掉之前没有收到协调者的指令。也就是上面的第4步挂了,这是可能协调者还没有发送第4步就挂了。这种情形下,新的协调者重新执行第一阶段和第二阶段操作。

    3. 发生在第二阶段 并且 有部分参与者已经执行完commit操作。就好比这里订单服务A和支付服务B都收到协调者 发送的commit信息,开始真正执行本地事务commit,但突发情况,Acommit成功,B却挂了。这个时候目前来讲数据是不一致的。虽然这个时候可以再通过手段让他和协调者通信,再想办法把数据搞成一致的,但是,这段时间内他的数据状态已经是不一致的了! 2PC 无法解决这个问题。

      如果因为网络原因得不到结果,就重试(下游接口实现幂等性),多次重试都失败就记日志,人工处理

三阶段提交

三阶段提交协议(3PC)主要是为了解决两阶段提交协议的阻塞问题,2pc存在的问题是当协作者崩溃时,参与者不能做出最后的选择。因此参与者可能在协作者恢复之前保持阻塞。

三段提交的核心理念是:在询问的时候并不锁定资源,除非所有人都同意了,才开始锁资源

三阶段提交的两个改动点:

  1. 引入超时机制,在协调者和参与者中都引入超时机制
  2. 在第一阶段和第二阶段中插入一个准备阶段,保证了在最后提交阶段之前各参与节点的状态是一致的。

3PC 把 2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommitPreCommitDoCommit三个阶段,相较于2PC而言,多设置了一个缓冲阶段保证了在最后提交阶段之前各参与节点的状态是一致的。

CanCommit

协调者询问参与者尝试获取数据库锁,如果可以,就返回Yes。

PreCommit

与 2PC 相比协调者和参与者都引入超时机制(2PC 只有协调者可以超时)。主要是避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地 commit 从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。

DoCommit

与 2PC 一样。

补偿事务 TCC(Try Confirm Cancel)

TCC事务处理流程和 2PC 二阶段提交类似,不过 2PC通常都是在跨库的DB层面,而TCC本质就是一个应用层面的2PC。

下游的每个系统,都需要开发 try,commit,cancel 三个接口。

TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:

  • Try 阶段:主要是对业务系统做检测及资源预留
  • Confirm 阶段:主要是对业务系统做确认提交,Try 阶段执行成功并开始执行 Confirm 阶段时,默认 Confirm 阶段是不会出错的。即:只要 Try 成功,Confirm 一定成功。
  • Cancel 阶段:主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。

TCC 与 XA/JTA 比较

  • XA 是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁
  • TCC 是业务层面的分布式事务,最终一致性不会一直持有资源的锁

两阶段提交内部过程是对开发者屏蔽的,事务管理器在两阶段提交过程中,从 prepare 到 commit/rollback 过程中,资源实际上一直都是被加锁的。如果有其他人需要更新这两条记录,那么就必须等待锁释放。

TCC中的两阶段提交并没有对开发者完全屏蔽,也就是说从代码层面,开发者是可以感受到两阶段提交的存在。

  1. try 过程的本地事务,是保证资源预留的业务逻辑的正确性。
  2. confirm/cancel 执行的本地事务逻辑确认/取消预留资源,以保证最终一致性,也就是所谓的补偿型事务
    由于是多个独立的本地事务,因此不会对资源一直加锁。

参考文章

Site by Cellophane using Hexo & Random

Hide