分布式(3)分布式事务方案

整理本文的目的主要是为了方便实际开发中分布式事务实现的设计提供思路和案例

本文并非原创,只是对一些前辈大佬们的文章做的整理和自己的一些总结 感谢
https://www.cnblogs.com/jack1995/p/10922802.html
https://www.cnblogs.com/soundcode/p/5590710.html
https://www.jianshu.com/p/eb7a36d25b2a
https://www.jianshu.com/p/6efbe69e9c00
https://blog.csdn.net/jesseyoung/article/details/37970271

前言

为了便于讨论问题,这里先说明下一些基础理论

CAP理论 C:强一致性 A:高可用性 P:分布式容忍性 CAP理论指的是一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足其中两个。

BASE理论 基本可用(Basically Available)、软状态(Soft state)、最终一致(Eventually consistent) BASE理论指的是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观

强一致 当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。但是根据
CAP 理论,这种实现需要牺牲可用性。

弱一致性 系统并不保证后续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。

最终一致性 弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响。

大型互联网系统一般由多个独立的服务组成,如何解决分布式调用时候数据的一致性?
具体业务场景如下,比如一个业务操作,如果同时调用服务 A、B、C,需要满足要么同时成功;要么同时失败。A、B、C 可能是多个不同部门开发、部署在不同服务器上的远程服务
在系统分布式的情况来说,如果不想牺牲一致性,CAP 理论告诉我们只能放弃可用性,这显然不能接受(一般来说远程调用有3类结果:成功、失败、超时,前两类情况我们可以通过回滚本地事务来保证一致性,但是在超时情况下我们是没办法判断远程调用的执行结果的)。
在工程实践上,为了保障系统的可用性和降低维护成本,互联网系统大多将强一致性需求转换成最终一致性的需求,并通过系统执行幂等性的保证,来保证数据的最终一致性。

分布式开发的事务原则

大多数的分布式接口调用在设计上尽可能不要做分布式事务,因为加入分布式事务后会增加代码复杂度,影响系统性能,我们一般通过监控(邮件、短信提醒)和日志记录(报错时的完整日志)来保证数据异常时的快速定位、排查问题,修复数据。

分布式事务解决方案

本地消息表方案

eBay提出的一种分布式事务方案。核心是各个系统都要有自己的本地消息表,调用方需要有失败重发机制,被调用方保证接口幂等性

原理
A系统向B系统发送异步调用消息
①A系统在一个事务内完成本地业务操作和本地消息表插入操作。
②A系统将对B系统调用的消息发送到MQ中去。
③B系统接收到消息之后,先判断该消息是否本处理过,如果处理过将直接return(保证B系统被调用接口幂等性);如果未处理过,将在一个事务里完成本地消息表插入操作和本地业务操作
④B系统执行成功之后,就会更新刚才插入消息表数据的状态以及A系统消息表的状态(由B系统向A系统发送状态修改消息)。
⑤如果B系统处理失败了,那么就不会更新步骤3插入消息表数据的状态,所以A系统需要一个定时任务定时扫描自己的消息表,如果有没处理的消息,会再次发送到MQ中去,让B去处理。
⑥这个方案保证了最终一致性,哪怕B事务失败了。但是A会不断重发消息,直到B那边成功为止。而且也可能会出现B完成事务操作并且发送消息成功,但是A系统更新消息表失败的情况,这样会导致A系统不断重复发送无用消息(但是逻辑层面是正确的)
缺点
严重依赖于数据库的消息表来管理事务,并且需要耗费大量系统资源来定时处理消息,高并发难以扩展。

两阶段提交方案/XA方案

该方案有一个事务管理器的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先询问各个数据库是否prepared。如果每个数据库都回复成功,那么就正式提交事务,在各个数据库上执行commit操作;如果任何一个数据库回答失败,那么就回滚事务。
适用场景:
适合单应用场景下,跨多个库的分布式事务(即同一个应用内分库分表的场景),可以使用JOTM,Atomikos等JTA规范框架
缺点
因为严重依赖于数据库层面来处理复杂的事务,执行过程中所有参与者都处于阻塞状态,效率很低,不适合高并发的场景(可以加入超时机制来解决单点故障问题,即三阶段提交<3PC>)。

TCC方案

