Clickhouse的数据副本协同原理详解(借助ZK实现)

news2024/12/25 1:22:08

Clickhouse的数据副本协同原理详解(借助ZK实现)

文章目录

  • Clickhouse的数据副本协同原理详解(借助ZK实现)
    • Clickhouse数据副本
      • 副本的特点
    • ReplicatedMergeTree原理解析
      • 数据结构
      • Zookeeper内的节点结构
        • 元数据
        • 判断标识
        • 操作日志
      • Entry日志对象的数据结构
        • LogEntry
        • MutationEntry
    • 副本协同的核心流程
      • INSERT的核心执行流程
        • (1)创建第一个副本实例
        • (2)创建第二个副本实例
        • (3)向第一个副本实例写入数据
        • (4)由第一个副本实例推送Log日志
        • (5)第二个副本实例拉取Log日志
        • (6)第二个副本实例向其他副本发起下载请求
        • (7)第一个副本实例响应数据下载
        • (8)第二个副本实例下载数据并完成本地写入
      • ALTER的核心执行流程
        • (1)修改共享元数据
        • (2)监听共享元数据变更并各自执行本地修改
        • (3)确认所有副本完成修改

Clickhouse数据副本

Clickhouse具有丰富的表引擎,而与副本相关的表引擎则有Replicated+*MergeTree来构成,如下图所示
在这里插入图片描述

换言之,只有使用了ReplicatedMergeTree复制表系列引擎,才能应用副本的能力。**ReplicatedMergeTree时MergeTree的派生引擎,它在MergeTree的基础上加入了分布式协同的能力。**如下图所示:

在这里插入图片描述

上图展示了两个节点利用Zookeepr进行副本协同复制的原理图。在MergeTree中,一个数据分区由开始创建到全部完成,会经历两类存储区域:

  1. 内存:数据首先会被写入内存缓冲区
  2. 本地磁盘:数据接着会被写入tmp临时目录分区,待全部完成后再将临时目录重命名为正式分区

ReplicatedMergeTree在上述基础上增加了Zookeeper的部分,它会进一步在Zookeeper内创建一系列的监听节点,并以此实现多个实例之间的通信。在整个通信中,Zookeeper并不会涉及表数据的传输。

副本的特点

作为数据副本的主要实现载体,ReplicatedMergeTree在设计上有一些显著特点:

  1. 依赖Zookeeper:在执行Insert和Alter查询的时候,ReplicatedMergeTree需要借助Zookeeper的分布式协同能力,以实现多个副本之间的同步。但是在查询副本的时候,并不需要ZK。
  2. 表级别的副本:副本是在表级别定义的,所以每张表的副本配置都可以按照它的实际需求进行个性化定义,包括副本的数量,以及副本在集群内的分布位置等。
  3. 多主架构:可以在任意一个副本上执行Insert和Alter查询,他们的效果是相同的。这些操作会借助Zookeeper的协同能力被分发至每个副本以本地形式执行。
  4. Block数据块:在执行Insert命令写入数据时,会依据max_insert_block_size的大小(默认1048576行)将数据切分成若干个Block数据块。所以Block数据块是数据写入的基本单元,并且具有写入的原子性和唯一性。
  5. 原子性:在数据写入时,一个Block快内的数据要么全部写入成功,要么全部失败
  6. 唯一性:在写入一个Block数据块的时候,会按照当前Block数据块的数据顺序、数据行和数据大小等指标,计算Hash信息摘要并记录在案。在此之后,如果某个待写入的Block数据块与之前已被写入的Block数据块拥有相同的Hash摘要(Block数据块内的数据顺序、数据大小和数据行均相同),则该Block数据块会被忽略。这项设计可以预防由异常原因引起的Block数据块重复写入的问题。

ReplicatedMergeTree原理解析

ReplicatedMergeTree作为复制表系列的基础表引擎,涵盖了数据副本最为核心的逻辑。

数据结构

在ReplicatedMergeTree的核心逻辑中,大量运用了Zookeeper的能力,以实现多个ReplicatedMergeTree副本实例之间的协同,包括主副本选举、副本状态感知、操作日志分发、任务队列和BlockID去重判断等。在执行Insert数据写入、Merge分区和MUTATION操作的时候,都会涉及与Zookeeper的通信。但是在通信的过程中,并不会涉及任何表数据的传输,在查询数据的时候也不会访问Zookeeper。

