【技术分享】万字长文图文并茂读懂高性能无锁 “B-Tree 改”:Bw-Tree

news2024/11/27 18:42:12

【技术分享】万字长文图文并茂读懂高性能无锁 “B-Tree 改”:Bw-Tree

原文链接: https://mp.weixin.qq.com/s/I5TphQP__tHn6JoPcP–_w
参考文献可能需要科学上网才能下载。如果你获取不到这几篇论文,可以关注公众号 IT技术小密圈 回复 bw-tree 获取。

一. 背景

Bw-Tree 希望实现以下能力:

  • 解决多核处理器性能瓶颈
    • 通过 CAS 操作实现 latch-free 能力, 提高多核 CPU 利用率。
    • 通过增量更新提高 CPU 缓存命中率。
  • 利用更为高效的闪存:虽然闪存有着相似的随机读速度和顺序读速度,但其随机写速度远小于顺序写操作。 Log-Structured Store(LSS), 可以很好的利用这一点实现高效读写。

二. 基于 Bw-Tree 的存储整体架构

Fig.1. The architecture of our Bw-tree atomic record store.

映射表

缓存层中维护着 映射表(mapping table), 保存逻辑页和物理页的映射关系,逻辑页由逻辑页标识符 PID 唯一标识。

映射表将 PID 映射为以下两种地址之一:

  • 闪存偏移量(flash offset): 持久化存储中的页的地址;
  • 内存指针(memory pointer): 内存页的地址。

The mapping table

BW-Tree 的节点指针都是逻辑的 PID,因此在 SMO 操作过程中, 某些节点的物理地址发生变化,并不需要更新所有对该节点有引用的所有节点指针(PID 并没有发生变化)。

增量更新

BW-Tree 通过创建描述变更内容的 增量记录(delta record) 并将其插入到当前页的前面来实现对页的状态变更。

如下图 (a) 中,先将对 Page P 的一次变更操作做成一个增量记录 ∆D,并让 ∆D 指向 Page P。然后将 Page P 的逻辑地址 PID P 映射的物理地址通过 CAS(compare and swap) 原子操作由 Page P 的物理地址改为 ∆D 的物理地址。(Page P 被称为 Base 页)

当变更导致前置的增量记录达到一定的规模之后,会触发合并操作,将所有的增量记录和原本的页合成一个新的页。

如下图(b) 中,将 Page P 前置的所有增量记录和 Page P 一起合并为一个 Consolidated Page P, 然后通过 CAS 操作将 Page P 的逻辑地址 PID P 映射的物理地址替换为 Consolidated Page P 的物理地址。Page P 及其前置的所有增量记录将会被垃圾回收机制回收处理。

In-memory pages. Page updates use compare-and-swap (CAS) on the physical pointer in the mapping table.

日志存储(Log Structure Store) 和 WAL(Write-Ahead Log) 日志

BW-Tree 在闪存中的存储结构如下图。当增量记录(∆record)达到一定数量之后,会执行一次刷盘操作将所有 Base 页的增量记录一起顺序写入磁盘。

这将会导致每一个 Base 页和它对应的许多 ∆record 并不在相邻的地址内,而闪存的随机读性能和顺序读性能几乎一致,因此可以接受。(如果是其他顺序读性能更好的持久化存储可能需要一定优化,后文有提及。)

Log-structured storage organization on flash

如上文所述, 并不是所有的变更操作都立即刷盘(而是会等待增量记录达到一定数量规模才会一次刷盘)。因此,在每次执行变更前,记录 WAL 日志也是必要的。

给每一次变更操作一个日志序列号(LSN), 当某次刷盘完成之后, 对应的最新 LSN 之前的 WAL 日志都可以失效。

BW-Tree 架构

整体架构

 **Architecture Overview** – An instance of a Bw-Tree with its internal logical links, Mapping Table links, and an ongoing CaS operation on the leaf Delta Chain.

如上图所示, BW-Tree 的每个节点都有唯一的逻辑地址 PID(N1, N2, …, Ni, …, Nj, …, Nk, …) 。节点之间不使用物理地址,而是使用逻辑地址 PID 相互引用。

当需要获取某个节点的物理地址时,会先查询映射表,将 PID 转化为物理地址。 因此在对单个原子的 CAS 指令就能实现对有多个引用的节点的物理地址进行变更。

