当前位置: 首页 > news >正文

分布式事务及其实现方案

文章目录

  • 分布式事务概念
  • 分布式事务实现方案
    • 2PC
      • 优缺点:
    • TCC
      • TCC举例:
      • TCC适用场景:
    • 本地消息表
    • MQ事务
    • Saga事务
      • Saga的组成:
      • Saga恢复策略:
      • Saga举例说明:
      • Saga隔离性问题:

分布式事务概念

分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上

简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

要实现分布式事务,有如下几种常见的解决方案:

分布式事务实现方案

2PC

说到2PC就不得不聊数据库分布式事务中的 XA Transactions。

在这里插入图片描述
如上图,在XA协议中分为两阶段:

  • 第一阶段:事务管理器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交。
  • 第二阶段:事务协调器要求每个数据库提交数据,或者回滚数据。

优缺点:

优点:

  • 尽量保证了数据的强一致,实现成本较低,在各大主流数据库都有自己实现,对于MySQL是从5.5开始支持。

缺点:

  • 单点问题:事务管理器在整个流程中扮演的角色很关键,如果其宕机,比如在第一阶段已经完成,在第二阶段正准备提交的时候事务管理器宕机,资源管理器就会一直阻塞,导致数据库无法使用。
  • 同步阻塞:在准备就绪之后,资源管理器中的资源一直处于阻塞,直到提交完成,释放资源。
  • 数据不一致:两阶段提交协议虽然为分布式数据强一致性所设计,但仍然存在数据不一致性的可能,比如在第二阶段中,假设协调者发出了事务commit的通知,但是因为网络问题该通知仅被一部分参与者所收到并执行了commit操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。

总的来说,XA协议比较简单,成本较低,但是其单点问题,以及不能支持高并发依然是其最大的弱点。

TCC

关于TCC(Try-Confirm-Cancel)的概念,最早是由Pat Helland于2007年发表的一篇名为《Lifebeyond Distributed Transactions:an Apostate’s Opinion》的论文提出。 TCC事务机制相比于上面介绍的XA,解决了其几个缺点:

  1. 解决了协调者单点,由主业务方发起并完成这个业务活动。业务活动管理器也变成多点,引入集群。
  2. 同步阻塞:引入超时,超时后进行补偿,并且不会锁定整个资源,将资源转换为业务逻辑形式,粒度变小。
  3. 数据一致性,有了补偿机制之后,由业务活动管理器控制一致性。

对于TCC做出如下的解释:

  • Try阶段:尝试执行,完成所有业务检查(一致性),预留必须业务资源(准隔离性)。
  • Confirm阶段:确认执行真正执行业务,不作任何业务检查,只使用Try阶段预留的业务资源,Confirm操作满足幂等性。要求具备幂等设计,Confirm失败后需要进行重试。
  • Cancel阶段:取消执行,释放Try阶段预留的业务资源 Cancel操作满足幂等性Cancel阶段的异常和Confirm阶段异常处理方案基本上一致。

TCC举例:

举例:一个人用一百块钱的存款买30元的东西,此时实现的就是:扣减用户余额的业务。假设账户A原来余额是100,需要余额扣减30元。

  • 阶段一( Try ):检查余额是否充足,如果充足则冻结金额增加30元,可用余额扣除30

初识余额:

image-20220902181217202

余额充足,可以冻结:

image-20220902181229657

此时,总金额 = 冻结金额 + 可用金额,数量依然是100不变。事务直接提交无需等待其它事务。

  • 阶段二(Confirm):假如要提交(Confirm),则冻结金额扣减30

确认可以提交,不过之前可用金额已经扣减过了,这里只要清除冻结金额就好了:

image-20220902181335085

此时,总金额 = 冻结金额 + 可用金额 = 0 + 70 = 70元

  • 阶段二(Canncel):如果要回滚(Cancel),则冻结金额扣减30,可用余额增加30

需要回滚,那么就要释放冻结金额,恢复可用金额:

image-20220902181419375

