图解分布式事务实现原理(一)

news2024/11/25 22:59:21

参考

本文参考https://zhuanlan.zhihu.com/p/648556608,在小徐的基础上做了个人的笔记。


分布式事务场景

事务核心特性

在聊分布式事务之前,我们先理清楚有关于 “事务” 的定义.

事务 Transaction,是一段特殊的执行程序,其需要具备如下四项核心性质:

在这里插入图片描述
当涉及到事务处理时,有四个核心要素,它们被称为事务的ACID四大特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。这些特性在关系型数据库范围内通常较容易实现,因为数据和操作都在同一个数据库内。然而,当一个事务涉及到跨越不同的数据库、服务或存储组件时,这个问题就变得更加复杂和有趣,这正是我们今天要重点讨论的“分布式事务”领域所涉及的问题。

分布式事务概念

由于数据库的拆分或分布式架构(微服务)不可避免的带来了分布式事务的问题。如下为当前针对分布式事务的工程实践和处理方式。

  • 基于业务逻辑和应用场景最小化分布式事务边界
    言外之意就是说应该在设计阶段尽可能规避没必要的分布式事务场景。
  • 基于 XA 的强一致性事务
    XA模式是传统的强一致性分布式事务解决方案,性能较低且锁资源竞争突出。XA的实现方式存在长事务风险且锁资源严重。在实际业务中使用较少,本文不做更多讨论。
  • 追求最终一致性的柔性事务
    柔性事务通过放宽对强一致性要求,而是通过反向补偿来达到最终一致性,同时换取系统吞吐量的提升和缓解锁资源竞争。目前,Seata 框架提供了多种事务管理模式来支持柔性事务的落地实现。

虚拟业务场景设计

下面我们通过一个常见的场景问题引出有关于分布式事务的话题.

假设我们在维护一个电商后台系统,每当在处理一笔来自用户创建订单的请求时,需要执行两步操作:

  • 从账户系统中,扣减用户的账户余额
  • 从库存系统中,扣减商品的剩余库存

从业务流程上来说,这个流程需要保证具备事务的原子性,即两个操作需要能够一气呵成地完成执行,要么同时成功,要么同时失败,不能够出现数据状态不一致的问题,比如发生从用户账户扣除了金额但商品库存却扣减失败的问题。

然而从技术流程上来讲,两个步骤是相对独立的两个操作,底层涉及到的存储介质也是相互独立的,因此无法基于本地事务的实现方式。

在这里插入图片描述

分布式事务的实现确实面临着很高的难度,但在业界已经提出了一套被广泛认可并应用的解决方案。这些解决方案将在后续的章节中介绍。在此之前,我们需要明确在分布式事务的实现中,所谓的数据状态一致性需要做出妥协:

  • 数据状态一致性:在分布式事务中,我们所谈论的数据状态一致性指的是数据的最终一致性,而不是即时一致性。即时一致性通常在分布式系统中难以实现,因为网络延迟和不同组件之间的通信可能导致即时一致性变得不切实际。因此,在分布式环境中,我们更关注确保数据最终达到一致的状态,即经过一段时间后,系统的各个节点都会收敛到相同的数据状态。

  • 百分之百的一致性无法保证:分布式事务中的一个根本挑战是无法百分之百地保证数据状态的一致性。这是因为分布式系统的稳定性和一致性受到网络环境的影响,以及与第三方系统的交互等多种因素的影响。即使采用了复杂的分布式事务协议和机制,也难以消除所有可能的故障和不一致性。

因此,分布式事务的实现需要在数据一致性和系统性能之间寻找平衡。通常情况下,分布式系统会采用某种程度的最终一致性,同时尽力减小数据不一致性的发生概率。这可能涉及到使用分布式事务协议、分布式锁、版本控制等技术手段,以确保在大多数情况下数据状态是一致的。但在特殊情况下,仍然需要处理可能的不一致性问题,并设计恢复机制来纠正这些问题。因此,分布式事务的实现需要权衡各种因素,以满足系统的要求和可用性目标。


事务消息方案

首先,一类偏狭义的分布式事务解决方案是基于消息队列 MessageQueue(后续简称 MQ)实现的事务消息 Transaction Message.

RocketMQ 简介

RocketMQ 是阿里基于 java 实现并托管于 apache 基金会的顶级开源消息队列组件,其中事务消息 TX Msg 也是 RocketMQ 现有的一项能力. 本章将主要基于 RocketMQ 针对事务消息的实现思路展开介绍.

RocketMQ github 地址:https://github.com/apache/rocketmq

