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

学习笔记-架构的演进之限流-3月day03

文章目录

  • 前言
  • 限流的目标
  • 流量统计指标
  • 限流设计模式
    • 流量计数器模式
    • 滑动时间窗模式
    • 漏桶模式
    • 令牌桶模式
  • 分布式限流
  • 总结

前言

任何一个系统的运算、存储、网络资源都不是无限的,当系统资源不足以支撑外部超过预期的突发流量时,就应该要有取舍,建立面对超额流量自我保护的机制,而这个机制就是微服务中常说的“限流”。

限流的目标

考虑一个简单的场景:一个系统的最大处理能力是80TPS,在遇到100TPS请求的环境下,理想的限流目标是什么?
理想的限流目标就是能够完成80TPS的业务,只有20TPS的请求失败或被拒绝(这可能看上去是理所应当的)。但如果未做良好的设计,实际场景可能是100TPS涉及的请求都被部分处理,实际完成的业务量为0

所以,一个健壮的系统要做到恰当的流量控制,需要妥善解决以下三个问题:

  1. 依据什么限流?
    要不要控制流量、要控制哪些流量、控制力度要有多大,等等,这些操作都没法在系统设计阶段静态地给出确定的结论,必须根据系统此前一段时间的运行状况,甚至未来一段时间的预测情况来动态决定。
  2. 具体如何限流?
    要想解决系统具体是如何做到允许一部分请求能够通行,而另外一部分流量实行受控制的失败降级的问题,就必须要了解和掌握常用的服务限流算法和设计模式
  3. 超额流量如何处理?
    超额流量可以有不同的处理策略,也许会直接返回失败(如 429 Too Many Requests),或者被迫使它们进入降级逻辑,这种策略被称为否决式限流;也可能是让请求排队等待,暂时阻塞一段时间后继续处理,这种则被称为阻塞式限流

流量统计指标

限流中的“流”到底指什么呢?要解答这个问题,我们得先梳理清楚经常用于衡量服务流量压力,但又比较容易混淆的三个指标的定义:

  • 每秒事务数(Transactions per Second,TPS)
    TPS 是衡量信息系统吞吐量的最终标准。“事务”可以理解为一个逻辑上具备原子性的业务操作(或者是需要被看做是原子性的业务操作)。比如对一笔订单的支付操作,不能允许只支付订单中的包装盒。
  • 每秒请求数(Hits per Second,HPS)
    HPS 是指每秒从客户端发向服务端的请求数(这里要把 Hits 理解为 Requests 而不是 Clicks)。如果只要一个请求就能完成一笔业务,那 HPS 与 TPS 是等价的。多数情况下,一笔业务都伴随多个请求。
  • 每秒查询数(Queries per Second,QPS)
    QPS 是指一台服务器能够响应的查询次数。如果只有一台服务器来应答请求,那 QPS 和 HPS 是等价的,但在分布式系统中,一个请求的响应,往往要由后台多个服务节点共同协作来完成。

在逻辑概念上,可以理解为TPS包括了HPS包括了QPS,而数值上QPS>HPS>TPS。

总体来说,以上这三点都是基于调用计数的指标,而在整体目标上,我们当然最希望能够基于 TPS 来限流,因为信息系统最终是为人类用户提供服务的,用户并不关心业务到底是由多少个请求、多少个后台查询共同协作来实现的。但是因为TPS更接近客户端侧,不能准确地反映出服务端系统所承受的压力,所以实际操作会比较困难。目前来说,主流系统大多倾向于使用 HPS 作为首选的限流指标,因为它相对容易观察统计,而且能够在一定程度上反映系统当前以及接下来一段时间的压力。
但是限流指标并不存在任何必须遵循的权威法则,根据系统的实际需要,哪怕完全不选择基于调用计数的指标都是有可能的。举个简单的例子,下载、视频、直播等 I/O 密集型系统,往往会把每次请求和响应报文的大小作为限流指标。比如说,只允许单位时间通过 100MB 的流量;再比如网络游戏等基于长连接的应用,可能会把登录用户数作为限流指标,热门的网游往往超过一定用户数就会让你在登录前排队等候。

限流设计模式

业界内有一些常见、常用、被实践证明有效的设计模式可以参考使用,包括流量计数器、滑动时间窗、漏桶和令牌桶这四种。

流量计数器模式

最容易想到的一种做限流的方法,就是设置一个计算器,根据当前时刻的流量计数结果是否超过阈值来决定是否限流。
这种做法很直观,而且有些简单的限流就是这么实现的,但它并不严谨,如:

  • 即使每一秒的统计流量都没有超过 80 TPS,也不能说明系统没有遇到过大于 80 TPS 的流量压力。(一秒框定的范围内可能超出80TPS,但却没被统计器计算到)
  • 即使连续若干秒的统计流量都超过了 80 TPS,也不能说明流量压力就一定超过了系统的承受能力。(要看系统的设计,比如超时机制)