BW-Tree 和其他基于 B+tree 索引直接最大的不同在于 BW-Tree 避免直接操作树的节点,而是直接将节点的增删改查保存增量记录中,这样极大地减少了 CPU 缓存失效的概率。

另外将每个 Base Page 的变更维护在一条 增量链(Delta Chain) 中,并通过中间层映射表隔离 Page 地址的变更(PID 保持不变), 使得可以在一次原子 CAS 中实现对 Page 进行变更操作。

逻辑节点的实现细节(Base 节点和 Delta 链)

如下图所示,在 BW-Tree 中,一个逻辑节点包含两部分: Base 节点和 Delta 链。Base 节点记录当前节点的在上一次合并(consolidate) 之后的数据,Delta 链记录在此之后 Base 节点发生的所有变更操作。

Delta 链将对 Base 节点的操作按照时间顺序用单向链表(物理指针)连接起来,链表的结尾处指向 Base 节点。

**Delta Records Overview** – A more detailed illustration of a logical leaf node from Fig. 1 with its base node and two delta nodes.

Base 节点和 Delta 链中的每一条 Delta 记录都保存了一些额外的元数据信息,它标识逻辑节点在某次操作时的状态(每次对某个节点做变更操作,都会将最新的状态记录在最新的 delta 记录中)。这些信息将会用于树的遍历等操作。

下表解释了这些元数据的内容。

  • low-key, high-key :当前逻辑节点的数据范围在区间 [low-key, high-key)。如上图中,逻辑节点的数据范围始终未变,在每个 Delta 记录及 Base 节点中都是 [K1, K8)
  • right-sibling: 指向右兄弟节点的逻辑地址(类似于 B-link tree)。如上图的兄弟节点 PID 为 N8
  • size: 记录当前逻辑节点的大小。如上图中。Base 节点的 size 为 5;在执行完 ∆delete [K1, V1] 操作后,size 变为了 4;在执行完 ∆insert [K2, V2] 操作后,size 又变为了 5
  • depth: 记录当前 Delta 记录在 Delta 链中与 Base 节点的距离。如上图中, ∆delete [K1, V1] 操作的 depth 为 1, ∆insert [K2, V2] 操作的 depth 为 2
  • offset:待操作的数据在当前 Base 节点的位置(而不是逻辑节点的位置,也就是说,不关 delta 链中其他节点什么事儿)。如上图中,∆delete [K1, V1] 操作中,K1 在 Base 节点的第一位,因此它的 offset 为 0∆insert [K2, V2] 操作中,K2 在 Base 节点的第二位,因此它的 offset 为 1

**Node Attributes** – The list of the attributes that are stored in the logical node’s elements (i.e., base node or delta records).

BW-Tree 的结构操作(Structure Modification Operation, SMO)

BW-Tree 的所有 SMO 操作都是通过原子操作实现的 latch-free 操作, 它将单个的 SMO 操作拆分为一些列 CAS 原子操作。为了确保没有线程需要等待其他线程的 SMO 操作结束,当它发现部分完成的 SMO 操作时,会在执行当前线程原本的任务之前,先将部分完成的 SMO 操作剩下部分执行完成。(help-along protocol)

下面本文将会详细介绍 BW-Tree 具体是如何实现这样的能力的。

分裂(Split)

**Split example.** Dashed arrows represent logical pointers, while solid arrows represent physical pointers.

与 B-link tree 类似, BW-Tree 将 split 分为两个阶段: 先将子节点用原子操作拆分为两个节点(half split), 然后将新的 分隔键(separator key) 和刚拆分的子节点的指针用原子操作更新到其父节点。

