分布式共识算法随笔 —— 从 Quorum 到 Paxos

news2024/9/9 7:26:41

分布式共识算法随笔 —— 从 Quorum 到 Paxos

概览: 为什么需要共识算法?

昨夜西风凋碧树,独上高楼,望尽天涯路

复制(Replication) 是一种通过将同一份数据在复制在多个服务器上来提高系统可用性和扩展写吞吐的策略, 。常见的复制策略有主从架构(Leader/Follower), 多主架构(Multi-Leader)无主架构(LeaderLess)[1]。在无主架构模式下,需要保证多个节点写入数据的一致(即共识(consensus))。如, 某个无主架构包含 Server1, Server2, Server3 三个服务器, 当前状态为 x=nil,并发同时向他们分别发起 x=1, x=2, x=3 请求,最终一定会得到一个确定的值(而不会产生分歧)。这个值可以是写入成功的 x=1, x=2 或 x=3,也可以是未发起写入操作前的 x=nil。

Leaderless Consensus

状态机(State Machine) 常用于维护不同服务器之间的数据同步状态[2]。只要保证各个服务器之间的状态机状态一致,则它们的数据集就是一样的。状态机通常由 Write-Ahead-Log(WAL 日志) 实现,这些日志记录了一系列有序的命令。每个服务器将会按照 WAL 日志的内容,按序执行这些命令。

因此, 只要这些服务器的 WAL 日志保持一致,则它们最终的状态机就是一致的,那么他们最终的数据集也就是一致的。通常, WAL 日志一旦写入,都是不可变的。因为一旦 WAL 写入, 很可能它已经应用到了状态机。如果要变更该日志, 意味着需要从状态机内回滚该操作。而回滚这一条 WAL 日志的操作,很可能需要先将后续的 WAL 日志都回滚, 这样的成本往往是很高的。(如果引入了共识模块, 情况将会变得更加糟糕。)

通过在每个服务器加入实现 WAL 日志保持一致性的共识模块(Consensus Module),我们就可以让所有的服务器的数据保持一致。各个服务器的共识模块通过和其他服务器的共识模块之间的通信,对并发写入的数据达成共识,确保最终写入的 WAL 日志顺序和命令(日志内容)都是一样的[3][4] 。下图出自参考文献[4] (本文未注明来源的图均为原创)。

image

为了保证所有的 WAL 日志的顺序和日志内容都是一样的,共识模块只需要依次对单个日志 ID(有的地方也被称为日志槽(Log Slot)[5]) 达成共识, 最终就会使得整个 WAL 日志都是达成共识的, 从而使得所有服务器的整个数据集都是一致的。

如下图, server1 和 server3 在第五条 WAL 日志处, 同时分别发起 x=3 和 y=2 的写操作。共识模块就“第五条WAL日志是什么”发起协商,最终的共识可以是 “第五条WAL日志是 x=3”, “第五条WAL日志是 y=2” 或“第五条WAL日志未达成一致, 依旧可以写入新值” 。图中, 共识算法选择了 “y=2” 作为第五条 WAL 日志,这一条日志将不再被允许更改。后续各个服务器会使用单共识算法依次对第六条 第七条…日志的命令日志达成共识, 最终实现整个服务器的数据集都达成共识。

image

对单个数据达成共识并且不再允许更改的共识算法称之为单共识算法(Single Consensus Algorithm), 如上图中, 对“第五条WAL日志是什么”发起的共识算法。而分别对每个日志ID执行单共识算法的算法称之为多共识算法(Multi-Consensus Algorithm), 该算法通过简单地重复单共识算法实现对整个 WAL 日志的共识,从而实现所有服务器数据集的共识。(Single Consensus 和 Multi-Consensu 是我通过 paxos 和 multi-paxos [6] [7]推广而来。这不一定准确, 因此你只需要记得它们的对应关系即可)

因为多共识算法约等于多个单共识算法的集合, 因此本文后续仅重点介绍单共识算法。