流量计数器模式缺陷的根源在于,它只是针对时间点进行离散的统计。因此为了弥补该缺陷,一种名为“滑动时间窗”的限流模式就被设计了出来,它可以实现平滑的基于时间片段的统计。

滑动时间窗模式

滑动窗口算法(Sliding Window Algorithm)在计算机科学的很多领域中都有成功的应用,比如编译原理中的窥孔优化(Peephole Optimization)、TCP 协议的阻塞控制(Congestion Control)等都使用到了滑动窗口算法。而对分布式系统来说,无论是服务容错中对服务响应结果的统计,还是流量控制中对服务请求数量的统计,也都经常要用到滑动窗口算法。

滑动时间窗的工作过程:
当频率固定的定时器被唤醒时,(实际中常用双端队列代替数组)

  1. 将数组最后一位的元素丢弃掉,并把所有元素都后移一位,然后在数组第一个插入一个新的空元素。这个步骤即为“滑动窗口”。
  2. 将计数器中所有统计信息写入到第一位的空元素中。
  3. 对数组中所有元素进行统计,并复位清空计数器数据供下一个统计周期使用。

这种模式也有一些缺点,它通常只适用于否决式限流,对于超过阈值的流量就必须强制失败或降级,很难进行阻塞等待处理,也就很难在细粒度上对流量曲线进行整形,起不到削峰填谷的作用。

漏桶模式

在计算机网络中,专门有一个术语“流量整形”(Traffic Shaping),用来描述如何限制网络设备的流量突变,使得网络报文以比较均匀的速度向外发送。流量整形通常都需要用到缓冲区来实现,当报文的发送速度过快时,首先在缓冲区中暂存,然后在控制算法的调节下,均匀地发送这些被缓冲的报文。
这里常用的控制算法,有漏桶算法(Leaky Bucket Algorithm)和令牌桶算法(Token Bucket Algorithm)两种,这两种算法的思路截然相反,但达到的效果又是相似的。

针对限流模式的话,你可以把“请求”想像成是“水”,水来了都先放进池子里,水池同时又以额定的速度出水,让请求进入系统中。这样,如果一段时间内注水过快的话,水池还能充当缓冲区,让出水口的速度不至于过快。不过,由于请求总是有超时时间的,所以缓冲区的大小也必须是有限度的,当注水速度持续超过出水速度一段时间以后,水池终究会被灌满。此时,从网络的流量整形的角度看,就体现为部分数据包被丢弃;而从信息系统的角度看,就体现为有部分请求会遭遇失败和降级。

漏桶模式在代码实现上也非常简单,它其实就是一个以请求对象作为元素的先入先出队列(FIFO Queue),队列长度就相当于漏桶的大小,当队列已满时就拒绝新的请求进入。漏桶实现起来很容易,比较困难的地方只在于如何确定漏桶的两个参数:桶的大小和水的流出速率。

  • 首先是桶的大小。如果桶设置得太大,那服务依然可能遭遇流量过大的冲击,不能完全发挥限流的作用;如果设置得太小,那很可能就会误杀掉一部分正常的请求,这种情况与流量计数器模式中举过的例子是一样的。
  • 流出速率在漏桶算法中一般是个固定值,这对于固定拓扑结构的服务是很合适的;但同时你也应该明白,现实世界里系统的处理速度,往往会受到其内部拓扑结构变化和动态伸缩的影响。这也暴露出漏桶模式的缺陷。

令牌桶模式

对比来看,漏桶是从水池里往系统出水,令牌桶则是系统往排队机中放入令牌。

具体实现是,假设我们要限制系统在 X 秒内的最大请求次数不超过 Y,那我们可以每间隔 X/Y 时间,就往桶中放一个令牌,当有请求进来时,首先要从桶中取得一个准入的令牌,然后才能进入系统处理。任何时候,一旦请求进入桶中发现没有令牌可取了,就应该马上失败或进入服务降级逻辑。
与漏桶类似,令牌桶同样有最大容量,这意味着当系统比较空闲的时候,桶中的令牌累积到一定程度就不再无限增加,而预存在桶中的令牌便是请求最大缓冲的余量。

