分布式事务

前言

  随着互联网的发展,传统的单体应用难以支持多用户的使用问题。从而出现了各种的拆分,而对于数据库的垂直拆分和水平拆分,如何保证其数据一致性,又是一大难题。对于系统服务化拆分,又会遇到系统一致性问题。

事务问题

  对于数据库事务。可以看我的另一篇博客,MySql必须知道的一些知识(二)锁机制与事务问题
分库分表导致跨库事务:
  当一个服务操作多个不同的库时,不能够依赖数据库自身的数据库事务实现数据一致。比如删除数据库A的一条记录的同时,也要更改数据库B的数据,此时其中一个数据库操作失败,便会导致数据不一致问题。

DTP模型与XA规范

国际开放组织Open Group提出来了DTP模型和XA规范

DTP模型

模型元素

DTP模型:(Distributed Transaction Processing)分布式事务处理模型中有5个角色

  • 应用程序(Application Program ,简称AP):决定事务的开始和结束,并且在事务边界内对资源进行操作。
  • 资源管理器(Resource Manager,简称RM):如数据库等,并提供访问资源的方式。
  • 事务管理器(Transaction Manager ,简称TM):负责处理事务,监控事务的执行,负责事务的提交和回滚等操作。
  • 通信资源管理器(Communication Resource Manager,简称CRM):控制一个TM域(TM domain)内或者跨TM域的分布式应用之间的通信。
  • 通信协议(Communication Protocol,简称CP):提供CRM提供的分布式应用节点之间的底层通信服务。
模型实例

一个模型实例,至少有3个组成部分:AP、RMs、TM。如下所示:

这张图类似于我们之前提到的跨库事务的概念,即单个应用需要操作多个库。在这里就是一个AP需要操作多个RM上的资源。AP通过TM来声明一个全局事务,然后操作不同的RM上的资源,最后通知TM来提交或者回滚全局事务。
特别的,如果分布式事务需要跨多个应用,类似于我们前面的提到的分布式事务场景中的服务化,那么每个模型实例中,还需要额外的加入一个通信资源管理器CRM。
下图中演示了2个模型实例,如何通过CRM来进行通信:

在这里插入图片描述
CRM作为多个模型实例之间通信的桥梁,主要作用如下:

基本的通信能力:从这个角度,可以将CRM类比为RPC框架,模型实例之间通过RPC调用实现彼此的通信。这一点体现在AP、CRM之间的连线。

事务传播能力:与传统RPC框架不同的是,CRM底层采用OSI TP(Open Systems Interconnection — Distributed Transaction Processing)通信服务,因此CRM具备事务传播能力。这一点体现TM、CRM之间的连线。

事务管理器作用域 (TM domain)

一个TM domain中由一个或者多个模型实例组成,这些模型实例使用的都是同一个TM,但是操作的RMs各不相同,由TM来统一协调这些模型实例共同参与形成的全局事务(global transaction)。

下图展示了一个由四个模型实例组成的TM Domain,这四个模型实例使用的都是同一个事务管理器TM1。

TM domain只是列出了最终参与到一个全局事务中,有哪些模型实例,并不关心这些模型实例之间的关系。这就好比,有一个班级,我们只是想知道这个班级中每位同学的名字,但是并不是关心谁是班长、谁是学习委员等。
不过显然的,当一个TM domain中存在多个模型实例时,模型实例彼此之间存在一定的层级调用关系。这就是全局事务的树形结构。

全局事务树形结构(Global Transaction Tree Structure)

当一个TM domain中,存在多个模型实例时,会形成一种树形条用关系,如下图所示:

其中:
发起分布式事务的模型实例称之为root节点,或者称之为事务的发起者,其他的模型实例可以统称为事务的参与者。事务发起者负责开启整个全局事务,事务参与者各自负责执行自己的事务分支。
而从模型实例之间的相互调用关系来说,调用方称之为上游节点(Superior Node),被调用方称之为下游节点(Subordinate Node)。

小结:通过对DTP模型的介绍,我们可以看出来,之前提到的分布式事务的几种典型场景实际上在DTP模型中都包含了,甚至比我们考虑的还复杂。DTP模型从最早提出到现在已经有接近30年,到如今依然适用,不得不佩服模型的设计者是很有远见的。

XA规范

在DTP本地模型实例中,由AP、RMs和TM组成,不需要其他元素。AP、RM和TM之间,彼此都需要进行交互,如下图所示:

这张图中(1)表示AP-RM的交互接口,(2)表示AP-TM的交互接口,(3)表示RM-TM的交互接口。也就是说XA规范的最主要的作用是,就是定义了RM-TM的交互接口,下图更加清晰了演示了XA规范在DTP模型中发挥作用的位置,从下图中可以看出来,XA仅仅出现在RM和TM的连线上。

XA规范除了定义的RM-TM交互的接口(XA Interface)之外,还对两阶段提交协议进行了优化。 一些读者可能会误认为两阶段提交协议是在XA规范中提出来的。事实上: 两阶段协议(two-phase commit)是在OSI TP标准中提出的;在DTP参考模型(<>)中,指定了全局事务的提交要使用two-phase commit协议;而XA规范(<< Distributed Transaction Processing: The XA Specification>>)只是定义了两阶段提交协议中需要使用到的接口,也就是上述提到的RM-TM交互的接口,因为两阶段提交过程中的参与方,只有TM和RMs。
XA规范中定义的RM 和 TM交互的接口如下图所示:

分布式一致性协议

两段提交协议(2pc)