在进入各种共识算法的介绍之前,我们还得了解下单共识算法的安全属性,这样才能明确我们需要达到的目标(系统需要始终保证其安全属性始终满足)。安全属性(Safety Property) 是指约束系统状态, 在系统运行中, 限制一些不可逆转的不好的事情发生(nothing bad happens)的属性。在系统正常运行过程中,需要始终保证满足安全属性。正式的定义可参考 [1] [8]。

根据上述内容, 我们可以简单地梳理出单共识算法需要满足的 Safety Property:

  1. 只有客户端提交的数据才能被服务端达成共识而选中。(防止异常数据产生)
  2. 当多个客户端并发写入时, 只能有一个 value 能被服务端选中。某个 value 一旦被服务端达成共识而选中, 它将不可被更改。(确保选中数据不会冲突, 不能被更改)
  3. 读请求只能“看见”服务端达成共识的 value。(防止脏读等情况)

探索: 单共识算法的可能性

衣带渐宽终不悔,为伊消得人憔悴

从 Quorum 算法聊起

Quorum 算法的基本思想是: 假如有 n 个服务器, 每次写请求至少保证 w 个服务器写入成功, 每次读请求至少保证 r 个服务器返回成功。根据鸽巢原理, 只要保证 w + r > n, 则我们一定能读到最新的数据 。[1][9], 下图引用自参考文献 [1]
image

简单起见, 下面都令 w = r = ⌊n⌋ + 1, 即读写都至少保证大部分服务器返回成功, 才认为操作成功。

  • 时间戳(timestamp)
    为了区分 r 个服务器返回的数据谁的版本最新, 我们需要定义数据写入的时间戳, 以区分数据的写入顺序。定义时间戳 timestamp := <clientID, version>。其中, clientID 表示发起该写请求的客户端的 ID。版本号 version 表示该数据(预期)在服务器中的版本号。 timestamp 的比较算法如下:

    image

  • 写请求流程

  1. 客户端(ID 记为 clientID)向服务端广播 get version 请求获取最新的版本号 version。
  2. 当大多数服务端已返回, 记它们的最大版本号为 latest_version。客户端发起写入请求 x=a, 请求的时间戳为 <clientID, latest_version+1>。
  3. 服务端接收到写请求后,比较上次写入操作的时间戳 last_timestamp 和本次写请求的时间戳 timestamp:
    1. 如果 last_timestamp 小于 timestamp, 则执行写操作(在当前服务器写入 x=a, 令 last_timestamp = timestamp), 并向客户端返回“操作执行成功, 当前版本号为 timestamp.version, 当前 x 的值为 a”。
    2. 否则, 向客户端返回“执行失败,当前版本号为 last_timestamp.version, 当前 x 的值”
  4. 客户端接受到大部分服务端返回执行成功, 则该写请求已执行成功(服务端已达成共识)。
  5. 否则:
    1. 如果大多数服务端返回 x 的当前值为 a 或 nil(因为 timestamp 过期导致写入操作失败), 则令版本号为返回版本号的最大值加一, 并再次发起 x=a 的写请求操作(回到第 2 步)。
    2. 否则, 数据冲突, 写入失败。

下图展示了客户端 C1, C2 同时向服务端 S1, S2, S3 发起写请求的场景。其中, C1 发起写操作 x = 1, C2 发起写操作 x = 2。最终服务端达成共识,成功写入 x = 2。
image

  • 读请求流程
  1. 客户端向服务端广播读请求
  2. 当大多数服务端已返回数据, 将 timestamp 最新的数据作为最终结果返回。

下图展示了客户端 C1 同时向服务端 S1, S2, S3 发起读请求, C2 发起 x=2 的写请求的场景。C1 最终读取到的数据为 x = 2。

image

  • Corner Case: 如下图,客户端 C1, C2 同时向服务端 S1, S2, S3 发起写请求的场景。其中, C1 发起写操作 x = 1, C2 发起写操作 x = 2。
  1. 图中点 A 时刻到点 D 时刻之间,用户读取到的数据可能是 x=2(用户读取 S1,S2 或 S1,S3),也可能是 x=nil(用户读取 S2, S3)。这违背了第三条 Safety Property(只有被选中的 value 才能被 “看见”)。
  2. 图中点 D 时刻到点 B 时刻之间, 用户可能读取到 x=2(用户读取 S1,S2 或 S1,S3),或 x=1(用户读取 S2, S3)。这同样违背了第三条 Safety Property。
  3. 点 B 时刻到点 C 时刻之间, 用户读到的数据为 x=1。但在点 C 时刻之后, 用户读到的数据却变为了 x=2。而这两个数据均是服务端达成的共识(值唯一),因此, 违背了第二条 Safety Property(只能有一个 value 能被服务端选中。某个 value 一旦被服务端达成共识而选中, 它将不可被更)。