以上图将 O 节点的子节点 P 拆分为 P 节点和 Q 节点为例:

  1. 拆分子节点(half-split)
    (a) 创建 P 节点的兄弟节点 Q: 如上图 (a) 所示。申请一个新的 Page 作为 Q 节点;在节点 P 中找一个合适的键 Kp 作为节点 P,Q 的分隔键(separator key)。

    节点 P 仅保留小于 Kp 的数据,大于等于 Kp 的数据将拷贝到节点 Q;将节点 Q 的兄弟节点设为节点 R(即当前节点 P 的兄弟节点);将节点 Q 注册到地址映射表中。

    整个流程中,节点 Q 均不被用户可见,因此不需要原子操作。在这个阶段节点 P 依然处于为分裂状态。

    (b) 更新 P 节点, 将 Q 节点作为其兄弟节点:如上图 (b) 所示。为节点 P 创建执行分裂操作的 delta 记录(Split ∆), 该记录包含两个信息: 将 Kp 作为节点 P,Q 的分隔键以及让 Q 节点作为 P 节点的兄弟节点(让 P 逻辑节点的兄弟节点指针 right-sibling 指向 Q 节点的逻辑地址 Q); 然后调用 CAS 原子操作将逻辑地址 P 指向 Split ∆ 的地址。

    当 CAS 操作完成时,对节点 P, Q 的所有查询,都将会被父节点 O 路由到 P 逻辑节点(Split ∆)。如果待查询的 K 小于 Kp, 查询将会被路由到节点 P。若 K 大于等于 Kp, 查询将会通过 right-sibling 路由到节点 Q。

  2. 更新父节点:要实现直接从父节点 O 路由到刚被分裂的节点 Q(而不经过节点 P),需要将节点 Q 的信息更新到节点 O 中。如上图 © 所示。 先创建一个指向节点 O 的 Delta 记录 Index entry ∆,它包含了三个信息: (a) 节点 P, Q 的分隔键 Kp; (b) 指向节点 Q 的逻辑地址;© 节点 Q 和其 right-sibling 的分隔键 Kq(Kp 和 Kq 确定出节点 Q Key 的范围 [Kp, Kq))。

合并(Merge)

**Merge example.** Dashed arrows represent logical pointers, while solid arrows represent physical pointers.

如上图所示,当某个节点的大小小于某个阈值,BW-Tree 将使用 latch-free 的方式将它合并到其他节点(BW-Tree 仅支持与左兄弟节点合并)。

以上图将 P 节点的子节点 R 合并到节点 L 为例:

  1. 将 R 节点标记为删除: 如上图 (a) 所示。为节点 R 新增 Delta 记录 Remove Node ∆, 用于将逻辑节点标记为删除。当查询访问到 Remove Node ∆ 节点,将会跳转到节点 R 左边的兄弟节点,即节点 L。

  2. 合并子节点: 如上图 (b) 所示。为节点 L 新增 Delta 记录 Merge ∆,该记录将节点 L 与节点 R 合并起来作为一个逻辑节点整体。

    在步骤 1 到步骤 2 之间,实际上是无法感知到节点 R 的。(因为节点 R 已经被Remove Node ∆ 节点逻辑移除了 )。在步骤 2 执行之后, 才能通过 Remove Node ∆ 跳转到 R 的左兄弟节点 L, 通过 Merge ∆ 查询到节点 R 的值。

    但是这并不会影响并发操作的正确性,因为 help-along protocol 会保证在发现其他线程存在未完成 SMO 操作的情况下,先将 SMO 操作执行完成,再进行原本的操作。因此就不会在步骤 1 到步骤 2 之间去对节点 R 进行操作。

  3. 更新父节点: 如上图 (a) 所示。父节点添加 Delta 记录 ∆ Delete Index Term for R,用于将节点 R 在父节点中的索引删除。节点 L 将节点 R 的索引范围也纳入其中。

    在这个阶段之后,Remove Node ∆ 这个 delta record 和节点 R 在地址映射表中的位置都将不再被使用, 他们将会被 epoch GC 逻辑回收。

点查询(Search)

  • 唯一键查询: 唯一建的查询和普通的 B+ 树类似,唯一的区别在于,当遍历到叶节点时,如果存在 Delta 链,它会先依次遍历 Delta 链,并将最先出现的结果返回。当 Delta 链中不存在时,才会去 Base 节点执行二分查找。

  • 非唯一建查询:当定位到数据仅可能存在在某个叶子节点时,必须遍历所有的 Delta 链和 Base 节点才能查找出指定键的所有值。操作逻辑如下图。

    在遍历 Delta 链的过程中,将已知符合要求的数据放在集合 Spresent, 将已知被删除的数据放在集合 Sdeleted。按顺序遍历 Delta 链时,当遍历到插入 Delta 记录(K, V) 时,如果 V 不在 Sdeleted,则将其加入 Spresent。当遍历到删除 Delta 记录(K, V) 时,如果 V 不在 Spresent,则将其加入 Sdeleted。

    Sbase 为 Base 节点中的该键的所有值的集合。 则最终的查询结果为 Spresent ∪ (Sbase - Sdeleted)