2pc把分布式事务拆成两个阶段。分别是准备阶段和提交阶段。两个阶段都是由TM(事务管理器)发起,TM称为协调者,RM(资源管理器)称为参与者。
  准备阶段:TM向各个RM发起准备请求,当RM收到请求后,判断自己是否可执行相应的操作,可以则执行持久化操作,锁定资源,mysql会写入redo(数据修改之后)或undo(数据修改之前)日志。不可以则回滚自己已经做的操作,并想TM返回否定答复,此时该事务被抛弃。
  提交阶段:当所有的RM都能返回准备成功,即每个RM的操作执行成功,此时TM发起提交请求,RM则提交事务,释放资源,如果在准备阶段有RM返回一个操作失败响应,则TM发起请求回滚所有已执行操作的RM,即执行undo,之后释放资源。

2pc的问题

  2pc的思想的确能够做到数据一致性,但是我们也发现在准备阶段时,RM会锁定资源,这样也会带来一些问题。

  • 阻塞:必须每个RM都执行成功操作或者失败操作完成,才能够释放资源。对于每个指令收到响应期间资源一直锁定
  • 单体故障:当TM宕机,但此时正在执行相应的事务。此时所有的RM都会锁定资源,即使切换另一个TM,也是无法进行释放资源。
  • 脑裂: 当协调者发起提交请求,但是有个别的没有接收到请求,则会发生数据不一致问题。

三段提交协议(3pc)

  3pc在2pc的基础上进行优化,通过加入超时的机制解决了2pc阻塞问题和单体问题。3pc有以下3个阶段:
  询问阶段(CanCommit): 协调者发起询问请求,等待所有的参与者的回复,参与者满足执行事务条件则回复yes,反之no。
  准备阶段(PreCommit): 有回复NO或超时无接收响应则终止事务。当全部参与者回复yes则进入该阶段。执行事务操作,但不提交和2pc类似。最后向协调者返回ack或no。中断事务时,当参与者收到协调者的abort请求或者未发送abort请求导致超时,则全部参与者进行事务终止。
  提交阶段(doCommit): 进入该阶段发起提交事务请求,参与者收到doCommit请求后,会正式执行事务提交,并释放整个事务期间占用的资源。返回ack或no,任何一个参与者反馈NO,或者等待超时后协调者尚无法收到所有参与者的反馈时,向所有参与者发出abort请求。执行回滚,释放资源。
  3pc解决了堵塞和单点故障,但还是有脑裂问题。当协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。

柔性事务

  2pc和3pc都是刚性事务(锁表),做到强一致性,而我们也可以基于base理论到达最终一致性的事务。

补偿事务(TCC)

  说完2pc和3pc,其实这两种方式已经可以做到分布式事务了,但是真正使用的人并不多,主要是其会产生阻塞或不一致问题,并且其复杂性和性能问题也是不适合使用的原因,那么,在2008年阿里的程立提出的tcc得到了大力的响应,此处放一个当时的ppt链接[https://wenku.baidu.com/view/be946bec0975f46527d3e104.html]
  TCC协议其实就是try-confirm-cancel三步骤。主业务发起发起操作,各个从业务先进行try操作,如果所有业务操作成功。则confirm,进行真正执行。否则撤销执行。下面详细讲一下

  • try操作: 完成所有业务检查。预留业务资源
  • confirm: 真正执行业务。不作任何业务检查。只使用 Try 阶段预留的业务资源。操作满足幂等性
  • cancel(补偿): 释放 Try 阶段预留的业务资源。操作满足幂等性。

    至于我们如何在项目中使用tcc,一般我们是基于tcc框架,这里是tcc-transaction的文档链接
      我们发现如果网络不通或其它原因导致了confirm获取cancel操作失败,就会产生数据不一致问题。那还有没有其它的方案呢?
本地消息表

  这个方案主要是通过本地事务保证的。遵循BASE理论,是最终一致性的,为了解释的明白,我画了个图。

  实现思路上图描述的很清楚了。我们分析会有什么问题。在我分析之前自己可以思考一番。
1、如果1或2出现异常,则直接回滚操作失败,不会有任何问题。
2、如果发送消息到mq出现异常,我们通过Spring的@Transactional或自己异常捕获进行回滚不会有问题。但是,重点,如果发送消息超时引发回滚,而消息已经发送到mq,此时,数据就会产生不一致问题了。那当发生这种情况我们可以删除消息。
3、如果5发生失败呢,那其实这个可以进行重试。所以消费方就要实现相应的幂等。但是重试操作不代表一直重试。有时需要人工介入。
4、当看到第7步的时候,可能有人有疑惑。为什么要去查一下消息表,其实是为了保证不能多次消费问题。我们先删掉第7步,mq发送多条消息过来,多次执行8和9,肯定不行的。
5、如果发送消息比较多或消息堆积或其它问题导致消费方并发较大,我们还要是在消费方对消息表数据实现redis加锁,即实现双重检查锁,这样就能保证数据的一致性。
6、定时任务补偿,其作用就是定时查询本地消息表中未被处理的数据,发送消息到MQ。如果没5秒执行一次,而5秒之内消费方没有消费完,即本地消息表的数据状态没有都被改变,此时会再次发送相同消息,因为实现幂等,多次发送并不会发生不一致,但是定时任务的间隔应该有个合适的正确时间。

MQ事务

  有一些第三方的MQ是支持事务消息的,比如RocketMQ,开源的版本从4.3.0开始支持事务消息,它的方式也是类似于采用的二阶段提交。但是我只学过rabbitMQ和kafka,所以这里我便不多说了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈大侠在江湖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值