TCC适用场景:

  • 强隔离性,严格一致性要求的活动业务。
  • 执行时间较短的业务。

本地消息表

本地消息表这个方案最初是ebay提出的,此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行

消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试

人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。

例如对于本地消息队列来说核心是把大事务转变为小事务,还是举上面用100元去买东西的例子:

  1. 当你扣钱的时候,你需要在你扣钱的服务器上新增加一个本地消息表,你需要把你扣钱和写入减去这个东西的库存到本地消息表放入同一个事务(依靠数据库本地事务保证一致性)。

  2. 这个时候有个定时任务去轮询这个本地事务表,把没有发送的消息,扔给商品库存服务器,叫他减去这个东西的库存,到达商品服务器之后这个时候得先写入这个服务器的事务表,然后进行扣减,扣减成功后,更新事务表中的状态。

  3. 商品服务器通过定时任务扫描消息表或者直接通知扣钱服务器,扣钱服务器本地消息表进行状态更新。

  4. 针对一些异常情况,定时扫描未成功处理的消息,进行重新发送,在商品服务器接到消息之后,首先判断是否是重复的,如果已经接收,在判断是否执行,如果执行在马上又进行通知事务,如果未执行,需要重新执行需要由业务保证幂等,也就是不会多扣一个东西的钱。

流程如下所示:
在这里插入图片描述

本地消息队列是BASE理论,是最终一致模型,适用于对一致性要求不高的场景,实现这个模型时需要注意重试的幂等

MQ事务

在RocketMQ中实现了分布式事务,实际上其实是对本地消息表的一个封装,将本地消息表移动到了MQ内部,下面MQ事务简单介绍:

基本流程如下:

  1. Prepared消息,会拿到消息的地址。
  2. 执行本地事务。
  3. 通过第一阶段拿到的地址去访问消息,并修改状态。消息接受者就能使用这个消息。

流程图如下:
在这里插入图片描述

如果确认消息失败,在RocketMq Broker中提供了定时扫描没有更新状态的消息,如果有消息没有得到确认,会向消息发送者发送消息,来判断是否提交,在rocketmq中是以listener的形式给发送者,用来处理。

如下图的失败处理:

在这里插入图片描述

如果消费超时,则需要一直重试,消息接收端需要保证幂等。如果消息消费失败,这个就需要人工进行处理,因为这个概率较低,如果为了这种小概率时间而设计这个复杂的流程反而得不偿失。

Saga事务

Saga是30年前一篇数据库伦理提到的一个概念。其核心思想是将长事务拆分为多个本地短事务,由Saga事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。

Saga的组成:

每个Saga由一系列sub-transaction Ti 组成 每个Ti 都有对应的补偿动作Ci,补偿动作用于撤销Ti造成的结果,这里的每个T,都是一个本地事务。 可以看到,和TCC相比,Saga没有“预留 try”动作,它的Ti就是直接提交到库。
在这里插入图片描述

Saga的执行顺序有两种:
T1, T2, T3, …, Tn
T1, T2, …, Tj, Cj,…, C2, C1,其中0 < j < n

Saga恢复策略:

Saga定义了两种恢复策略:

  • 向前恢复,适用于必须要成功的场景,执行顺序是类似于这样的:T1, T2, …, Tj(失败), Tj(重试),…, Tn,其中j是发生错误的sub-transaction。该情况下不需要Ci。这里要注意的是,在saga模式中不能保证隔离性,因为没有锁住资源,其他事务依然可以覆盖或者影响当前事务。
  • 向后恢复,即上面提到的第二种执行顺序,其中j是发生错误的sub-transaction,这种做法的效果是撤销掉之前所有成功的sub-transation,使得整个Saga的执行结果撤销。

Saga举例说明:

还是拿100元买东西(一瓶水)的例子来说,这里定义:

T1=扣100元,T2=给用户加一瓶水,T3=减库存一瓶水;
C1=加100元,C2=给用户减一瓶水,C3=给库存加一瓶水;