Zookeeper内的节点结构

ReplicatedMergeTree需要依靠Zookeeper的事件监听机制以实现各个副本之间的协同。所以,**在每张ReplicatedMergeTree表的创建过程中,它会以zk_path为根路径,在Zookeeper中为这张表创建一组监听节点。**按照作用的不同,监听节点可以大致分成如下几类:

元数据

  • /metadata:保存元数据信息,包括主键、分区键、采样表达式等。
  • /columns: 保存列字段信息,包括列名称和数据类型
  • /replicas: 保存副本名称,对应设置参数中的replica_name

判断标识

  • /leader_election: 用于主副本的选举工作,主副本会主导MERGE和MUTATION操作(Alter Delete 和 Alter Update)。这些任务在主副本完成之后再借助Zookeeper将消息事件分发至其他副本。
  • **/blocks:**记录Block数据块的Hash信息摘要,以及对应的partition_id。通过Hash摘要能够判断Block数据块是否重复;通过partition_id,则能够找到需要同步的数据分区。
  • **/block_numbers:**按照分区的写入顺序,以相同的顺序记录partition_id。各个副本在本地进行Merge时,都会依照相同的block_numbers顺序进行
  • **/quorum:**记录quorum的数量,当至少有quorum数量的副本写入成功后,整个写操作才算成功。quorum的数据由insert_quorum参数控制,默认值为0。

