分布式事务

https://zhuanlan.zhihu.com/p/183753774

https://blog.csdn.net/eluanshi12/article/details/84528393  TCC分布式事务的实现原理(补偿机制)

https://destiny1020.blog.csdn.net/article/details/92432059  分布式事务-TCC

1. 2pc

二阶段提交。 二阶段提交是一种强一致性设计,2PC 引入一个事务协调者的角色来协调管理各参与者(也可称之为各本地资源)的提交和回滚,二阶段分别指的是准备(投票)提交两个阶段。

缺点:

1.2PC 是一种尽量保证强一致性的分布式事务,因此它是同步阻塞的,而同步阻塞就导致长久的资源锁定问题,总体而言效率低,并且存在单点故障问题,在极端条件下存在数据不一致的风险。

  单点故障问题: 事务协调者挂了。 解决方案集群。通过选举。

  总体而言效率低: 同步阻塞的

  极端条件下存在数据不一致的风险:    协调者给某一个参与者发送了 提交/或者回滚命令,之后协调者和当前参与者就都挂了。 新的协调者通过选举产生了。 刚刚的参与者也好了。这时,协调者并不知道这个

参与者的命令有没有执行成功。协调者不知道自己是应该执行提交命令还是回滚命令。这时就容易参数数据不一致问题。

    1:每个参与者自身的状态只有自己和协调者知道. 新的协调者选举成功后。他得不到参与者自身的状态信息(他不知道以前的协调者有没有发参与者发送过 提交或者回滚的命令)  。 

      解决方案: 协调者可以在发送提

交或者回滚命令的时候,记录在日志表中。新的协调者来了。可以通过日志知道以前的协调者有没有给参与者发送过命令。

    2. 致命问题。  就算1的问题解决了。 协调者 不会知道参与者的命令是否执行成功了。 所以 在和极端的情况下。会出现数据不一致问题。

2,2PC 适用于数据库层面的分布式事务场景,而我们业务需求有时候不仅仅关乎数据库,也有可能是上传一张图片或者发送一条短信(同步非阻塞的设计更适于发短信场景)

3PC

3PC 的出现是为了解决 2PC 的一些问题,相比于 2PC 它在参与者中也引入了超时机制,并且新增了一个阶段使得参与者可以利用这一个阶段统一各自的状态

3PC 包含了三个阶段,分别是准备阶段预提交阶段提交阶段

看起来是把 2PC 的提交阶段变成了预提交阶段和提交阶段,但是 3PC 的准备阶段协调者只是询问参与者的自身状况,比如你现在还好吗?负载重不重?这类的。

而预提交阶段就是和 2PC 的准备阶段一样,除了事务的提交该做的都做了。

提交阶段和 2PC 的一样,让我们来看一下图。

1. 首先准备阶段的变更成不会直接执行事务,而是会先去询问此时的参与者是否有条件接这个事务,因此不会一来就干活直接锁资源,使得在某些资源不可用的情况下所有参与者都阻塞着。

2.而预提交阶段的引入起到了一个统一状态的作用,它像一道栅栏,表明在预提交阶段前所有参与者其实还未都回应,在预处理阶段表明所有参与者都已经回应了。

3.参与者引入了超时机制,参与者就不会傻等了,如果是等待提交命令超时,那么参与者就会提交事务了(那如果应该回滚的场景出现了。这里数据就会不一致),因为都到了这一阶段了大概率是提交的,如果是等待预提交命令超时,那该干啥就干啥了,反正本来啥也没干。

缺点:

1.但是多引入一个阶段也多一个交互,因此性能会差一些,而且绝大部分的情况下资源应该都是可用的,这样等于每次明知可用执行还得询问一次。

2.然而超时机制也会带来数据不一致的问题,比如在等待提交命令时候超时了,参与者默认执行的是提交事务操作,但是有可能执行的是回滚操作,这样一来数据就不一致了。

3PC 的引入是为了解决提交阶段 2PC 协调者和某参与者都挂了之后新选举的协调者不知道当前应该提交还是回滚的问题。(协调者不知道该如果做(是提交呢?还是回滚呢?))

1,3PC 就是通过引入预提交阶段来使得参与者之间的状态得到统一。新协调者来的时候发现有一个参与者处于预提交或者提交阶段,那么表明已经经过了所有参与者的确认了(都经历过了准备阶段),所以此时执行的就是提交命令(这也只能让协调者知道该如果做,但不能保证这样做一定对的)

  1.1 大部分情况下直接执行提交命令是没有问题的。如果参与者没有执行事务成功。3pc 直接执行了提交事务就是错的。这里就应该回滚事务。

让我们总结一下, 3PC 相对于 2PC 做了一定的改进:

1.引入了参与者超时机制,并且增加了预提交阶段使得故障恢复之后协调者的决策复杂度降低,但整体的交互过程更长了,性能有所下降,并且还是会存在数据不一致问题。

2. 3PC 的准备阶段是没有事务的。 所以准备阶段出问题了,不会造成阻塞。

3.TCC

1. 核心思想 是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。分为三个阶段:

    • Try 阶段:主要是对业务系统做检测(一致性)及资源预留(准隔离性)
    • Confirm 阶段:主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。(Confirm 操作满足幂等性。要求具备幂等设计,Confirm 失败后需要进行重试,一直不成功就人工介入。
      )
    • Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。(Cancel 操作满足幂等性)

2. 如果你要实现一个TCC分布式事务,首先你的业务的主流程以及各个接口提供的业务含义,不是说直接完成那个业务操作,而是完成一个Try的操作。