image

  • Corner Case 的处理
  1. 点 A 时刻到点 D 时刻之间: 将读操作逻辑以大多数server返回的值为最终的结果值(而不是 latest version 的 value)。这样在 A->D 读到的数据为 (2, nil, nil), 大多数的值为 nil, 因此结果始终为 nil。(x = 2 并未达成共识, 因此无法被读取)。

  2. 点 D 时刻到点 B 时刻之间: 服务端在每次接收到 get version 操作后, 都将自身的 version 加一并返回, 同时限制过期的 version 的写入。(仅接受 version 号和自身相同的 write 请求)。但这么一来就会出现图中 S3 先接受了 C1 的写请求(x=1), 再接收到 C2 的 get version 请求。而 S1, S2 则正好相反的情况。这种情况下, 可能会出现下图的点 D 时刻到点 C 时刻之间, 业务读取到的数据为 (2, nil, 1), 不存在大多数派的数据, 服务端未达成共识, 可以认为当前的共识为 x=nil。
    image

  3. 点 B 时刻到点 C 时刻之间(x=1)以及点 C 时刻之后(x=2): 2 的解决方案, 同时也能解决该问题。

以上处理 Corner Case 的逻辑已经可以管中窥豹,看到一些 paxos 算法的雏形了。我们先按下不表,先看下如何改造 两阶段提交(2PC) 算法实现单一致性算法。

改造 2PC(Two Phase Commit) 算法

2PC(Two-Phase Commit) 是一种用于解决多个服务器如何就同一分布式事务达成一致的方法。在 2PC 中,多个服务器分别处理用于执行同一个写入操作的不同子请求(比如某个写请求需要处理不同服务器上的 partition 的数据)。它们必须同时成功地将写入提交(committed),否则就会同时失败并回滚(aborted)。[1] [14]

我们可以将同一个写请求通过 2PC 实现的事务在不同的服务器上执行一遍,实现 replication 功能。该方式能够保证不同服务器上的数据要么同时写入, 要么同时回滚,从而使得服务器间对该数据达成共识。

用上述方式实现的共识算法虽然扩展了服务器的读性能, 但是一旦某个服务器崩溃,所有的写请求都会失败(2PC 需要保证所有的服务器都执行成功)。这将导致集群的可用性大幅降低。

为了提高集群的可用性,我们引入上文的 Quorum 算法改造 2PC 算法,以实现读性能高可用的同步扩展。

2PC 算法有两个角色,一个是发起事务操作的客户端 TM(transaction manager), 一个是处理事务的服务器 RM(resource manager)。 2PC 单一致性算法保证 TM 提交的写操作在 RM 集群达成共识。

  • 写请求流程
  1. TM 将写请求的 prepare 消息广播给各个 RM。
  2. RM 接收到 prepare 请求后(相当于加了写锁),
    1. RM 前处于 preparing 状态,且 prepare 请求和 RM 中 preparing 的请求不一致,则请求冲突,返回 prepare 失败。
    2. 当前已有提交的值,且 prepare 请求写入的值和 RM 中已提交的值不一致,则请求冲突,返回 prepare 失败。
    3. 否则, 保存 prepare 信息, 将 RM 状态置为 preparing, 返回 prepare 成功。
  3. 当TM 接收到大多数 RM 返回 prepare 失败,则向 RM 广播 abort 消息,写操作执行失败。 若大多数 RM 都返回 prepare 执行成功, 则向所有 RM 广播 commit 操作。写操作执行成功。
  4. RM 接收到 commit 请求后,写入数据,并将 RM 恢复初始状态。
  5. RM 接受到 abort 请求后,并将 RM 恢复初始状态。