在这里插入图片描述

kafka

在这里插入图片描述
Kafka(Apache Kafka)是一种高吞吐量、分布式、持久性的消息传递系统,最初由LinkedIn开发,并且后来成为了Apache软件基金会的一个顶级项目。Kafka旨在处理大量数据流,并支持实时数据流处理应用程序。
Kafka的典型用例包括日志聚合、事件溯源、监控和度量、实时数据分析、日志流式处理、电子商务订单处理等。它在大规模数据处理、实时数据流和事件驱动架构中广泛使用。

基于 MQ 实现分布式事务

我们知道在 MQ 组件中,通常能够为我们保证的一项能力是:投递到 MQ 中的消息能至少被下游消费者 consumer 消费到一次,即所谓的 at least once 语义.

基于此,MQ 组件能够保证消息不会在消费环节丢失,但是无法解决消息的重复性问题. 因此,倘若我们需要追求精确消费一次的目标,则下游的 consumer 还需要基于消息的唯一键执行幂等去重操作,在 at least once 的基础上过滤掉重复消息,最终达到 exactly once 的语义.

在这里插入图片描述
依赖于 MQ 中 at least once 的性质,我们简单认为,只要把一条消息成功投递到 MQ 组件中,它就一定被下游 consumer 端消费端,至少不会发生消息丢失的问题.

倘若我们需要执行一个分布式事务,事务流程中包含需要在服务 A 中执行的动作 I 以及需要在服务 B 中执行的动作 II,此时我们可以基于如下思路串联流程:

  • 以服务 A 作为 MQ 生产方 producer,服务 B 作为 MQ 消费方 consumer
  • 服务 A 首先在执行动作 I,执行成功后往 MQ 中投递消息,驱动服务 B 执行动作 II
  • 服务 B 消费到消息后,完成动作 II 的执行

对上述流程进行总结,其具备如下优势:

  • 服务 A 和服务 B 通过 MQ 组件实现异步解耦,从而提高系统处理整个事务流程的吞吐量
  • 当服务 A 执行 动作 I 失败后,可以选择不投递消息从而熔断流程,保证不会出现动作 II 执行成功,而动作 I 执行失败的不一致的问题
  • 基于 MQ at least once 的语义,服务 A 只要成功消息的投递,就可以相信服务 B 一定能消费到该消息,至少服务 B 能感知到动作 II 需要执行的这一项情报
  • 依赖于 MQ 消费侧的 ack 机制,可以实现服务 B 有限轮次的重试能力. 即当服务 B 执行动作 II 失败后,可以给予 MQ bad ack,从而通过消息重发的机制实现动作 II 的重试,提高动作 II 的执行的成功率

与之相对的,上述流程也具备如下几项局限性:

  • 问题 1:服务 B 消费到消息执行动作 II 可能发生失败,即便依赖于 MQ 重试也无法保证动作一定能执行成功,此时缺乏令服务 A 回滚动作 I 的机制. 因此很可能出现动作 I 执行成功,而动作 II 执行失败的不一致问题
  • 问题 2:在这个流程中,服务 A 需要执行的操作有两步:(1)执行动作 I;(2)投递消息. 这两个步骤本质上也无法保证原子性,即可能出现服务 A 执行动作 I 成功,而投递消息失败的问题.

在这里插入图片描述

本地事务+消息投递

上面的小节中,聊到的服务 A 所要执行的操作分为两步:本地事务+消息投递. 这里我们需要如何保证这两个步骤的执行能够步调统一呢,下面不妨一起来推演一下我们的流程设计思路:

首先,这两个步骤在流程中一定会存在一个执行的先后顺序,我们首先来思考看看不同的组织顺序可能会分别衍生出怎样的问题:

组合 I:先执行本地事务,后执行消息投递

在这里插入图片描述
组合 I 的优势:

  • 消息投递成功与本地事务一致:当使用组合 I 策略时,可以确保消息的投递与本地事务的执行是一致的。这意味着只有在本地事务执行成功时,消息才会被投递。这可以防止消息投递成功但本地事务失败的情况
  • 熔断机制:如果本地事务执行失败,您可以主动停止或熔断消息的投递动作。这可以防止错误的消息被发送,降低了系统可能面临的问题。

组合 I 的劣势:

  • 消息投递失败可能导致消息丢失:虽然组合 I 确保了消息投递与本地事务的一致性,但在某些情况下,消息投递可能会失败。例如,即使本地事务成功,但消息投递由于网络或其他问题而失败,导致消息丢失。此时,由于本地事务已经提交,要执行回滚操作会非常复杂和昂贵。

