美文网首页编程语言
分布式事务几种解决方案

分布式事务几种解决方案

作者: 小马过河R | 来源:发表于2020-08-26 09:16 被阅读0次

    本文小马参考部分文章,对分布式事务自己做了一下消化和总结整理。

    一般来说,事务是必须满足4个条件(ACID)::原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。 参看《MySQL事务隔离级别之理解篇》助于理解。那分布式事务怎么搞呢?

    一、两阶段提交(2PC)

    两阶段提交(Two-phase Commit,2PC),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。

    两阶段分别指准备阶段和提交阶段。

    1、准备阶段

    协调者询问参与者事务是否执行成功,参与者发回事务执行结果。图表达的不是很直观的话,可以参看这里

    2、提交阶段

    如果事务在每个参与者上都执行成功(测试还没有提交),事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。

    3、存在的问题

    同步阻塞 所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。

    单点问题 协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。

     数据不一致 在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。(小马觉得这是原子性操作问题)

    太过保守 任意一个节点失败就会导致整个事务失败,没有完善的容错机制。(不够这一点貌似也都这样,不是应该就是有一个失败都一起失败吗?)

    由此引申出来的3PC,三阶段提交又称3PC,其在两阶段提交的基础上增加了 CanCommit阶段,并引入了 超时机制。一旦事务参与者迟迟没有收到协调者的Commit请求,就会自动进行本地commit,这样相对有效地解决了协调者单点故障的问题。但是性能问题不一致问题仍然没有根本解决。

    二、补偿事务(TCC)

    TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作(让我想起tx的ams)

    它分为三个阶段:

    Try 阶段主要是对业务系统做检测及资源预留。(先锁资格)

    Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。(成功扣除资格)

    Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。(失败回滚资格)

    举个例子,假入 Bob 要向 Smith 转账,思路大概是: 我们有一个本地方法,里面依次调用

    首先在 Try 阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来。

    在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。

    如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。

    优点: 跟2PC比起来,实现以及流程相对简单了一些。

    缺点: 缺点还是比较明显的,在2,3阶段中都有可能失败--就是锁完资格也不提交也不回滚(数据的一致性比2PC也要差一些),小马认为是打破了原子性操作的原则。小马认为可以对锁加时间,但这个时间必须根据业务来判断,很难准确衡量。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码(也可以理解为回滚代码),在一些场景中,一些业务流程可能用TCC不太好定义及处理。

    这让我想起了tx的ams,当时处理的是DB增加到指定步数触发发礼包消耗资格。当步数增加成功,礼包发送失败,则需要将新增的步数数据和资格回滚减回去(配置对应的补偿接口),否则会出现步数加到指定步数,礼包没发,之后就没办法再次触发到这个指定步数的礼包了。

    曾经在《微服务架构设计模式》一书中被提到使用Saga模式解决分布式事务,原生分布式框架支持。

    Saga(一阶段提交)对于ACID的保证和TCC一样。注意以下对一致性的描述。

    这里提到log可以保证一致性

    上面这段啥意思呢?就是TCC有三个动作,try接入需要入侵原来的业务逻辑(锁资源),而saga少了一个try动作(就是我们通常理解的锁资格动作),只需要添加补偿接口即可,等失败了我再调用一个接口补偿一下。一阶段提交,无锁(锁是同步的),性能高,异步补偿动作容易实现(一个独立的补偿接口,假如第三方无法提供try接口也不怕),适用于此类场景。

    Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT(Java  2PC)、TCC、SAGA(对于ACID的保证和TCC一样) 和 XA (JAVA  支持XA 事务的数据库)事务模式,为用户打造一站式的分布式解决方案。(所以没有语言限制的是TCC、SAGA)。

    Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。

    三、本地消息表(异步确保)

    本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性(ACID),并且使用了消息队列来保证最终一致性

    这句话啥意思呢?你不是有DB吗?DB一般不是都具备事务机制吗,比如MySQL事务。那我能不能利用它把我的本地的A事务先执行完,写两个表A业务执行表,B本地消息表,本地消息表代表了这个和A业务需要一起执行的B业务。然后B本地消息表把这个请求放入消息队列,B业务去消费这个队列把剩下的B业务执行完毕。以此来异步达到事务的最终一致性。只要消费队列消息可靠性(一般kafka可靠性好)有保证不丢失,最终肯定会达到一致性。

    1、在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。

    2、之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。

    3、在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。

    优点: 一种非常经典的实现,避免了分布式事务(用本地DB事务 + 异步来转换),实现了最终一致性。(小马觉得这个思想非常秒,移花接木的感觉)

    缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。

    四、MQ 事务消息

    这一块涉及借助MQ的实现,小马就知识涉猎了,直接借用网络上的文章来说明,在此感谢原作者分享,原文

    有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。

    以阿里的 RocketMQ 中间件为例,其思路大致为:

    第一阶段Prepared消息,会拿到消息的地址。 第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。

    上面这段什么意思呢?我们看图来分析。

    1、消息发送者发送消息到消息队列。第一阶段Prepared消息,会拿到消息的地址。

    2、第二阶段执行本地事务,并发送确认消息到刚刚拿到地址的消息队列,并修改状态。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,如果发现了Prepared消息,它会向消息发送者确认。所以生产方(消息发送者)需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送(事务交给消息队列控制,就不需要本地消息表了)本地事务同时成功或同时失败。

    3、4、这个步骤就是和本地消息表后面一样的道理了,异步来执行剩下的事务逻辑,达到最终一致性。

    优点: 实现了最终一致性不需要依赖本地数据库事务

    缺点: 实现难度大,主流MQ不支持,RocketMQ事务消息部分代码也未开源。

    小马的疑问是,如果这个事务链条很长,处理起来是不是会相当复杂,而且异步造成的延迟是不是也是应该考虑的问题(必须先锁住所有资格)?

    总结

    分布式事务本身是一个技术难题,和微服务一样,也没有所谓的银弹吧,根据不同的场景和实际情况选择吧。这句话你信?反正小马也非深究亲践,待小马大杀四方一下回来再把酒话桑麻,分享补充,总结一下。

    参考文献:

    分布式事务的四种解决方案

    相关文章

      网友评论

        本文标题:分布式事务几种解决方案

        本文链接:https://www.haomeiwen.com/subject/rrxxsktx.html