下图展示了 TM1, TM2 并发发起写操作, TM1 写入成功, 而 TM2 回滚的场景。

image

  • 读操作流程
  1. TM 向 RM 广播读请求
  2. 当大多数 RM 返回数据作为最终结果返回,如果不存在多数派数据, 则表示 RM 并未达成共识, 返回 nil。

下图展示了 TM1 发起写操作 x=1, TM2 同时发起读操作 read x 的场景。TM2 最终读取到的数据为 x=1。
image

  • Corner Case
  1. 上图中, 如果 TM1 在点 A 时刻到点 B 时刻崩溃,其他 TM 虽然可以处理其他 key 的数据,但是 x 已经处于 prepare x=1 状态,其他 TM 只能发起 x=1 操作(而不能做其他处理)。如下图,TM2 发起 x=2 写操作失败,但是它发起 x=1 写操作成功。
    image

  2. 如下图,如果存在三个 TM 对一个相同的 key 分别在三个不同的 RM prepare 了不同的 value, 则这个 key 将永远无法达成一致。
    image

  • Corner Case 的处理
  1. 点 A 时刻到点 B 时刻 TM1 崩溃: 后续的 prepare 可以覆盖前面的 prepare,但是需要通过 timestamp 来确定因果顺序(casual order)
  2. Corner Case 2 也可由 Corner Case 1 的解决方案解决处理。

解密: paxos 算法“略解”

众里寻他千百度,蓦然回首,那人正在灯火阑珊处

paxos 算法共有两个角色: 负责发起写请求的 proposer 和负责达成共识的 acceptor。[5] [6] [7] [10] [11]

  • 写操作流程
  1. proposer 指定一个 proposal number n, 向所有 acceptor 广播 prepare(n, key) 请求。其中 proposal number 的定义与 Quorum 算法提到的 timestamp 一致。
  2. acceptor 会将当前接收到的最大的 proposal number 记作 promise_number, acceptor 将会拒绝接受所有小于 promise_number 的 propose/prepare 请求。
  3. acceptor 接收到 prepare(n,key) 请求后, 如果 promise_numver > n, 则令 promise_number = n。向 proposer 返回当前 acceptor 已接收的 proposal number 和 value, promise_number。
  4. 当大多数 acceptor 都已成功接受 prepare 请求(promise_number = proposal_number):
    1. 如果响应均没有返回 accept proposal,则 proposer 发起 propose(n, key, value) 请求。
    2. 否则,令 value 等于响应中 accepted proposal number 最大的 accepted proposal value ,并发起 propose(n, key, value) 请求。
  5. 如果大多数 acceptor 都已拒绝 prepare 请求(promise_number > proposal_number),则令 n.version 等于 acceptor 返回的最大的 promise_number.version+1, 再次发起 prepare(n) 请求。
  6. 响应 propose(n, key, value) 请求:
    1. 如果 acceptor 的 promise_number 小于等于 n,则接受该值, 并且将 promise_number 置为 n,返回 promise_number
    2. 否则, 拒绝该 propose。返回 promise_number。
  7. 如果大多数已返回,且大多数 propose 被拒绝, 则令 n.version 等于 acceptor 返回的最大的 promise_number.version+1, 再次发起 prepare(n) 请求。否则, 大多数返回的 promise number 和 prepare 一致,value 已经写入成功。
    image
  • 读操作流程: 与上一部分 2PC 的读操作流程一致。

  • live lock: paxos 算法在 prepare 阶段很容易出现 live lock 的情况,如图 Proposer1 和 Proposer2 反复的刷新 promise_number, 却谁都无法抢占先机。 一般有两种处理手段: 其一, Proposer 利用选主算法(可以是一次 paxos)+lease策略, 让集群中始终只有一个 Proposer 处于激活状态[11] [13]。其二, 每次可以随机等待一段时间, 再重试, 减少冲突发生的概率。[4]
    image

附录: 伪代码

Quorum

  • 写请求
    image

  • 写响应
    image

  • 读请求
    image

2PC(Two Phase Commit)

  • 写请求
    image

  • 写响应
    image

  • 读请求
    image