我们一次进行T1,T2,T3如果发生问题,就执行发生问题的C操作的反向。

Saga隔离性问题:

上面说到的隔离性的问题会出现在,如果执行到T3这个时候需要执行回滚,但是这个用户已经把水喝了(另外一个事务),回滚的时候就会发现,无法给用户减一瓶水了。这就是事务之间没有隔离性的问题。

可以看见saga模式没有隔离性的影响还是较大,可以参照华为的解决方案:从业务层面入手加入一Session 以及锁的机制来保证能够串行化操作资源。也可以在业务层面通过预先冻结资金的方式隔离这部分资源, 最后在业务操作的过程中可以通过及时读取当前状态的方式获取到最新的更新。

相关文章:

  • Flink系列-背压(反压)
  • 随机森林实战(分类任务+特征重要性+回归任务)(含Python代码详解)
  • 面向对象编程原则(02)——单一职责原则
  • C++面向对象程序设计(第2版)第二章(类和对象的特性)知识点总结
  • 学习springboot杂乱无章的笔记
  • java计算机毕业设计红河旅游信息服务系统源码+数据库+系统+lw文档+mybatis+运行部署
  • Pytorch 实战 LESSON 16 深度学习视觉入门 上
  • 10.VScode下载---Windows64x
  • java计算机毕业设计互联网保险网站源码+数据库+系统+lw文档+mybatis+运行部署
  • Linux14 NAT网络配置原理 查看网络ip和网关 修改ip地址 指定ip方法 主机名与hosts映射 主机名解析过程
  • SPDK Vhost在线恢复:让I/O飞一会儿
  • 如何判断一个低代码平台是否专业?
  • 达利欧《原则》读书思考笔记
  • C语言动态内存管理、柔性数组(超详细版)
  • 【USB设备设计】-- CDC 设备开发(虚拟串口设备)
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • (ckeditor+ckfinder用法)Jquery,js获取ckeditor值
  • 【347天】每日项目总结系列085(2018.01.18)
  • Android Studio:GIT提交项目到远程仓库
  • JDK 6和JDK 7中的substring()方法
  • JS创建对象模式及其对象原型链探究(一):Object模式
  • niucms就是以城市为分割单位,在上面 小区/乡村/同城论坛+58+团购
  • Octave 入门
  • Redis中的lru算法实现
  • SpringBoot几种定时任务的实现方式
  • 爱情 北京女病人
  • 多线程 start 和 run 方法到底有什么区别?
  • 利用DataURL技术在网页上显示图片
  • 利用阿里云 OSS 搭建私有 Docker 仓库
  • 前端_面试
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 消息队列系列二(IOT中消息队列的应用)
  • 用element的upload组件实现多图片上传和压缩
  • (libusb) usb口自动刷新
  • (独孤九剑)--文件系统
  • (每日持续更新)jdk api之FileFilter基础、应用、实战
  • (学习日记)2024.03.25:UCOSIII第二十二节:系统启动流程详解
  • (转)linux下的时间函数使用
  • (转)Linux整合apache和tomcat构建Web服务器
  • .NET Core 控制台程序读 appsettings.json 、注依赖、配日志、设 IOptions
  • .net开发引用程序集提示没有强名称的解决办法
  • .net开源工作流引擎ccflow表单数据返回值Pop分组模式和表格模式对比
  • /run/containerd/containerd.sock connect: connection refused
  • @font-face 用字体画图标
  • [ 第一章] JavaScript 简史
  • [Android] Android ActivityManager
  • [Android]常见的数据传递方式
  • [C++]AVL树怎么转
  • [C++]C++基础知识概述
  • [CSS]盒子模型
  • [DevOps云实践] 彻底删除AWS云资源
  • [IE9] 解决了傲游、搜狗浏览器在IE9下网页截图的问题
  • [IM] [Webhook] Webhook实现IM平台机器人
  • [IOI2007 D1T1]Miners 矿工配餐
  • [javascript]Tab menu实现