**Non-unique Key Support** – The two sets (Spresent , Sdeleted ) track the visibility of ∆insert and ∆delete records in the Delta Chain.

遍历(Scan)

  • 正向 Scan: 正向遍历会将正在处理的节点拷贝到迭代器中。当迭代器中保存的节点的数据全部遍历完成,就会继续将下一个节点的数据全部拷贝到迭代器继续遍历。因此,整个 Scan 过程读取的数据并不是一个快照(snapshot)的数据

    如下图所示,当遍历完一个节点 N0 [K0, K1) 的数据,会查找该节点的上界 K1 所在的节点作为下一个节点。 如果遍历 N0 过程中, N0 发生的 SMO 操作是的 N0 键的范围变大, 该节点的上界 K1 所在的节点依然是 N0, 则将新的 N0 拷贝到迭代器中。然后查找到 K1 的位置,继续遍历该节点。

**Forward Iteration with Concurrent Merge**

  • 反向 Scan:反向 Scan 整体逻辑和正向 Scan 一致。唯一的区别在于反向 Scan 的下一个节点的查找方式有所不同。反向 Scan 遍历完一个叶子节点后,会将小于该叶子节点的下界的最大的 Key所在的叶子节点作为下一个遍历的节点。

    如下图,N1 的下界是 K5, 小于 K5 的最大键为 K4(N0), 因此, K4 所在的节点 N0 就是就是 N1 遍历完之后,下一个需要遍历的节点。

**Backward Iteration** – For backward iteration using K5 as the low key, the path is [(K1, P1), (K2, P3), (K3, P5), (K4, N0)]. This is achieved by always going left when a separator item with key K5 is seen during inner node search.

对 BW-Tree 的优化

  • Delta 记录的预分配: 如下图, 提前为内存中的 Delta记录预分配内存空间,减少内存碎片。

**Pre-allocated Chunk** – This diagram depicts the logical view and physical view of a OpenBw-Tree node. Slots are acquired by threads using a CaS on the marker, which is part of the allocation metadata on lower-address of the chunk.

  • 用去中心化 Epoch GC 替代中心化 Epoch GC

    • 中心化 Epoch GC: 如下图 (a) 所示, 唯一的 GC 线程(Background Thread) 维护 Epoch 链表。每个 Epoch 节点维护引用当前 Epoch 删除的资源及其引用线程数的总和。当某个 Epoch 的线程引用计数恢复 0 时,该 Epoch 及其维护的垃圾资源可以被删除。如下图中的 Epoch 101。

    • 去中心化 Epoch GC
      (1) 全局 Global Epoch 维护全局 Epoch 时钟 e_global。每个工作线程产生的垃圾节点由本线程维护在本地垃圾回收链表 l_local, 并将该垃圾节点的 e_delete 设置为当前进程的 e_local。

      (2) 当某个线程开始索引操作时,会先将当前的全局 Epoch 时钟 e_global 拷贝到当前线程,记作 e_local。当该索引操作结束后,会再次将 e_local 刷新为 e_global。
      (3) 每个工作现场会定期获取当前全局最小的 e_local, 并将本线程维护的 l_local 中 e_delete 小于全局最小 e_local 的垃圾节点回收。
      **Garbage Collection*

  • 快速整合(Fast Consolidation):

    • 将原 Base 节点的键区间作为一个整体 [start, end). 依次遍历 Delta 链,将键区间分为多个部分。
      (1) 当遍历到插入 Delta 记录,则将当前记录所在区间 [s, e) 拆分为 [s, offset)[offset, e)
      (2) 当遍历到删除 Delta 记录,则将当前记录所在区间 [s, e) 拆分为 [s, offset)[offset+1, e)
      (3) 如果删除 Delta 记录删除的数据不在 Base 节点中,则忽略该记录。
    • 上述操作将 Base 节点拆分为多个部分。然后将拆分的多个部分和新插入数据一起整合成新的 Base 节点。