这个操作,一般都是锁定某个资源,设置一个预备的状态,冻结部分数据,等等,大概都是这类操作。

3. try 其实就是一种试探,看看个个服务处理数据是否ok,  try 要失败了,就走回滚操作(业务回滚)。 try 如果成功了。 默认 confirm 就一定会成功。 如果在执行confirm 时有个服务失败了。就会一直重试。保证其他服务都正常的执行confirm。 如果当前服务就一直不能confrm 成功。就通过记录的日志进行补偿操作(定时任务补偿/人工补偿)。

4. TCC 3步都要记录日志,增加 事务状态控制表。这样我们在处理 try  confirm cancael 的时候我们可以通过日志数据的唯一性加状态来来处理一些 特殊的场景 或进行数据的补偿。

   4.0  事务状态控制表 字段 1.主事务ID 2.分支事务ID 3.分支事务状态 (INIT(I) - 初始化CONFIRMED© - 已提交ROLLBACKED® - 已回滚)

   4.1  幂等处理  因为网络抖动等原因,分布式事务框架可能会重复调用同一个分布式事务中的一个分支事务的二阶段接口。

  4.2  空回滚  当没有调用参与方Try方法的情况下,就调用了三阶段的Cancel方法。 

  4.3  资源悬挂   当没有调用参与方Try方法的情况下,就调用了三阶段的Cancel方法,后面try 恢复了。有执行了try 进行了资源预留,所以造成了资源悬挂。

5. TCC 框架  ByteTCC、himly、tcc-transaction 

4. 最大努力通知型事务

  “最大努力通知型事务”是为解决跨网络(不同公司间的调用),跨服务的柔性事务的另一种解决方案,它是基于方法回调。(也是一思想,这种思想在其他分布式解决中也会用到 比如本地消息表)适用于对时间不敏感的业务,例如短信通知。。

1.业务活动的调用方(通知发起方),在完成本地业务活动处理后,调用业务被调用方。这个过程允许服务调用失败,
2.如果失败了,服务调用方能够通过重试尽力实现双方的数据一致性。此处的的重试就体现出了–最大努力 的特点。
3.然后是业务的被调用方。业务被调用方会暴露一个业务结果接收接口(或者叫回调接口也可以)给业务调用方,对收到的通知消息进行必要的校验,校验通过后执行本地业务,从而使业务达到闭环
4.同时,由于调用方对被调用方的通知次数是有限的,即我们不可能无限制的通知,因此业务活动调用方需要提供一个业务查询补偿接口供被调用方使用,被调用方会根据定时策略,向业务活动的调用方发起查询操作,从而对丢失的业务消息达到补偿的目的。
5.整个过程中,业务被调用方暴露给调用方的通知接收接口 以及 业务调用方提供给被调用方进行查询操作的接口均需要实现幂等,这样才能保证数据完整性不会被破坏,从而实现最终一致性。

  前提条件:

1. 调用方和被调用方 最好都留有调用日志记录表。调用方通过调用日志记录表来实现循环通知被调用方

2.调用方需要提供一个业务查询补偿接口供被调用方使用。
3.业务被调用方会暴露一个业务结果接收接口(或者叫回调接口也可以)给业务调用方,对收到的通知消息进行必要的校验,校验通过后执行本地业务,从而使业务达到闭环.

4. 回调接口都要幂等

5 本地消息表(适用于自己公司微服务之间的调用)

本地消息表其实就是利用了 各系统本地的事务来实现分布式事务

本地消息表顾名思义就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候 将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。

然后再去调用下一个操作,如果下一个操作调用成功了好说,消息表的消息状态可以直接改成已成功。

如果调用失败也没事,会有 后台任务定时去读取本地消息表,筛选出还未成功的消息再调用对应的服务,服务更新成功了再变更消息的状态。

这时候有可能消息对应的操作不成功,因此也需要重试,重试就得保证对应服务的方法是幂等的,而且一般重试会有最大次数,超过最大次数可以记录下报警让人工处理。

可以看到本地消息表其实实现的是最终一致性,容忍了数据暂时不一致的情况。

 6. 消息事务

RocketMQ 就很好的支持了消息事务,让我们来看一下如何通过消息实现事务。

一步先给 Broker 发送事务消息即半消息,半消息不是说一半消息,而是这个消息对消费者来说不可见,然后发送成功后发送方再执行本地事务

再根据本地事务的结果向 Broker 发送 Commit 或者 RollBack 命令。

并且 RocketMQ 的发送方会提供一个反查事务状态接口,如果一段时间内半消息没有收到任何操作请求,那么 Broker 会通过反查接口得知发送方事务是否执行成功,然后执行 Commit 或者 RollBack 命令。

如果是 Commit 那么订阅方就能收到这条消息,然后再做对应的操作,做完了之后再消费这条消息即可。

如果是 RollBack 那么订阅方收不到这条消息,等于事务就没执行过。

可以看到通过 RocketMQ 还是比较容易实现的,RocketMQ 提供了事务消息的功能,我们只需要定义好事务反查接口即可。

可以看到消息事务实现的也是最终一致性。

 

总结

可以看出 2PC 和 3PC 是一种强一致性事务,不过还是有数据不一致,阻塞等风险,而且只能用在数据库层面。

而 TCC 是一种补偿性事务思想,适用的范围更广,在业务层面实现,因此对业务的侵入性较大,每一个操作都需要实现对应的三个方法。

本地消息、事务消息和最大努力通知其实都是最终一致性事务,因此适用于一些对时间不敏感的业务。

原文地址:https://www.cnblogs.com/xiaowangbangzhu/p/15732970.html