Paxos

  • 写请求
    image

  • 写响应
    image

  • 读请求
    image

参考文献

  • [0] 本文所有绘图均使用 draw.io 绘制
  • [1] Designing Data-Intensive Applications - by Martin Kleppmann
  • [2] Implementing fault-tolerant services using the state machine approach: a tutorial
  • [3] In Search of an Understandable Consensus Algorithm(Extended Version)
  • [4] CONSENSUS: BRIDGING THEORY AND PRACTICE
  • [5] Paxos Made Moderately Complex
  • [6] Paxos Made Live - An Engineering Perspective
  • [7] The Part-Time Parliament
  • [8] DEFINING LIVENESS - Bowen ALPERN and Fred B. SCHNEIDER
  • [9] Quorum Consensus - web.mit.edu/6.033
  • [10] Paxos lecture (Raft user study)
  • [11] Paxos Made Simple
  • [12] How to Build a Highly Available System Using Consensus
  • [13] Leases: An Efficient Fault-Tolerant Mechanism for Distributed File Cache Consistency
  • [14] Consensus on Transaction Commit

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

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

相关文章

Flink CDC 原理

文章目录CDC&#xff0c;Change Data Capture 变更数据捕获 目前CDC有两种实现方式&#xff0c;一种是主动查询、一种是事件接收。 主动查询&#xff1a; 相关开源产品有Sqoop、Kafka JDBC Source等。 用户通常会在数据原表中的某个字段中&#xff0c;保存上次更新的时间戳或…

一篇博客教会你写序列化工具

文章目录什么是序列化&#xff1f;序列化格式JSON序列化精简序列化数据总结源码什么是序列化&#xff1f; 总所周知&#xff0c;在Java语言中&#xff0c;所有的数据都是以对象的形式存在Java堆中。 但是Java对象如果要存储在别的地方&#xff0c;那么单纯的Java对象就无法满…

我靠steam/csgo道具搬运实现财富自由

和莘莘学子一样&#xff0c;本着毕业对未来的憧憬&#xff0c;规划着漫漫人生&#xff0c;可是被残酷的事实打败。 直到一次偶然的同学聚会&#xff0c;谈及了现如今的生活才发现一片新大陆&#xff0c;通过信息差去赚取收益。 记得之前在校期间常常和哥几个通宵干CSGO,直到这…

Elasticsearch7.8.0版本高级查询—— 完全匹配查询文档

目录一、初始化文档数据二、完全匹配查询文档2.1、概述2.2、示例一、初始化文档数据 在 Postman 中&#xff0c;向 ES 服务器发 POST 请求 &#xff1a;http://localhost:9200/user/_doc/1&#xff0c;请求体内容为&#xff1a; { "name":"zhangsan", &quo…

【Nginx】使用Docker完成Nginx负载均衡+动静分离

前提是需要配置Nginx的反向代理&#xff0c;可以我看之前的文章 上篇Nginx配置动态代理的文章&#xff0c;我们在tomcat里写了两个简单html 这次我们依然采取同样的思路来演示负载均衡 一、负载均衡 1.在两个Tomcat容器&#xff08;我这里一个端口8081&#xff0c;一个8082…

Gradle vs Maven 基本对比(一)

Gradle 与Maven 的基本对比 对比目录&#xff1a; 1、工具包目录对比 2、创建项目结构对比 3、启动进程对比 4、性能对比 5、简洁性对比 什么是gradle: Gradle 是一个开源的运行在JVM上自动化构建工具&#xff0c;专注于灵活性和性能。Gradle 使用 Groovy 或 Kotlin DSL(领…

低代码平台飞速创软完成3000万元A+轮融资

疫情形势下&#xff0c;云原生全场景低代码及数字化基础设施提供商珠海飞速创软科技有限公司&#xff08;以下简称&#xff1a;飞速创软&#xff09;依然发展迅速&#xff0c;逆势而上。继2021年中获得珠海正菱创投、炼金术资本等机构A轮数千万融资之后&#xff0c;于2022年底&…

【手写 Vue2.x 源码】第三十一篇 - diff算法-比对优化(下)