组合 II:先执行消息投递,后执行本地事务

在这里插入图片描述
组合 II 的优势:

  • 避免不必要的本地事务:如果消息投递失败,您可以避免执行不必要的本地事务。这可以提高系统的效率,因为不会浪费资源在本地事务上,除非消息可以被成功投递。

组合 II 的劣势:

  • 消息投递成功可能导致问题:尽管组合 II 确保了本地事务与消息投递的一致性,但在某些情况下,消息投递成功可能导致问题。例如,如果消息成功发送后,本地事务一直无法成功执行,那么可能会出现数据不一致或其他问题。

对上面对流程进行梳理总结实现思路是:基于本地事务包裹消息投递操作的实现方式,对应执行步骤如下:

  • 首先 begin transaction,开启本地事务
  • 在事务中,执行本地状态数据的更新
  • 完成数据更新后,不立即 commit transaction
  • 执行消息投递操作
  • 倘若消息投递成功,则 commit transaction
  • 倘若消息投递失败,则 rollback transaction

在这里插入图片描述
这个流程乍一看没啥毛病,重复利用了本地事务回滚的能力,解决了本地修改操作成功、消息投递失败后本地数据修正成本高的问题.

然而,这仅仅是表现. 上述流程实际上是经不住推敲的,其中存在三个致命问题:

  • 本地事务中夹杂第三方组件的IO操作:在本地事务中执行与第三方组件的IO操作可能引发长事务的风险。长事务可能会导致数据库锁定、性能问题和资源浪费。为了缓解这个问题,可以考虑将IO操作与数据库事务解耦,将其移到事务之外,或者采用异步处理方法。
  • 消息投递可能因超时或其他问题导致异常:当消息在实际上已成功投递但生产者未能获得投递响应时,可能会导致本地事务被误回滚的问题。为了避免这种情况,可以实现幂等性操作,确保消息处理具有幂等性,以便在重试消息时不会引发问题。
  • 事务提交失败可能导致无法回滚消息:如果在执行事务提交操作时发生失败,数据库修改操作会回滚,但已经发送的MQ消息无法回收。这可能导致数据不一致性。为了处理这个问题,可以采用两阶段提交(2PC)或者分布式事务管理器来确保事务操作的一致性,包括数据库和消息的一致性。

事务消息原理TX Msg

我们以 RocketMQ 中 TX Msg 的实现方案为例展开介绍。首先抛出结论,TX Msg 能保证我们做到在本地事务执行成功的情况下,后置的投递消息操作能以接近百分之百的概率被发出. 其实现的核心流程为:

  • 生产方 producer 首先向 RocketMQ 生产一条半事务消息,此消息处于中间态,会暂存于 RocketMQ 不会被立即发出
  • producer 执行本地事务
  • 如果本地事务执行成功,producer 直接提交本地事务,并且向 RocketMQ 发出一条确认消息
  • 如果本地事务执行失败,producer 向 RocketMQ 发出一条回滚指令
  • 倘若 RocketMQ 接收到确认消息,则会执行消息的发送操作,供下游消费者 consumer 消费
  • 倘若 RocketMQ 接收到回滚指令,则会删除对应的半事务消息,不会执行实际的消息发送操作
  • 此外,在 RocketMQ 侧,针对半事务消息会有一个轮询任务,倘若半事务消息一直未收到来自 producer 侧的二次确认,则 RocketMQ 会持续主动询问 producer 侧本地事务的执行状态,从而引导半事务消息走向终态。

在这里插入图片描述
在 TX Msg 的实现流程中,能够保证 前面小节中谈及的各种 bad case 都能被很好地消化:

  • 倘若本地事务执行失败,则 producer 会向 RocketMQ 发出删除半事务消息的回滚指令,因此保证消息不会被发出
  • 倘若本地事务执行成功, 则 producer 会向 RocketMQ 发出事务成功的确认指令,因此消息能够被正常发出
  • 倘若 producer 端在发出第二轮的确认或回滚指令前发生意外状况,导致第二轮结果指令确实. 则 RocketMQ 会基于自身的轮询机制主动询问本地事务的执行状况,最终帮助半事务消息推进进度.