编码实现是:

  1. 让系统以一个由限流目标决定的速率向桶中注入令牌,比如要控制系统的访问不超过 100 次,速率即设定为 1/100=10 毫秒。
  2. 桶中最多可以存放 N 个令牌,N 的具体数量是由超时时间和服务处理能力共同决定的。如果桶已满,第 N+1 个进入的令牌就会被丢弃掉。
  3. 请求到时会先从桶中取走 1 个令牌,如果桶已空就进入降级逻辑。

总体来说,令牌桶模式的实现看似可能比较复杂,每间隔固定时间,我们就要把新的令牌放到桶中,但其实我们并不需要真的用一个专用线程或者定时器来做这件事情,只要在令牌中增加一个时间戳记录,每次获取令牌前,比较一下时间戳与当前时间,就可以轻易计算出这段时间需要放多少令牌进去,然后一次性放完全部令牌即可,所以真正编码时并不会显得很复杂。(是否还应该记录上一次发放令牌的时间戳)

分布式限流

此前,我们讨论的种种限流算法和模式全部是针对整个系统的限流,总是有意无意地假设或默认系统只提供一种业务操作,或者所有业务操作的消耗都是等价的,并不涉及不同业务请求进入系统的服务集群后,分别会调用哪些服务、每个服务节点处理能力有何差别等问题。另外,这些限流算法直接使用在单体架构的集群上确实是完全可行的,但到了微服务架构下,它们就最多只能应用于集群最入口处的网关上,对整个服务集群进行流量控制,而无法细粒度地管理流量在内部微服务节点中的流转情况。

所以,我们把前面介绍的限流模式都统称为单机限流,把能够精细控制分布式集群中每个服务消耗量的限流算法称为分布式限流。

分布式限流与单机限流最主要的区别是如何管理限流的统计指标。
单机限流很好办,指标都是存储在服务的内存当中;而分布式限流的目的是要让各个服务节点的协同限流。无论是将限流功能封装为专门的远程服务,还是在系统采用的分布式框架中有专门的限流支持,都需要把每个服务节点的内存中的统计数据给开放出来,让全局的限流服务可以访问到才行。

  • 一种常见的简单分布式限流方法,是将所有服务的统计结果都存入集中式缓存(如 Redis)中,以实现在集群内的共享,并通过分布式锁、信号量等机制,解决这些数据在读写访问时的并发控制问题。这样,在可以共享统计数据的前提下,原本用于单机的限流模式,理论上也是可以应用于分布式环境中的,可是它的代价也显而易见:每次服务调用都必须要额外增加一次网络开销,所以这种方法的效率肯定是不高的,当流量压力大的时候,限流本身反倒会显著降低系统的处理能力。(只要集中式存储统计信息,就不可避免地会产生网络开销。)
  • 一种改善办法是在令牌桶限流模式的基础上,进行“货币化改造”。即不把令牌看作是只有准入和不准入的“通行证”,而把它看作是数值形式的“货币额度”。
    当请求进入集群时,首先在 API 网关处领取到一定数额的“货币”,不同等级用户的额度可以有所差异。我们将用户 A 的额度表示为 QuanityA。由于任何一个服务在响应请求时,都需要消耗集群中一定量的处理资源,所以在访问每个服务时都要求消耗一定量的“货币”。假设服务 X 要消耗的额度表示为 CostX,那当用户 A 访问了 N 个服务以后,他剩余的额度 LimitN 就会表示为:
    LimitN = QuanityA - ∑NCostX
    我们可以把剩余额度 LimitN 作为内部限流的指标,规定在任何时候,只要剩余额度 LimitN 小于等于 0 时,就不再允许访问其他服务了。另外,这时还必须先发生一次网络请求,重新向令牌桶申请一次额度,成功后才能继续访问,不成功则进入降级逻辑。除此之外的任何时刻,即 LimitN 不为 0 时,都无需额外的网络访问,因为计算 LimitN 是完全可以在本地完成的。

这种基于额度的限流方案,对限流的精确度会有一定的影响,比如可能存在业务操作已经进行了一部分服务调用,却无法从令牌桶中再获取到新额度,因“资金链断裂”而导致业务操作失败的情况。这种失败的代价是比较高昂的,它白白浪费了部分已经完成了的服务资源,但总体来说,它仍然是一种在并发性能和限流效果上,都相对折衷可行的分布式限流方案。

总结

对于分布式系统容错的设计,是必须要有且无法妥协的措施。但限流与容错不一样,做分布式限流从不追求“越彻底越好”,我们往往需要权衡方案的代价与收益。

此文章为3月Day03学习笔记,内容来源于极客时间《周志明的软件架构课》

相关文章:

学习笔记-架构的演进之限流-3月day03