一&#xff0c;前言 上篇&#xff0c;diff算法-比对优化&#xff08;上&#xff09;&#xff0c;主要涉及以下几个点&#xff1a; 介绍了如何进行儿子节点比对&#xff1b;新老儿子节点可能存在的3种情况及代码实现&#xff1b;新老节点都有儿子时的 diff 方案介绍与处理逻辑…

墙裂推荐,2023年最强、最实用的IDEA插件推荐合集

插件目录Alibaba Java Coding Guidelines(阿里巴巴java开发规范)Alibaba Cloud AI Coding Assistant(阿里云AI代码助理)Code Glance3(代码地图)Codota AI Autocomplete for Java and JavaScriptCSDN Tools(CSDN官方插件)FindBugsGenerateAllSetter Postfix Completion (自动生成…

小程序uni-app的api

小程序uni-app的apiuni api简介uni api使用uni-app自定义组件—传统方式核心步骤uni-app自定义组件—easycom简介核心步骤uni-app组件库uViewUIuview介绍关键步骤uni api简介 uni-api 指的是uni-app 针对一些 微信小程序api所做的封装它解决了两个问题 原生的小程序api不支持…

C/C++const关键字详解(全网最全)

目录 1、const修饰普通变量 2、const修饰指针 &#xff08;1&#xff09;const修饰p: &#xff08;2&#xff09;const修饰*p&#xff1a; &#xff08;3&#xff09;const修饰p和*p 4、const修饰数组 5、const修饰函数形参 &#xff08;1&#xff09;const修饰普通形参…

【数据结构】6.4 图的存储结构

文章目录6.4.1 邻接矩阵&#xff08;数组&#xff09;表示法无向图的邻接矩阵无向图邻接矩阵的特点有向图的邻接矩阵有向图邻接矩阵的特点网&#xff08;有权图&#xff09;的邻接矩阵采用邻接矩阵创建无向网邻接矩阵的优缺点6.4.2 邻接表&#xff08;链式&#xff09;无向图的…

【人工智能原理自学】初识Keras:轻松完成神经网络模型搭建

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;笔记来自B站UP主Ele实验室的《小白也能听懂的人工智能原理》。 &#x1f514;本文讲解初识Keras&#xff1a;轻松完成神经网络模型搭建&#xff0c;一起卷起来叭&#xff01; 目…

Eureka入门

Eureka入门Eureka入门什么是Eureka构建项目demo服务拆分远程调用创建Pom聚合工程Eureka使用搭建注册中心注册服务远程调用出现的问题Eureka入门 什么是Eureka Eureka是SpringCloud提供的注册中心&#xff0c;用来解决微服务之间远程调用问题&#xff0c;如&#xff1a; 消费…

交通流的微观模型研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Redis原理篇(四)内存回收

Redis之所以性能强&#xff0c;最主要原因是基于内存存储。但是单节点的Redis其内存大小不宜过大&#xff0c;会影响持久化或主从同步性能。 可以通过配置文件来设置最大内存 # maxmemory <bytes> maxmemory 1gb一、过期策略 可以通过expire命令给Redis的key设置TTL …

【C++算法图解专栏】一篇文章带你掌握高精度加减乘除运算

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4e3;专栏定位&#xff1a;为 0 基础刚入门数据结构与算法的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们一起交流~ &#x1f4da;专栏地址&#xff1a;https://blog.csdn.net/Newin…

Java 异常 笔记

异常体系结构 异常分为Error和Exception。Error通常是灾难性错误&#xff0c;一般发生时&#xff0c;JVM选择终止程序执行&#xff1b;Exception通常可在程序中进行处理&#xff0c;尽量避免 Exception分支中有一个重要子类RuntimeException&#xff0c;运行时异常 ArrayInd…

数据库,计算机网络、操作系统刷题笔记34

数据库&#xff0c;计算机网络、操作系统刷题笔记34 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle…

深入理解Promise

Promise的前提概念 Promise是一个构造函数&#xff0c;用来生成Promise实例 Promise构造函数接受一个函数作为参数&#xff0c;该函数有两个参数&#xff0c;分别是resolve和reject resolve&#xff1a;成功时的回调 reject&#xff1a;失败时的回调 Promise分别有三个状态 1…