TCC全称是:Try、Confirm、Cancel。
原理:和2PC类同都用到了事务管理器,但是TCC使用到了补偿的概念。整个过程分为三个阶段:
①Try阶段:这个阶段说的是对各个服务的资源做检查以及对资源进行锁定或者预留。
②Confirm阶段: 执行实际的操作。
③Cancel阶段:如果操作中任何一个失败了,那么就需要回滚进行补偿。
适用场景
该方案使用场景较少,除非一致性要求极高的核心场景,比如常见的就是资金类的场景,那你可以用TCC方案了,自己编写大量的业务逻辑,自己判断一个事务中的各个环节是否成功,失败就执行补偿/回滚代码。而且最好是你的各个业务执行的时间都比较短。目前常见的实现框架有蚂蚁的DTS、LCN等等。
缺点 :
事物回滚严重依赖于自己写代码来回滚和补偿,会造成补偿代码巨大,还需要自己确定回滚逻辑,代码难维护。

可靠消息(MQ)最终一致性方案

先说结论,消息最终一致性方案真正解决的是上游事务和消息发送的原子性,并不能实现下游调用失败回滚时同时对上游事务进行回滚,当下游调用失败时需要人工或则业务兜底处理,适用于多数事务场景

普通消息的处理流程

在这里插入图片描述
1.消息生产者发送消息
2.MQ收到消息,将消息进行持久化,在存储中新增一条记录
3.返回ACK给生产者
4.MQ push 消息给对应的消费者,然后等待消费者返回ACK
5.如果消息消费者在指定时间内成功返回ack,那么MQ认为消息消费成功,在存储中删除消息,再执行第6步;如果MQ在指定时间内没有收到ACK,则认为消息消费失败,会尝试重新push消息,重复执行4、5、6步骤
6.MQ删除消息

普通消息处理存在的一致性问题

1.以订单创建为例,订单系统先创建订单(本地事务),再发送消息给下游处理;如果订单创建成功,然而消息没有发送出去,那么下游所有系统都无法感知到这个事件,会出现脏数据

public void processOrder() {
    // 订单处理(业务操作) 
    orderService.process();
    // 发送订单处理成功消息(发送消息) 
    sendBizMsg ();
}

2.如果先发送订单消息,再创建订单;那么就有可能消息发送成功,但是在订单创建的时候却失败了,此时下游系统却认为这个订单已经创建,也会出现脏数据。

public void processOrder() {
   // 发送订单处理成功消息(发送消息) 
    sendBizMsg ();
    // 订单处理(业务操作) 
    orderService.process();
}

3.那么我们将消息发送和业务处理放在同一个本地事务中来进行处理,如果业务消息发送失败,那么本地事务就回滚,这样是不是就能解决消息发送的一致性问题呢?

@Transactionnalpublic void processOrder() {
    try{
        // 订单处理(业务操作) 
        orderService.process(); 
        // 发送订单处理成功消息(发送消息) 
        sendBizMsg ();
    }catch(Exception e){
         事务回滚;   
    }}

消息发送和业务处理放在同一个本地事务时消息发送的异常情况分析

可能的情况一致性
订单处理成功,然后突然宕机,事务未提交,消息没有发送出去一致
订单处理成功,由于网络原因或者MQ宕机,消息没有发送出去,事务回滚一致
订单处理成功,消息发送成功,但是MQ由于其他原因,导致消息存储失败,事务回滚一致
订单处理成功,消息存储成功,但是MQ处理超时,从而ACK确认失败,导致发送方本地事务回滚不一致

从上面3类情况分析中我们可以了解到,使用普通的处理方式,无论如何,都无法保证业务处理与消息发送两边的一致性,其根本的原因就在于:远程调用,结果最终可能为成功、失败、超时;而对于超时的情况,处理方最终的结果可能是成功,也可能是失败,调用方是无法知晓的。 举个类比的例子,调用方先在本地写数据,然后发起RPC服务调用,但是处理方由于DB数据量比较大,导致处理超时,调用方在出现超时异常后,直接回滚本地事务,从而导致调用方这边没数据,而处理方那边数据却已经写入了,最终导致两边业务数据的不一致,而使用MQ进行异步请求则涉及到消息生产者和MQ服务器之间的一致性问题

事务消息