文章目录前言限流的目标流量统计指标限流设计模式流量计数器模式滑动时间窗模式漏桶模式令牌桶模式分布式限流总结附前言 任何一个系统的运算、存储、网络资源都不是无限的,当系统资源不足以支撑外部超过预期的突发流量时,就应该要有取舍,建…...

动态规划 背包问题

动态规划 背包问题 问题描述: 有一个背包,总容量为12。有6件物品,每件物品的重量和价值不同,求在背包总容量12的前提下,装进物品的最大价值以及装进物品的编号 单个物品重量和价值: 为方便进行思考&#…...

C++ Primer Plus 学习笔记(四)—— 内存模型和名称空间

1 单独编译 C允许将组件函数放在独立的文件即头文件中,头文件中可以包含以下内容: 函数原型;使用#define或const定义的符号常量;结构声明;类声明;模板声明;内联函数。 注意,在包含…...

详解基于 Celestia、Eclipse 构建的首个Layer3 链 Nautilus Chain

以流支付为主要概念的Zebec生态,正在推动流支付这种新兴的支付方式向更远的方向发展,该生态最初以Zebec Protocol的形态发展,并从初期的Solana进一步拓展至BNB Chian以及Near上。与此同时,Zebec生态也在积极的寻求从协议形态向公链…...

列表与数组的转化

目录用np.array(a)将列表转换为数组列表转数组的特殊情况(一)列表转数组的特殊情况(二)针对子元素个数不一致的解决办法用a.tolist()函数将数组转化为列表在python的学习中,经常会用到数组与列表的相互转化,本文主要介绍下关于数组与列表转化的问题。用n…...

docker 运行花生壳实现内外网穿透

环境:centos 7 ,64位 1、创建一个指定的文件夹作为安装示例所用,该示例文件夹为“hsk-nwct”。“hsk-nwct”内创建“app”文件夹作为docker容器挂载出来的文件。 2、在“app”内下载花生壳linux安装包,下载花生壳应用:花生壳客户…...

操作系统——16.时间片轮转、优先级、多级反馈队列算法

这篇文章我们来看一下进程调度算法中的时间片轮转、优先级、多级反馈队列算法 目录 1.概述 2.时间片轮转调度算法(RR,Round-Robin) 3.优先级调度算法 4.多级反馈队列调度算法 5.分析对比 1.概述 首先,我们来看一下这篇文章…...

Python3.8.8-Django3.2-Redis-连接池-数据类型-字符串-list-hashmap-命令行操作

文章目录1.认识Redis1.1.优点1.2.缺点2.在Django中Redis的连接3.Redis的基础用法3.1.hashmap结构3.2.list结构4.命令行查看数据库5.作者答疑1.认识Redis Remote DIctionary Server(Redis) 是一个key-value 存储系统,是跨平台的非关系型数据库。是一个开源的使用 AN…...

Android kotlin 系列讲解(进阶篇)高级项目架构模式 - MVVM

<<返回总目录 1、MVVM是什么 MVVM是Model-View-ViewModel的缩写&#xff0c;是一种高级项目架构模式。 MVVM架构可以将程序结构主要分成三个部分&#xff1a; Model&#xff1a;数据模型部分&#xff0c;包括从服务端获取的json数据或者从本地获取的数据等等View&…...

8. 查找

1 题目描述 查找成绩10开启时间2021年09月24日 星期五 18:00折扣0.8折扣时间2021年11月15日 星期一 00:00允许迟交否关闭时间2021年11月23日 星期二 00:00 输入 n(n ≤ 10^6)个不超过 10^9的单调不减的&#xff08;就是后面的数字不小于前面的数字&#xff09;非负整数 &#…...

二分查找算法

感谢“五点七边”工作室的算法讲解&#xff0c;详细内容可以参考视频讲解 二分查找为什么总是写错&#xff1f;_哔哩哔哩_bilibili 此处仅是个人学习总结 以target等于5为例&#xff0c;输入: 1 2 3 5 5 5 8 9 1. 找到第一个 > target 的元素 判断条件 < target&am…...

Git(3)之远程服务器

Git基础之远程服务器 Author&#xff1a;onceday date&#xff1a;2023年3月5日 满满长路有人对你微笑过嘛… windows安装可参考文章&#xff1a;git简易配置_onceday_CSDN博客 參考文档&#xff1a; 《progit2.pdf》&#xff0c;Progit2 Github。《git-book.pdf》 文章目…...

Javalin解构

Javalin Javalin是一个轻量级http框架&#xff0c;我们可以很容易的了解请求的处理过程及其设计&#xff0c;具有较高的学习意义。 从demo说起 public static void main(String[] args) {Javalin app Javalin.create(config -> {System.out.println("用户配置"…...