Fast Consolidation

  • 节点搜索快捷方式(Node Search Shortcuts)
    • 当工作线程遍历 Delta 链查找键 K 时, 它会初始化二分查找的偏移量(offset) [min. max) 范围为 [0, +inf)。遍历过程中,当遇到键位 K’, 偏移量为 offset 的 ∆insert 或 ∆delete 记录,它会比较 K 与 K’。若 K=K’, 则立即得到 K 所在偏移量为 `[offset, offset+1)``,不用在 Base 节点进行二分查找。若 offset > min 并且 K>K′, 则将 min 设为 offset。 若 offset < max 并且 K <K′, 将 max 设为 offset。如果最后的区间大小大于 1, 则在偏移量区间内二分查找键 K。

    • 如下图中的例子, 最终得到的区间是 [2, 5), 因此最后只需要在 Base 节点中 offset 在 [2. 5) 区间内的键二分查找 Key=6。

**Node Search Shortcuts** – This diagram illustrates how thread makes use of the offset attribute. On the base level the thread searches only three elements instead of five.

其他

本文更多的是介绍内存内的 BW-Tree 的维护逻辑,更多关于持久化数据的维护相关的内容请查看 LLAMA: A Cache/Storage Subsystem for Modern Hardware。后续我也会在公众号 IT技术小密圈 更新对该论文的分享,欢迎关注。

参考文献

  • Building a Bw-Tree Takes More Than Just Buzz Words
  • The Bw-Tree: A B-tree for New Hardware
    Platforms
  • LLAMA: A Cache/Storage Subsystem for Modern Hardware

参考文献可能需要科学上网才能下载。如果你获取不到这几篇论文,可以关注公众号 IT技术小密圈 回复 bw-tree 获取。

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

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

相关文章

类和对象初阶

目录 一、再谈构造函数 1.1 构造函数体赋值 1.2 初始化列表 1.3 注意 1.4 总结 二、拷贝对象时的一些编译器优化 三、static成员 3.1 静态成员变量 3.1.1 引入 3.1.2 特点 3.1.3 区别 3.2 静态成员函数 3.2.1 引入 3.2.2 特点 3.2.3 例题 四、友元 4.1 友元函…

数据结构与算法·第2章【线性表】

线性结构具有以下基本特征&#xff1a; 有唯一的一个被称为首元素&#xff08;或头元素&#xff09;的元素&#xff0c;没有直接前驱&#xff1b;有唯一的一个被称为尾元素&#xff08;或尾节点&#xff09;的元素&#xff0c;没有直接后继。 数据元素之间存在一对一的线性关…

python 实现单链表

链表 链表是一种在存储单元上非连续、非顺序的存储结构。数据元素的逻辑顺序是通过链表中的指针链接次序实现。 链表是由一系列的结点组成&#xff0c;结点可以在运行时动态生成。每个结点包含两部分&#xff1a;数据域与指针域。数据域存储数据元素&#xff0c;指针域存储下一…

Yapi内网部署[CentOS7]

mongo安装 # 下载MongoDB https://www.mongodb.com/try/download/community4.2.24 RedHat/CentOS7.0 tgz# 安装MongoDB mkdir -p /home/jpge/devp-tools/tools cd /home/jpge/devp-tools/tools wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-4.2.24.tgz…

线性代数:线性方程求解、矩阵的逆、线性组合、线性独立

本文参考www.deeplearningbook.org一书第二章2.3 Identity and Inverse Matrices 2.4 Linear Dependence and Span 本文围绕线性方程求解依次介绍矩阵的逆、线性组合、线性独立等线性代数的基础知识点。 一、线性方程 本文主要围绕求解线性方程展开&#xff0c;我们先把线性…

揭秘Redis持久化原理,探索fork与Copy-on-Write的魔法!

大家好&#xff0c;我是小米&#xff0c;今天我将和大家一起探索Redis持久化原理中的两个关键概念&#xff1a;fork和Copy-on-Write。这两个概念对于理解Redis的数据持久化机制至关重要。让我们一起来揭开这些技术的神秘面纱吧&#xff01; Redis持久化简介 在开始之前&#…

组合数学第四讲

Generating Function&#xff08;生成函数&#xff09; 这里是一个普通生成函数例子&#xff0c;生成函数一般适用于根据递推关系式求出比较复杂的通项公式的 关键点&#xff1a; 1.项可转换成G(x)-,因为生成函数规定是从0到∞的 2.,当|x|<1时&#xff0c;最终可收敛为。这里…

基础算法(六):回溯算法

前言 Hello大家好&#xff0c;停了半个多月算法学习的荔枝又变菜了&#xff0c;最近决定认认真真地重新学习回溯&#xff0c;无意间看到Carl哥的代码随想录&#xff0c;感动之余也是跟着一步步走&#xff0c;后悔上车晚了呜呜呜~~~。之前自己摸索确实有点难受&#xff0c;在这篇…

YUM安装LNMP架构配置命令与搭建论坛

LNMP架构配置命令与搭建论坛 LNMP简介安装须知安装 Nginx配置yum源yum安装nginx并查看版本号开启服务并且设为开机自启 安装 MySQL 5.7 (mariadb)移除软件包下载安装mysql安装MySQL报错问题解决方案&#xff1a; 开启服务并设为开机自启在日志文件中找出root用户的初始密码登录…

数据结构 --- 树

1、二叉树 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 每个结点最多有两棵子树&#xff0c;即二叉…

CAPL(vTESTStudio) - CAPL控制RS232继电器

目录 为什么要使用CAPL控制继电器? 一、RS232继电器选择 二、继电器通信协议

AList 一个支持多种存储的文件列表程序,使用 Gin 和 Solidjs。

一个支持多种存储&#xff0c;支持网页浏览和 WebDAV 的文件列表程序&#xff0c;由 gin 和 Solidjs 驱动。 特点 使用简单 AList 从一开始就设计为易于安装&#xff0c;并且可以在所有平台上使用。 多种存储 AList 支持多个存储提供商&#xff0c;包括本地存储、阿里云盘、O…

大数据治理入门系列:数据治理

在信息经济时代&#xff0c;数据是企业的一大关键资产。为了制定科学、有效、合理的决策&#xff0c;企业需要收集大量的数据并进行各种数据分析&#xff0c;为决策提供依据。在此过程中&#xff0c;收集数据的速度、数据的质量和可靠性、对数据的分析过程、合适的分析工具等&a…

三十四、数学知识——约数(试除法 + 约数个数 + 约数之和 + 欧几里得算法)

约数相关算法主要内容 一、基本思路1、定义2、试除法——求一个数的所有约数3、约数个数4、约数之和5、欧几里得算法——辗转相除法&#xff08;求解最大公约数&#xff09; 二、Java、C语言模板实现三、例题题解 一、基本思路 1、定义 约数&#xff1a; 约数是指一个数&…

利用百度API进行植物识别

植物识别_拍照识别植物-百度AI开放平台百度AI植物识别,支持识别超过2万种通用植物和近8千种花卉&#xff0c;接口返回植物的名称&#xff0c;并获取百科信息&#xff0c;适用于拍照识图类APP中https://ai.baidu.com/tech/imagerecognition/plant 偶然看到的&#xff0c;不过真…

STM32F103C8T6+2.4寸SPI TFT触摸屏代码+标准库 项目开发

目录 模块清单&#xff1a; 模块介绍&#xff1a; 1&#xff1a;STM32F103C8T6 2&#xff1a;2.4寸SPI TFT触摸屏 项目结果展示 2.4寸 TFT SPI显示触摸屏 2.4寸 SPI TFT 显示触摸屏代码下载链接&#xff1a; (1条消息) 2.4寸SPITFT显示触摸屏资源-CSDN文库 模块清单&#x…

Vue后台管理系统【附源码】

登录 – 完成 路由拦截 – 完成 商品管理&#xff08;增加、编辑、搜索、删除&#xff09; – 完成 角色管理&#xff08;增加、编辑、搜索、删除、权限管理&#xff09; – 完成 交易订单&#xff08;增加、编辑、搜索、删除&#xff09; – 完成 用户管理&#xff08;增加、编…

在Centos Stream 9上Docker的实操教程 - 实操准备篇

在Centos Stream 9上Docker的实操教程 - 实操准备篇 认识Docker准备Centos Stream 9安装Docker更新仓库绕不开的HelloWorld结语 认识Docker 什么都要实操了&#xff0c;你还不知道Docker是什么&#xff1f;网上关于Docker的介绍一搜一大把&#xff0c;博主就不必浪费时间去侃侃…

ESP32-OTA

文章目录 1. 什么是OTA&#xff1f;2. OTA的基本原理3. ESP32远程OTA步骤&#xff1a;3.1 将需要升级的程序放在该目录下3.2 启动HTTP服务器3.3 配置3.4 烧录程序3.5 上电测试ESP32端 4. 问题&#xff1a;5. 通过命令控制OTA6. 参考&#xff1a; 1. 什么是OTA&#xff1f; OTA…