总结一下:

  • 保证本地事务成功后消息投递接近百分之百的概率:RocketMQ的TX Msg机制确保了在本地事务执行成功的情况下,消息会以接近百分之百的概率被成功发出。这是因为只有在本地事务成功后,才会向RocketMQ发送确认消息,从而触发消息的真正发送。

  • 事务性保障:RocketMQ的TX Msg允许生产者执行本地事务,确保了消息发送与事务的一致性。如果本地事务失败,消息不会被发送,从而维护了数据的一致性。

  • 回滚机制:如果本地事务执行失败,RocketMQ会接收到回滚指令,然后删除对应的半事务消息,而不会执行实际的消息发送操作。这意味着即使本地事务失败,不会导致消息被误发送,保持了数据的一致性。

  • 轮询任务:RocketMQ会定期轮询半事务消息的状态,如果长时间未收到本地事务的二次确认,RocketMQ会主动询问生产者本地事务的执行状态,确保半事务消息能够最终达到终态。

RocketMQ 中半事务消息轮询流程示意如下:

在这里插入图片描述

最后,我们再回过头把 RocketMQ TX Msg 的使用交互流程总结梳理如下:

在这里插入图片描述

事务消息局限性

现在我们就来总结梳理一下,TX Msg 中存在的几项局限性:

  • 流程高度抽象:TX Msg 把流程抽象成本地事务+投递消息两个步骤. 然而在实际业务场景中,分布式事务内包含的步骤数量可能很多,因此就需要把更多的内容更重的内容糅合在所谓的“本地事务”环节中,上游 producer 侧可能会存在比较大的压力
  • 不具备逆向回滚能力:倘若接收消息的下游 consumer 侧执行操作失败,此时至多只能依赖于 MQ 的重发机制通过重试动作的方式提高执行成功率,但是无法从根本上解决下游 consumer 操作失败后回滚上游 producer 的问题. 这一点正是 TX Msg 中存在的最大的局限性.

关于上面第二点,我们再展开谈几句. 我们知道,并非所有动作都能通过简单的重试机制加以解决.

打个比方,倘若下游是一个库存管理系统,而对应商品的库存在事实上已经被扣减为 0,此时无论重试多少次请求都是徒然之举,这就是一个客观意义上的失败动作.

而遵循正常的事务流程,后置操作失败时,我们应该连带前置操作一起执行回滚,然而这部分能力在 TX Msg 的主流程中并没有予以体现.

要实现这种事务的逆向回滚能力,就必然需要构筑打通一条由下游逆流而上回调上游的通道,这一点并不属于 TX Msg 探讨的范畴.

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1213737.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32与ZigBee技术在智能家居无线通信中的应用研究

一、引言 智能家居系统是利用物联网技术将家庭各种设备进行互联互通,实现智能化控制和管理的系统。在智能家居系统中,无线通信技术起着至关重要的作用,而STM32微控制器和ZigBee技术则是实现智能家居无线通信的关键技术。本文将对STM32与ZigB…

C/C++最大质数 2021年9月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C比n小的最大质数 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C/C比n小的最大质数 2021年9月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 对于给定的n,求比n小的质数中…

Spring-IoC与DI入门案例

IoC入门案例 IoC入门案例思路分析 管理什么?(Service与Dao)如何将被管理的对象告知IoC容器?(配置)被管理的对象交给IoC容器,如何获取到IoC容器?(接口)IoC容…

代码随想录算法训练营第五十一天|309. 买卖股票的最佳时机含冷冻期、714. 买卖股票的最佳时机含手续费

第九章 动态规划part12 309. 买卖股票的最佳时机含冷冻期 给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。​ 设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票&…

STM32 独立看门狗

目录 1.独立看门狗介绍 2.独立看门狗本质 3.独立看门狗框图​编辑 4.独立看门狗时钟 5.预分频寄存器(IWDG_PR)​编辑 6.重装载寄存器(IWDG_RLR) 7.键寄存器(IWDG_KR) 8.独立看门狗实验和代码示例 9.独立看门狗和窗口看门狗的异同点 …

GB/T 1032-2023 三相异步电机试验方法 笔记

仅仅是为了技术分享。如有侵权请随时告知,我会尽快删除相关内容,谢谢! 1.阻值的温度效应 7.x 2.温升与负载电 7.x 3.力矩修正公式及功率公式 8.3 3.1铁损和铜损测量 4.空载特性曲线 9.3 4.1 空载损耗 5.堵转特性 6.剩余损耗 6.1 另一种由转子…

自己动手实现一个深度学习算法——六、与学习相关的技巧

文章目录 1.参数的更新1)SGD2)Momentum3)AdaGrad4)Adam5)最优化方法的比较6)基于MNIST数据集的更新方法的比较 2.权重的初始值1)权重初始值不能为02)隐藏层的激活值的分布3&#xff…