yolov5算法,训练模型,模型检测

嘟嘟嘟嘟&#xff01;工作需要&#xff0c;所以学习了下yolov5算法。是干什么的呢&#xff1f; 通俗来说&#xff0c;可以将它看做是一个小孩儿&#xff0c;通过成年人&#xff08;开发人员&#xff09;提供的大量图片的学习&#xff0c;让自己知道我看到的哪些场景需要提醒给成…...

linux系统防火墙开放端口

linux系统防火墙开放端口 在外部访问CentOS中部署应用时&#xff0c;需要通过防火墙管理软件,开端口,或者直接关闭防火墙进行解决(不建议) 加粗样式 常用命令&#xff1a; systemctl start firewalld #启动 systemctl stop firewalld #停止 systemctl status firewalld #查看…...

CSAPP第九章 虚拟内存

理解虚拟内存的原因 本章前部分描述虚拟内存是如何工作的&#xff0c;后一部分描述应用程序如何使用和管理虚拟内存 物理和虚拟寻址 虚拟内存作为缓存的工具 页表 页命中 缺页 虚拟内存作为内存管理的工具 简化链接&#xff0c;简化加载&#xff0c;简化共享&#xff0c;简化…...

numpy数组与矩阵运算(二)

文章目录矩阵生成与常用操作矩阵生成矩阵转置查看矩阵特性矩阵乘法计算相关系数矩阵计算方差、协方差、标准差计算特征值与特征向量计算逆矩阵求解线性方程组奇异值分解函数向量化矩阵生成与常用操作 矩阵生成 扩展库numpy中提供的matrix()函数可以用来把列表、元组、range对…...

Dubbo 中 Zookeeper 注册中心原理分析

Dubbo 中 Zookeeper 注册中心原理分析 文章目录Dubbo 中 Zookeeper 注册中心原理分析一、ZooKeeper注册中心1.1 ZooKeeper数据结构1.2 ZooKeeper的Watcher机制1.3 ZooKeeper会话机制1.4 使用ZooKeeper作为注册中心二、源码分析2.1 AbstractRegistry2.2 FailbackRegistry2.2.1 核…...

素数产生新的算法(由筛法减法改为增加法)--哥德巴赫猜想的第一次实际应用

素数产生新的算法&#xff08;由筛法减法改为增加法&#xff09;--哥德巴赫猜想的第一次实际应用 摘要&#xff1a;长期以来&#xff0c;人们认为哥德巴赫猜想没有什么实际应用的。 现在&#xff0c;我假设这个不是猜想&#xff0c;而是定理或公理&#xff0c;就产生了新的应用…...

递归-需要满足三个条件

一&#xff0c;概述 递归是一种应用非常广泛的算法&#xff08;或者编程技巧&#xff09;。很多数据结构和算法的编码实现都要用到递归&#xff0c;比如 DFS 深度优先搜索、前中后序二叉树遍历等。 去的过程叫“递”&#xff0c;回来的过程叫“归”。基本上所有的递归问题都可…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

【Linux】C语言执行shell指令

在C语言中执行Shell指令 在C语言中&#xff0c;有几种方法可以执行Shell指令&#xff1a; 1. 使用system()函数 这是最简单的方法&#xff0c;包含在stdlib.h头文件中&#xff1a; #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

AtCoder 第409​场初级竞赛 A~E题解

A Conflict 【题目链接】 原题链接&#xff1a;A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串&#xff0c;只有在同时为 o 时输出 Yes 并结束程序&#xff0c;否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

LeetCode - 394. 字符串解码

题目 394. 字符串解码 - 力扣&#xff08;LeetCode&#xff09; 思路 使用两个栈&#xff1a;一个存储重复次数&#xff0c;一个存储字符串 遍历输入字符串&#xff1a; 数字处理&#xff1a;遇到数字时&#xff0c;累积计算重复次数左括号处理&#xff1a;保存当前状态&a…...

连锁超市冷库节能解决方案:如何实现超市降本增效

在连锁超市冷库运营中&#xff0c;高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术&#xff0c;实现年省电费15%-60%&#xff0c;且不改动原有装备、安装快捷、…...

深入理解JavaScript设计模式之单例模式

目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式&#xff08;Singleton Pattern&#…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?

现有的 Redis 分布式锁库&#xff08;如 Redisson&#xff09;相比于开发者自己基于 Redis 命令&#xff08;如 SETNX, EXPIRE, DEL&#xff09;手动实现分布式锁&#xff0c;提供了巨大的便利性和健壮性。主要体现在以下几个方面&#xff1a; 原子性保证 (Atomicity)&#xff…...