操作日志

  • /log: 常规操作日志节点(INSERT、MERGE和DROP PARTITION),它是整个工作机制中最为重要的一环。保存了副本需要执行的任务指令。log使用了Zookeeper的持久顺序型节点,每条指令的名称以log-为前缀递增,利用log-0000000000、log-0000000001等。每一个副本实例都会监听/log节点,当有新的指令加入时,它们会把指令加入副本各自的任务队列,并执行任务。
  • /mutations: MUTATION操作日志节点,作用与log日志类似,当执行ALTER DELETE和ALTER UPDATE查询时,操作指令会被添加到这个节点。mutations同样使用了Zookeeper的持久顺序型节点,但是它的命名没有前缀,每条指令直接以递增数字的形式保存,例如0000000000、0000000001等。
  • /replicas/{replica_name}/*: 每个副本各自的节点下的一组监听节点,用于指导副本在本地执行具体的任务指令,其中较为重要的节点有如下几个:
    • /queue:任务队列节点,用于执行具体的操作任务。当副本从/log或/mutations节点监听到操作指令时,会将执行任务添加到该节点下,并基于队列执行
    • /log_pointer:log日志指针节点,记录了最后一次执行的log日志下标信息,例如log_pointer:4对应log/log-0000000003
    • mutation_pointer:mutations日志指针节点,记录了最后一次执行的mutations日志名称,例如mutation_pointer:0000000000对应了/mutations/0000000000。

Entry日志对象的数据结构

从上一小节的介绍中能够得知,ReplicatedMergeTree在Zookeeper中有两组非常重要的父节点,那就是/log和/mutations。它们是分发操作指令的信息通道,而发送指令的方式,则是为这些父节点添加子节点。所有的副本实例,都会监听父节点的变化,当有子节点被添加时,它们能够实时感知。

这些被添加的子节点在Clickhouse中被统一抽样为Entry对象,而具体实现则由LogEntry和MutationEntry对象承载,分别对应/log和/mutations节点。

LogEntry

LogEntry用于封装/log的子节点信息,它拥有如下几个核心属性:

  • source replica:发送这条Log指令的副本来源,对应replica_name
  • type:操作指令类型,主要有get、merge、和mutation三种,分别对应从远程副本下载分区、合并区分和MUTATION操作。
  • block_id:当前分区的BlockID,对应/blocks路径下子节点的名称
  • partition_name:当前分区目录的名称

MutationEntry

MutationEntry用于封装/mutations的子节点信息,它同样拥有如下几个核心属性:

  • source replica:发送这条MUTATION指令的副本来源,对应replica_name
  • commands:操作指令,主要有ALTER DELETE 和 ALTER UPDATE
  • mutation_id:MUTATION操作的版本号
  • partition_id:当前分区目录的ID

以上就是Entry日志对象的数据结构信息,在接下来将要介绍的核心流程中,将会看到它们的身影。

副本协同的核心流程

副本协同的核心流程主要有INSERT、MERGE、MUTATION和ALTER四种,分别对应了数据写入,分区合并,数据修改和元数据修改。INSERT和ALTER查询是分布式执行的。 借助Zookeeper的事件通知机制,多个副本之间会自动进行有效协同,但是它们不会使用Zookeeper存储任何分区数据。

INSERT的核心执行流程

当需要在ReplicatedMergeTree中执行INSERT查询以写入数据时,即会进入INSERT核心流程。整体流程如下图所示:

在这里插入图片描述

(1)创建第一个副本实例

假设首先从Linux121节点开始,对Linux121节点执行下面的语句后,会创建第一个副本实例

CREATE TABLE test.replicated_test_1
  (
      `id` String,
      `price` Float64,
      `create_time` DateTime
  )
ENGINE = ReplicatedMergeTree('/clickhouse/tables/01/replicated_test_1', 'linux121')
PARTITION BY toYYYYMM(create_time)
ORDER BY id;

在创建的过程中,ReplicatedMergeTree会进行一些初始化操作,例如:

  • 根据zk_path初始化所有的Zookeeper节点。

在这里插入图片描述

  • 在/replicas/节点下注册自己的副本实例Linux121

在这里插入图片描述

这里我因为已经在Linux122节点下创建了对应表副本,所以注册的replicas包括linux121和linux122

  • 启动监听任务,监听/log日志节点

  • 参与副本选举,选举出主副本,选举的方式是向/leader_election/插入子节点,第一个插入成功的副本就是主副本

(2)创建第二个副本实例

接着,在Linux122节点执行下面的语句,创建第二个副本实例。表结构和zk_path需要与第一个副本相同,而replica_name则需要设置成Linux122的域名

CREATE TABLE test.replicated_test_1
  (
      `id` String,
      `price` Float64,
      `create_time` DateTime
  )
ENGINE = ReplicatedMergeTree('/clickhouse/tables/01/replicated_test_1', 'linux122')
PARTITION BY toYYYYMM(create_time)
ORDER BY id;

在创建过程中,第二个ReplicatedMergeTree同样会进行一些初始化操作,例如:

  • 在/replicas/节点下注册自己的副本实例Linux122,如上面图中所示。
  • 启动监听任务,监听/log日志节点
  • 参与副本选举,选举出主副本。 在这个例子中,Linux121副本成为主副本

(3)向第一个副本实例写入数据

现在尝试向第一个副本Linux121写入数据。执行如下命令:

insert into replicated_test_1 values ('A001', 100.0, '2023-04-01 12:00:00');

上述命令执行之后,首先会在本地完成分区目录的写入,在clickhouse-server.log日志下可以找到对应记录:

2023.04.02 16:37:54.521226 [ 9683 ] {} <Trace> system.metric_log: Renaming temporary part tmp_insert_202304_3005_3005_0 to 202304_8903_8903_0.

此外,如果设置了insert_quorum参数(默认为0),并且insert_quorum >= 2,则Linux121会进一步监控已完成写入操作的副本个数,只有当写入副本个数大于或等于insert_quorum时,整个写入操作才算成功。

(4)由第一个副本实例推送Log日志

在第(3)步骤完成之后,会继续由执行了INSERT的副本向/log节点推送操作日志。在这个例子中,会由第一个副本Linux121担此重任。日志的编号为:/log/log-0000000000,而LogEntry的核心属性如下:

在这里插入图片描述

从日志内容可以看出,操作类型为get下载,而需要下载的分区是202304_0_0_0。其余所有副本都会基于Log日志以相同的顺序执行命令。

(5)第二个副本实例拉取Log日志

Linux122副本会一直监听/log节点变化,当Linux121推送了/log/log-0000000000之后,Linux122便会触发日志的拉取任务并更新log_pointer,将其指向最新日志下标:

/replicas/linux122/log_pointer: 0

在拉取了LogEntry之后,它并不会直接执行,而是将其转为任务对象放至队列:

/replicas/linux122/queue
Pulling 1 entries to queue: log-0000000000 - log-0000000000

这是因为在复杂的情况下,考虑到在同一时段内,会连续收到许多个LogEntry,所以使用队列的形式消化任务是一个更为合理的设计。注意,拉取的LogEntry是一个区间,这同样也是因为可能会连续收到多个LogEntry。

(6)第二个副本实例向其他副本发起下载请求

Linux122基于/queue队列开始执行任务。当看到type类型为get的时候,ReplicatedMergeTree会明白此时在远端的其他副本中已经成功写入了数据分区,而自己需要同步这些数据。

Linux122上的第二个副本实例会开始选择一个远端的其他副本作为数据的下载来源。远端副本的选择算法大致是这样的:

  1. 从/replicas节点拿到所有的副本节点
  2. 遍历这些副本,选取其中一个。选取的副本需要拥有最大的log_pointer下标,并且/queue子节点数量最少。log_pointer下标最大,意味着该副本执行的日志最多,数据应该更加完整。而/queue最小,则意味着该副本目前的任务执行负担最小。

在这个例子中,算法选择的远端副本是Linux121。于是Linux122副本向Linux121节点发起了HTTP请求,希望下载分区202304_0_0_0:

2023.04.02 16:10:28.848950 [ 7092 ] {} <Debug> test.replicated_test_1 (b07cd471-2267-4e0b-ba15-eafd33b020f3): Fetching part 202304_0_0_0 from /clickhouse/tables/01/replicated_test_1/replicas/linux121

如果第一次下载请求失败,在默认情况下,Linux122再尝试请求4次,一共会尝试5次(由max_fetch_partition_retries_count参数控制,默认为5)。

(7)第一个副本实例响应数据下载

Linux121的DataPartsExchange端口服务接收到调用请求,在得知对方来意之后,根据参数做出响应,将本地分区202304_0_0_0基于DataPartsExchange的服务响应返回Linux121:

Sending part 202404_0_0_0

(8)第二个副本实例下载数据并完成本地写入

Linux122副本在收到Linux121的分区数据后,首先将其写至临时目录:

tmp_fetch_202304_0_0_0

待全部数据接收完成之后,重命名该目录:

Renaming temporary part tmp_fetch_202304_0_0_0 to 202304_0_0_0

至此,整个写入流程结束。

可以看到,在INSERT的写入过程中,Zookeeper不会进行任何实质性的数据传输。本着谁执行谁负责的原则,在这个案例中由Linux121首先在本地写入了分区数据。之后,也由这个副本负责发送Log日志,通知其他副本下载数据。如果设置了insert_quorum并且insert_quorum>=2 ,则还会由该副本监控完成写入的副本数量。其他副本在接收到Log日志之后,会选择一个最合适的远端副本,点对点地下载分区数据。

ALTER的核心执行流程

当对ReplicatedMergeTree执行ALTER操作进行元数据修改的时候,即会进入ALTER部分的逻辑,例如增加、删除表字段等。而ALTER的核心流程如下图所示:

在这里插入图片描述

与INSERT的流程相比,ALTER的流程会简单很多,其执行过程并不会涉及/log日志的分发。整个流程从上至下按照时间顺序进行,其大致分成3个步骤。现在根据上图讲解整个过程:

(1)修改共享元数据

在Linux122节点尝试增加一个列字段,执行如下语句:

ALTER TABLE replicated_test_1 add column place String after id

执行之后,Linux122会修改Zookeeper内的共享元数据节点:

/metadata,/columns
Update shared metadata nodes in ZooKeeper. Waiting for replicas to apply changes

数据修改后,节点的版本号也会同时提升:

Version of metadata nodes in Zookeeper changed. Waiting for structure write lock.

与此同时,linux122还会负责监听所有副本的修改完成情况:

Waiting for linux121 to apply changes
Waiting for linux122 to apply changes

(2)监听共享元数据变更并各自执行本地修改

Linux121和Linux122两个副本分别监听共享元数据的变更。之后,它们会分别对本地的元数据版本号与共享版本号进行对比。在这个案例中,它们会发现本地版本号低于共享版本号,于是它们开始在各自的本地执行更新操作:

Metadata changed in Zookeeper. Applying changes locally
Applied changes to the metadata of the table

(3)确认所有副本完成修改

Linux122确认所有副本均已完成修改。

ALTER finished
Done processing query

至此,整个ALTER流程结束

可以看到,在ALTER整个的执行过程中,Zookeeper不会进行任何实质性的数据传输。所有的ALTER操作,最终都是由各个副本在本地完成的。本着谁执行谁负责的原则,在这个案例中,由linux122负责对共享元数据的修改以及对各个副本修改进度的监控。

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

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

相关文章

【AXU3EG】Zynq UltraScale平台启动引导过程

Zynq UltraScale MPSoC&#xff08;16nm&#xff09; 的核心是两个 CPU 模块&#xff1a; 应用处理单元&#xff08;APU&#xff09;&#xff1a;四核 ARM Cortex-A53&#xff0c;适合于 Linux 和裸机应用程序。实时处理单元&#xff08;RPU&#xff09;&#xff1a;双核 ARM …

Unity 高级程序员应该具备怎样的能力?要怎样成长为 Unity 高级程序员?

如何从零基础小白成长为 Unity 高级程序员&#xff1f;【全篇学习内容免费&#xff01;快来白嫖】 高能预警&#xff0c;下文包含从零基础新手到高级程序员一站式技术学习、学习方法、心态等内容&#xff0c;供各个阶段的同学进行参考。 从零基础到高级程序员 上干货 话不多说…

MySQL——存储过程和函数从零基础到入门必学教程(涵盖基础实战)

文章目录 目录 文章目录 前言 一、创建存储过程 二、在存储过程中使用变量 1.定义变量 2.为变量赋值 三、光标的使用 1.打开光标 2.打开光标 3.使用光标 4.关闭光标 四、流程控制的作用 1.IF语句 2.CASE语句 3.LOOP语句 4.LEAVE语句 5.ITERATE语句 6.REPEAT语…

碳酸氢锂硫酸锂溶液除钙镁

#碳酸氢锂硫酸锂溶液除钙镁 随着新能源汽车快速发展&#xff0c;以粗Li2CO3(85-98&#xff05;)和工业级Li2CO3(98.5-99.0&#xff05;)为原料制备高纯电池级Li2CO3(99.5-99.9&#xff05;)的工艺的突破&#xff0c;显得尤为重要&#xff0c;正越来越受到人们的关注。 粗Li2CO3…

智能排班系统

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 担任本次比赛的队长&#xff0c;这是我的个人博客主页&#xff1a; lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011…

安全技术交底大全

交底内容得过大&#xff0c;过宽&#xff0c;不要造成土建结构缺陷。02)管路敷设完后应立即进行保护其他工种在操作时应注意不要将管子砸扁和踩坏。(3)在碎板&#xff0c;加气板上别洞时&#xff0c;注意不要剔断钢筋&#xff0c;剔洞时应先用钻打孔&#xff0c;再护孔&#xf…

visual stdio c++调用python混合编程

visual stdio2019 c调用python混合编程 1.Python环境的搭建 关于环境创建可以参考https://blog.csdn.net/qq_36559788/article/details/123174309 这里python我用的是anaconda里的python38&#xff0c;并且没有debug库&#xff0c;所以我只用了release库 在vs中设置项目属性…

谈谈JVM的垃圾回收机制

目录 1. 死亡对象的判断算法 1.1 引用计数算法 1.2 可达性分析算法 2. 垃圾回收算法 2.1 标记——清除算法 2.2 复制算法 2.3 标记——整理算法 2.4 分代算法 1. 死亡对象的判断算法 对于支持垃圾回收机制的编程语言来说&#xff0c;常见的死亡对象的判断方法有引用计数…

成功上岸北大!总分418分,数学150分,经验贴+方法论

Datawhale干货 作者&#xff1a;葛云阳&#xff0c;杭州电子科技大学&#xff0c;Datawhale成员 前 言 大家好&#xff0c;我是北海。2023年以总分418分的成绩上岸北京大学信息工程学院计算机应用技术专业&#xff0c;其中初试第三&#xff0c;复试第五&#xff0c;总成绩第三…

《编程思维与实践》1038.排版

《编程思维与实践》1038.排版 题目 思路 分两个步骤进行解决: 1.在给定长度下找到每一行可以容纳的最多单词数(单词长度&#xff1c;M/2保证每行至少有两个单词); 2.输出时补充额外的空格. 其中,第一个步骤可以通过分割字符串将每个字符串存起来,再找到第一个超过给定长度M的单…

Java项目上线之云服务器环境篇(二)——Tomcat的安装与配置

Java项目上线之云服务器环境篇&#xff08;二&#xff09;——Tomcat的安装与配置 Tomcat的选择&#xff1a; 云服务器tomcat的选择最好与本机项目运行的tomcat版本号一致&#xff0c;避免一些不必要的问题。 配置步骤&#xff1a; 1、首先进入云服务器创建好放置tomcat的文件…

UE5实现Runtime环境下绘制线功能

文章目录 1.实现目标2.实现过程2.1 C++实现2.2 蓝图调用3.参考资料1.实现目标 UE5在Runtime环境下基于PDI绘制线,GIF动图如下: 2.实现过程 在UE5 Runtime环境下常用的绘制线方法有使用以下几种方式。一是基于SplineMeshComponent,即使用已有的点位去初始化样条线,然后在挂…

Ae:合成查看器 - 视图选项

合成查看器、素材查看器、图层查看器等面板底部的视图选项大同小异。 放大率弹出式菜单 Magnification ratio popup 用于显示和控制当前的放大率。 默认为适应当前面板大小。 更改放大率时&#xff0c;只是改变了面板中预览的外观&#xff0c;而不是合成的实际分辨率和像素。 适…

数据挖掘实验-week8-关联规则挖掘(Association Rule Mining)

Contents 0. 引言0.1 关联规则挖掘0.2 Apriori算法 实验Step 1&#xff1a;Familiarize yourself with the arules package in R.1.1 Load the package.1.2 To load data into R enter.1.3 To get information about the total number of transactions in a file sample1.csv e…

【requests模块上】——02爬虫基础——如桃花来

目录索引 requests请求&#xff1a;1. 基于get请求&#xff1a;*基础写法&#xff1a;**带参数的get请求&#xff1a;* 2. 基于post请求&#xff1a; 获取数据&#xff1a;1. 获取json数据&#xff1a;2. 获取二进制数据&#xff1a; 引入&#xff1a; requests是python的第…

不得不说的结构型模式-享元模式

目录 享元模式&#xff08;Flyweight Pattern&#xff09;&#xff1a; 下面是一个简单的C代码案例 下面是面试中可能遇到的问题&#xff1a; 享元模式&#xff08;Flyweight Pattern&#xff09;&#xff1a; 是一种结构型模式&#xff0c;它通过共享对象来减少系统中对象…

第四范式发布「式说」大模型,以生成式AI重构企业软件(AIGS)

4月26日&#xff0c;第四范式首次向公众展示其大模型产品「式说3.0」&#xff0c;并首次提出AIGS战略&#xff08;AI-Generated Software&#xff09;&#xff1a;以生成式AI重构企业软件。式说将定位为基于多模态大模型的新型开发平台&#xff0c;提升企业软件的体验和开发效率…

湿法冶金铼提取工艺

湿法冶金 通过溶液分离金属的技术 湿法冶金是利用浸出剂在一定温度压力下与矿石接触&#xff0c;把矿石中有用的金属溶解后再从溶液中回收有价金属的一种工艺&#xff0c;因为其过程大都是在水溶液中进行&#xff0c;所以又被称为“水法冶金”。 01 湿法冶金工艺特点及工艺流…

AI智能课程:第五讲chatGPT搞定APP小程序

内容总结 作业 目录 APP&小程序测试初识 ### APP&小程序测试的区别&#xff1f; 设计APP&小程序功能测试用例 设计APP&小程序功能测试用例–继续优化 设计APP&小程序测试用例-中断场景 设计小程序测试用例–补充小程序功能专项 APP&小程序兼容性测试…

Raft 共识算法4-选举限制

Raft 共识算法4-选举限制 Raft算法中译版地址&#xff1a;https://object.redisant.com/doc/raft%E4%B8%AD%E8%AF%91%E7%89%88-2023%E5%B9%B44%E6%9C%8823%E6%97%A5.pdf 英原论文地址&#xff1a;https://raft.github.io/raft.pdf Etcd Assistant 是一款 etcd 可视化管理软件&a…