基于STM32的无线通信系统设计与实现

【引言】 随着物联网的迅速发展,无线通信技术逐渐成为现代通信领域的关键技术之一。STM32作为一款广受欢迎的微控制器,具有丰富的外设资源和强大的计算能力,在无线通信系统设计中具有广泛的应用。本文将介绍如何基于STM32实现一个简单的无线通…

leetcode:476. 数字的补数

一、题目 476. 数字的补数 - 力扣(LeetCode) 函数原型: int findComplement(int num) 二、思路 将num的每一位取出来,取反后,乘以2的位次方,最终所有结果相加即可得到结果。 如何取出num的每一位&#xff1…

Unity地面交互效果目录

大家好,我是阿赵。   之前写了几篇关于地形交互、地面轨迹、脚印效果实现的博文。虽然写的篇数不多,但里面也包含了不少基础知识,比如局部UV采样、法线动态混合、曲面细分等知识,这些都是可以和别的效果组合在一起,做…

Linux 系统编程,Binder 学习,文件访问相关的接口

文章目录 Linux 系统编程,Binder 学习,文件访问相关的接口1.概念2.linux文件结构3.文件描述符4.Linux文件系统的两类常用接口,linux系统内置库函数4.1 open4.2 close4.3 read4.4 write 5.标准I/O库函数5.1 fopen Linux 系统编程,B…

文生图算法评价

1.sd_eval stable diffusion模型评价框架_Kun Li的博客-CSDN博客文章浏览阅读418次。作者的思路我认为也是没问题,和我看法基本一致,生成式的sd不需要那么多定向的模型,提供强泛化能力的基础模型只需要几个就可以,而外挂的能力多…

VSCode 使用CMakePreset找不到cl.exe编译器的问题

在用vscode开发c项目的时候,使用预先配置的CMakePresets.json可以把一些特定的cmake选项固定下来,在配置时直接使用 "cmake --config --preset presetname"就可以进行配置,免去在命令行输入过多的配置参数。 但是在vscode中&#…

Python OpenCV 通过trackbar调整图像亮度对比度颜色

上一篇文章通过设置固定值的方式来调整图像,这篇文章通过trackbar来动态调整参数,从而实时展现图像处理结果,得到想要的图像处理参数。 1. 创建trackbar import cv2 import numpy as npdef nothing(x):passcv2.namedWindow(image) # 创建5个…

Kylin-Server-V10-SP3+Gbase+宝兰德信创环境搭建

目录 一、Kylin-Server-V10-SP3 安装1.官网下载安装包2.创建 VMware ESXi 虚拟机3.加载镜像,安装系统 二、Gbase 安装1.下载 Gbase 安装包2.创建组和用户、设置密码3.创建目录4.解压包5.安装6.创建实例7.登录8.常见问题 三、宝兰德安装1.获取安装包2.解压安装3.启动…

大数据爬虫分析基于Python+Django旅游大数据分析系统

欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 基于Python和Django的旅游大数据分析系统是一种使用Python编程语言和Django框架开发的系统,用于处理和分…

力扣刷题篇之数与位2

系列文章目录 目录 系列文章目录 前言 数值计算 总结 前言 本系列是个人力扣刷题汇总,本文是数与位。刷题顺序按照[力扣刷题攻略] Re:从零开始的力扣刷题生活 - 力扣(LeetCode) 数值计算 415. 字符串相加 - 力扣(…

sqli-labs(Less-5) updatexml闯关

updatexml() - Xpath类型函数 1. 确定注入点闭合方式 确认为字符型注入 2. 爆出当前数据库的库名 http://127.0.0.1/sqlilabs/Less-5/?id1 and updatexml(~,concat(~,(select database())),~) --3. 爆出当前用户名 http://127.0.0.1/sqlilabs/Less-5/?id1 and updatexml…

windows安装maven,配置环境变量

官网下载: 其他版本找 Other Releases 配置环境变量 1、解压缩之后开始配置环境变量 2、右键此电脑,选中属性->高级系统设置->高级->环境变量。 3、①和②任选一个都可 ①在系统变量那边增加MAVEN_HOME,路径是解压缩后的文件路径。…

Vue3清除Echarts图表

一:前言 Vue3是一款流行的JavaScript框架。它提供了丰富的工具和组件,使得开发者可以轻松构建交互式的Web应用程序。而Echarts是一款功能强大的图表库,它可以帮助开发者以直观的方式展示数据。 在使用Vue3和E charts的过程中&#xf…