由于传统的处理方式无法解决消息生成者本地事务处理成功与消息发送成功两者的一致性问题,因此事务消息就诞生了,它实现了消息生成者本地事务与消息发送的原子性,保证了消息生成者本地事务处理成功与消息发送成功的最终一致性问题。
基本概念

  • 最终一致性
    RocketMQ是一种最终一致性的分布式事务,就是说它保证的是消息最终一致性,而不是像2PC、3PC、TCC那样强一致分布式事务
  • Half Message(半消息) 是指暂不能被Consumer消费的消息。Producer 已经把消息成功发送到了 Broker
    端,但此消息被标记为暂不能投递状态,处于该种状态下的消息称为半消息。需要
    Producer对消息的二次确认后,Consumer才能去消费它。
  • 消息回查 由于网络闪段,生产者应用重启等原因。导致 Producer 端一直没有对 Half Message(半消息) 进行
    二次确认。此时Brock服务器会定时扫描长期处于半消息的消息,会主动询问 Producer端
    该消息的最终状态(Commit或者Rollback),该消息即为 消息回查。
事务消息处理的流程

在这里插入图片描述
1、A服务先发送个Half Message给Brock端,消息中携带 B服务的处理信息。
2、当A服务知道Half Message发送成功后,那么开始第3步执行本地事务。
3、执行本地事务(会有三种情况1、执行成功。2、执行失败。3、网络等原因导致没有响应)
4.1)、如果本地事务成功,那么Product像Brock服务器发送Commit,这样B服务就可以消费该message。
4.2)、如果本地事务失败,那么Product像Brock服务器发送Rollback,那么就会直接删除上面这条半消息。
4.3)、如果因为网络等原因迟迟没有返回失败还是成功,那么会执行RocketMQ的回调接口,来进行事务的回查。
从上面流程可以得知只有A服务本地事务执行成功 ,B服务才能消费该message。
注意点:
1)由于MQ通常都会保证消息能够投递成功,因此,如果业务没有及时返回ACK结果,那么就有可能造成MQ的重复消息投递问题。因此,对于消息最终一致性的方案,消息的消费者必须要对消息的消费支持幂等,不能造成同一条消息的重复消费的情况。
2)如果回查,那么一定要先查看当前事务的执行情况,再看是否需要重新执行本地事务。想象下如果出现发送完半消息在执行本地事务完后发生系统故障而引起的回查,如果不先查看当前事务的执行情况,而是直接执行事务,那么就相当于成功执行了两个本地事务。

MQ最终一致性的理解

举个例子:假设 A 给 B 转 100块钱,同时它们不在一个服务上,我们的目的是就是让 A 减100块钱,B 加100块钱
实际情况可能有四种:
1)就是A账户减100 (成功),B账户加100 (成功)
2)就是A账户减100(失败),B账户加100 (失败)
3)就是A账户减100(成功),B账户加100 (失败)
4)就是A账户减100 (失败),B账户加100 (成功)
这里 第1和第2 种情况是能够保证事务的一致性的,但是 第3和第4 是无法保证事务的一致性的。而使用MQ最终一致性方案后能保证情景4不会出现,因为如果A服务本地事务都失败了,那B服务则不会执行任何操作,因为消息压根就不会传到B服务。
那么情景3 A账户减100 (成功),B账户加100 (失败) 是否可能存在?
答案是会的,因为A服务只负责当我消息执行成功了,保证消息能够送达到B,至于B服务接到消息后最终执行结果A并不保证。
那B服务失败怎么办?
如果B最终执行失败,几乎可以断定就是代码有问题所以才引起的异常,因为消费端MQ有重试机制,如果不是代码问题一般重试几次就能成功(ACK机制)。如果是代码的原因引起多次重试失败后,也没有关系,将该异常记录下来,由人工处理,人工兜底处理后,就可以让事务达到最终的一致性。

最大努力通知方案

由一个中间服务接受请求消息,记录请求,服务调用,失败重试等,一般用于防止网络延迟或则系统短时故障时请求丢失的情况
原理
①系统A本地事务执行完毕之后,发送消息到MQ。
②这里会有个专门消费MQ的最大努力通知服务,这个服务会消费MQ然后写入数据库中记录下来,或者放入一个内存队列,接着调用系统B的接口。
③要是B执行成功了就ok了。要是B执行失败了,那么最大努力通知服务就会定时尝试重新调用系统B,反复N次,最后还是不行就放弃。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值