es基本概念-自学笔记
小白刚学,看了一些概念,避免忘记,以此记录。
基本概念
node,index索引,分片,副本,doc values 和 segment,lucence,FST,压缩数组智能存储 是什么
一、lucence(驱动es的核心引擎)
lucence是apache旗下的代码库,工具包。lucene 是一个超级强大的 V12 发动机,es是超跑。
它的存在只为解决一件事 ——如何以最快速度从海量文本中找出最相关的文档。它不负责网络通信、数据存储、分布式协调等。
极致性能: 它的设计目标就是 “快”!通过:
-
神奇的倒排索引结构: 直接告诉你哪些文档包含关键词,而不是扫描所有文档。
-
高效数据结构: FST 压缩字典省内存、快速定位词;FOR/RLE/Packed 压缩文档列表省内存、加速遍历和集合操作。
-
强大的评分算法: 计算文档与查询的相关度分数(如 TF-IDF, BM25),保证结果质量。
1. 倒排索引
其实很简单,你想要在购物商城搜索”苹果手机“,那么肯定是基于这个词进行搜索,就要有词对应的文档信息。比如”苹果手机“存在文档1和文档2中,这样的简单的结构就是倒排索引。
构建的所有词称为term dictornary
为了搜索方便,还有个term index,就是把所有的词进行字典序排列,这样查找就变成了二分查找。
2. FST(前缀树)
所有的数据存储到硬盘上,因为词太多了,但是为了加快查询速度,会把词的索引信息加载到内存中,构建一个类似前缀树的结构,这样既节省了内存也加快了查询速度。
3. 压缩数组智能存储
“压缩数组智能存储”如何智能地解决?
它采用了一套组合拳:FOR (Frame Of Reference) + Packed Block + (可选的) RLE (Run-Length Encoding)。我们拆解来看:
1. Frame Of Reference (FOR - 帧参考 / 差值编码)
- 本质: 利用文档 ID 的局部递增特性,存储差值而不是绝对值。 把大整数变成小整数。
- 怎么做:
- 将文档 ID 列表分成固定大小的 块 (Block) (比如 128 个 DocID 一个块)。
- 对每个块:
- 取块里的第一个文档 ID 作为基准值 。
- 计算块内后续每个 DocID 与基准值的差值。
- 示例:
- 原始 DocID Block:
[1000, 1003, 1005, 1010, 1021]
- 基准值 (MinID):
1000
- 差值 Delta List:
[0, 3, 5, 10, 21]
(1000 - 1000 = 0, 1003 - 1000 = 3, …) - 效果: 原本需要存储 5 个 可能很大的
int
(比如范围在 0~几十亿)。现在存储 1 个较大的int
(基准值 1000) + 5 个很小的int
(0, 3, 5, 10, 21)。小整数是压缩的关键!
- 原始 DocID Block:
2. Packed Blocks (打包块 / 位压缩)
- 本质: 利用差值(或原始小数值)都很小的特点,按需分配比特位,而不是固定32位。
- 怎么做:
- 拿到一个块的计算后的差值列表 (或者对于词频列表,直接用原始的小数值列表)。
- 找出这个列表中的最大值。
- 计算存储这个最大值所需的最少比特位数。
- 使用一个紧凑的位打包结构来存储这个块的所有值:每个值只占用计算出来的比特位数。
- 示例:
- 差值列表 Delta List:
[0, 3, 5, 10, 21]
- 最大值 Max Delta:
21
- 存储
21
需要5
个比特位 (因为 2^5=32 > 21)。 - 将每个差值用
5
个比特位存储:0
->00000
,3
->00011
,5
->00101
,10
->01010
,21
->10101
。 - 将它们紧密打包成一个连续的比特流:
00000 00011 00101 01010 10101
(这个比特流在内存中就是连续的字节)。
- 差值列表 Delta List:
- 效果:
- 原始存储:5个
int
= 5 * 4字节 = 20字节。 - FOR + Packed: 1个基准值(4字节) + 5个5位值 = 4字节 + (5 * 5bits) / 8 bits/byte ≈ 4字节 + 3.125字节 ≈ 7.125字节 (节约了 64% 以上!)。
- 对于大量词频为 1 的块,压缩率更高(可能只需要 1-2 个比特位)!
- 原始存储:5个
- CPU 优势: 一个块被打包在连续、紧凑的内存区域。当 ES 需要遍历这个块(比如做交集
AND
操作),它能高效地一次性将整个块或大部分块加载到 CPU 缓存中进行处理,避免了频繁访问主内存。位解压操作在现代 CPU 上也很快。
3. 可选的:Run-Length Encoding (RLE - 游程编码)
- 本质: 利用连续重复值的特性,用
(值, 连续出现次数)
对来存储,而不是存储每个单独的值。 - 适用场景: 特别适合词频列表。想象一个非常常见的词(如 “the”),它在成千上万个文档中的词频很可能大部分是 1。也可能在某个长文档中出现很多次。
- 与 FOR/Packed 结合:
- 在应用 FOR/Packed 之前或之后,检测块内是否有长串的连续相同值。
- 如果有,就用 RLE 压缩这些连续值。
- 例如,词频块
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1]
可能被编码为类似(1, 12), (2, 1), (3, 1), (1, 3)
的形式。
- 效果: 对包含大量相同词频的块,RLE 能带来极高的压缩率。
总结: “压缩数组智能存储” 的智能之处
- 分块 (Blocking): 将大数据集切分成小块,是后续优化的基础,提高 CPU 缓存利用率。
- FOR (差值编码): 利用数值局部递增性(文档ID)或值域相对较小(词频)的特性,将大数转化为小数。
- Packed Encoding (位压缩): 利用差值/词频都很小的特性,按需分配比特位,极致节省空间。
- (可选) RLE (游程编码): 利用连续重复值的特性,进一步提高压缩率(尤其对词频)。
- 空间与速度双赢:
- 省内存: 显著减少倒排索引中 DocID 和 Freq 列表的内存占用(通常压缩到原始大小的 1/5 到 1/10 甚至更低)。
- 提速度:
- CPU 缓存友好: 紧凑的块结构更容易放入高速 CPU 缓存,减少访问主内存的昂贵延迟。
- SIMD 加速潜力: 打包好的整块数据更适合现代 CPU 的 SIMD (单指令多数据) 指令进行并行处理(比如同时对块内多个差值做加法或比较)。
- 减少 IO: 内存占用小意味着更多索引数据可以常驻内存,减少磁盘 IO。
大白话一句话总结:
ES 的“压缩数组智能存储”就像给倒排索引里的数字列表(哪些文档有这个词、词频多少)做超级打包!它发现这些数字往往变化不大或者经常重复,于是:1) 先记录起点和差值(把大数变小);2) 然后像拼拼图一样只用最少的“小格子”(比特位)存这些小差值;3) 碰到一串一模一样的数(比如一堆词频是1),就记成“1出现100次”而不是写100个1。最终效果是:省了大把内存,查起来还飞快!
这就是 Elasticsearch (以及 Apache Lucene) 能在有限内存下支撑海量数据高效搜索的关键黑魔法之一。它和 FST (处理文本词表) 配合,共同构成了倒排索引高性能的基石。
4. 模糊查询
通过编辑距离(Levenshtein算法)来实现的,太远的距离就pass了,能节省很多时间。
二、其他词汇
1. node
node就是es的实例,一个进程。node有很多角色,承担不同的任务。
node的核心角色类型及其本质作用:
-
master
(主节点候选人 / 集群管家候选人):- 通俗本质: 它们是有资格竞选“集群总管”职位的员工。
- 核心职责:
- 集群管理: 维护集群状态(所有索引、分片、节点的元数据,称为
cluster state
)。 - 决策: 做出全局性决策:
- 创建/删除索引。
- 跟踪所有分片的位置 (哪个节点上有什么分片)。
- 决定分片的分配和迁移(当节点加入或离开时重新平衡数据)。
- 处理索引分片分配相关的设置。
- 协调节点加入/离开: 处理新节点加入集群和节点故障离开的事件。
- 集群管理: 维护集群状态(所有索引、分片、节点的元数据,称为
- 关键点:
master
角色只表示该节点有资格成为主节点或被选为主节点,不表示它当前一定是主节点。- 一个健康的集群必须至少有 3 个配置了
master
角色的节点(生产环境强烈推荐奇数个,如 3、5、7)。这是为了确保在少数节点故障时,仍然能形成一个法定人数 (Quorum) 来进行选举和管理。 - 主节点本身通常不存储数据 (除非同时配置了
data
角色)。这是为了让它专注于集群管理任务,避免因数据处理负载过重而影响集群稳定性。 - 负载: 主要是 CPU 和内存(用于维护集群状态和处理决策)。集群状态变更(如索引创建)会广播给所有
master
节点。
-
data
(数据节点 / 仓库管理员):- 通俗本质: 它们是实际保管书本(数据分片)和提供借阅服务(搜索、聚合)的仓库管理员。
- 核心职责:
- 存储分片: 存储分配给它的主分片和副本分片。
- 数据处理: 执行所有与数据相关的操作:
- 数据的写入、更新、删除。
- 对数据的搜索请求。
- 对数据的聚合计算。
- 执行字段值的排序等。
- 关键点:
- 这是存储和计算负载最重的节点,需要大量的 CPU、内存(特别是 JVM Heap)和磁盘 I/O。
- 集群的性能(吞吐量、延迟)主要取决于
data
节点的数量和能力。 - 通常配置为仅具有
data
角色(避免同时承担master
角色),以最大化其数据处理能力。
-
ingest
(预处理节点 / 数据整理员):- 通俗本质: 数据入库前的“流水线工人”,在书本上架(索引)之前对原始数据进行清洗、转换、丰富。
- 核心职责:
- 在文档被索引到
data
节点之前,应用预定义的预处理管道 (Ingest Pipeline)。 - 管道中可以执行的操作包括:
- 字段重命名、删除、添加。
- 数据类型转换。
- 文本处理(如小写转换)。
- 丰富数据(如根据 IP 查地理位置)。
- 解析复杂结构(如 CSV, JSON)。
- 在文档被索引到
- 关键点:
- 目的是在数据入库前将数据整理成更易用的格式,避免在
data
节点上执行昂贵的转换操作。 - 是可选角色。如果没有专门的
ingest
节点,data
节点或coordinating
节点也会执行管道处理(但会增加它们的负担)。
- 目的是在数据入库前将数据整理成更易用的格式,避免在
-
coordinating
(协调节点 / 接待员/调度员):- 通俗本质: 它是集群的统一入口和任务调度中心。
- 核心职责:
- 请求入口: 接收来自客户端应用程序 (如你的代码、Kibana) 的所有请求(搜索、写入)。
- 请求路由:
- 写请求: 根据文档 ID 确定应该写到哪个数据节点上的哪个分片(主分片)。
- 搜索请求: 将搜索请求分发到所有相关的
data
节点(包含目标分片的节点),收集并汇总来自这些节点的部分结果,最后将完整结果排序后返回给客户端。
- 预处理执行: 如果请求需要预处理且该节点有
ingest
角色,它会执行预处理管道。 - 结果合并: 处理搜索结果的排序、分页、聚合结果合并等。
- 关键点:
- 本身不存储数据 (
data: false
),不能是主节点 (master: false
),通常也不做预处理 (ingest: false
)。 它是纯协调角色。 - 在大集群或高查询负载的场景下,显式配置专门的
coordinating
节点非常重要:- 避免
data
节点宝贵的 CPU 和 I/O 资源被协调任务消耗。 - 提供一个可扩展的、无状态的入口层,方便负载均衡(在
coordinating
节点前加负载均衡器如 Nginx, HAProxy)。
- 避免
- 负载: 主要是 CPU 和网络 I/O(处理请求分发和结果合并)。
- 本身不存储数据 (
2. index索引
按业务需求进行的大的逻辑分区。
比如日志系统对接es,索引就是各个业务系统名称。
打个比方就像:图书馆里按主题划分的一个分区。比如 “计算机科学书库”、“历史书库”、“小说书库”。每个专题书库就是一个 “索引”。
3.分片
索引的数据存在分片中,分片是对索引数据的水平切分。分片可以存在不同的机器不同的位置处。(高可用)
ES 会自动管理分片的分布,确保同一个分片的主副本不会在同一个节点上(避免单点故障),并尽可能均衡分布。
分片分为:
- shard主分片
- 副本分片。
每个分片本身就是一个功能完整的、独立的 Lucene 索引实例(包含倒排索引、正排索引等所有结构)。
特别注意:索引的主分片数量在索引创建时指定,之后不能修改(除非通过 Reindex)。
4. segment 段
逻辑分区索引下面有分片,负责存储在不同的node的不同地方,分片是由多个segment构成的,segment就是最小的、不可变的 Lucene 索引单元。(不用担心再嵌套下去了,学不玩了/(ㄒoㄒ)/~~)
segment的编辑操作
只会创建新的segment,不会去改动原有的segment!!!这是因为segment中存储了大量其他基本信息(倒排索引,文件结构,元信息等),牵一发动全身。那么高并发的时候,segment必然会创建很多份,防止文件爆炸,es会定期merge segment。
5. doc values
正排索引,为了解决用户需要根据文档内容进行【聚合、排序、脚本操作等】的需要,比如根据创建时间排序,获取用户的数量等信息。
本质是个列式存储结构(类似mysql那种,一列就是一个字段)。 Doc Values 是 Lucene 在索引文档时,在磁盘上额外构建的一种正向索引结构。它以文档 ID 为行,字段为列存储数据。
解决倒排索引的痛点: 倒排索引精于快速查找 “某个词在哪些文档”,但不适合聚合、排序、脚本访问字段值。这些操作需要遍历大量文档并访问字段的原始值,使用倒排索引效率极低(需要 “随机” 打开每个文档找字段值)。
三、ES集群高可用核心
1. Master 节点选举机制 (Bully 算法原理)
ES 采用一个改进版的 Bully 算法 来选举主节点。其核心目标是:在具有 master
资格的节点中,选出一个节点来承担主节点职责。
选举的核心流程与本质:
-
触发选举 (什么时候选?):
- 集群启动: 当第一个具有
master
角色的节点启动时,它发现没有主节点存在,就会触发选举。 - 主节点故障: 当前主节点宕机或与集群失去联系(网络分区)时,其他
master
节点检测到主节点丢失(通过心跳超时),会触发新的选举。 - 显式移除主节点: 运维操作导致。
- 集群启动: 当第一个具有
-
候选人 (谁可以参选?):
- 只有配置了
node.roles
包含master
的节点才有资格参与选举。
- 只有配置了
-
核心选举步骤 (怎么选? - Bully 算法精髓):
- 步骤 1:发现主节点丢失:一个或多个
master
节点 (candidate
) 检测到主节点心跳超时,认为主节点失效。 - 步骤 2:发起选举:每个认为自己可能是新主节点的
candidate
节点(通常是所有检测到主节点丢失的master
),向当前所有已知的、具有master
资格的节点广播一个StartElectionRequest
(选举请求)。这个请求包含一个clusterStateVersion
(集群状态版本号,越高表示包含的变更越新)。 - 步骤 3:比较竞争:
- 收到
StartElectionRequest
的master
节点会比较:- 集群状态版本 (
clusterStateVersion
):拥有最新集群状态版本的master
节点具有最高优先级。这是为了避免新选出的主节点状态落后,导致数据不一致(最重要原则!)。 - 节点 ID (Node ID):如果集群状态版本相同(例如集群刚启动时都是0),则比较 节点 ID(一个唯一的字符串标识符)。节点 ID 越小,优先级越高。
- 节点启动顺序:极端情况下(相同集群状态版本和节点ID),比较节点启动顺序(很少见)。
- 集群状态版本 (
- 比较规则:先比
clusterStateVersion
(越大越好),再比节点 ID (越小越好)。
- 收到
- 步骤 4:投票 (Acknowledgment):
- 收到
StartElectionRequest
的master
节点,会判断自己是否比发起请求的节点 更适合 (more qualified
) 当主节点(根据上面的比较规则)。- 如果请求发起者比自己更适合:该节点会投票支持 (
acknowledge
) 这个发起者,并承诺在选举期间不会投票给其他人。同时会返回自己知道的最新集群状态版本给发起者。 - 如果自己比发起者更适合:会拒绝 (
reject
) 这个发起者的请求,并且可能会自己发起一个新的选举请求。
- 如果请求发起者比自己更适合:该节点会投票支持 (
- 收到
- 步骤 5:当选 & 发布集群状态:
- 发起选举的
candidate
节点需要收到 法定人数 (quorum
) 的投票支持才能当选主节点。- 法定人数 (
quorum
) 计算:discovery.zen.minimum_master_nodes
(Zen1) 或cluster.initial_master_nodes
+ 集群拓扑感知 (Zen2) 共同保证。核心是:floor(N / 2) + 1
(其中 N 是master
节点总数)。 例如,你有 3 个master
节点,法定人数是 2 (floor(3/2)+1 = 1+1=2
);有 5 个,法定人数是 3。
- 法定人数 (
- 当一个
candidate
收到超过法定人数的投票支持后,它就宣告自己成为新的主节点 (elected-as-master
)。 - 新主节点立即发布一个包含自己当选信息的新版
cluster state
给集群中的所有节点(包括master
和data
/coordinating
/ingest
节点)。其他节点接收到这个状态后,就认可这个新主节点。
- 发起选举的
- 步骤 1:发现主节点丢失:一个或多个
-
关键保障机制:
- 法定人数 (
Quorum
):- 本质:防止脑裂 (
split-brain
) 的核心机制。 - 原理:要求任何决策(包括选举结果、集群状态变更)必须获得大多数
master
节点 (quorum
) 的同意才能生效。这样就能确保在网络分区的情况下,最多只有一个分区能形成有效的集群(获得quorum
),另一个分区因为没有足够节点而无法选举或更新状态,从而避免两个主节点同时存在导致数据损坏。 - 配置:在 ES 7.0 之前,需要手动配置
discovery.zen.minimum_master_nodes
(必须设置成quorum
数)。在 ES 7.0+ 的集群引导阶段,使用cluster.initial_master_nodes
列出初始master
节点列表,ES 会自动计算和维护quorum
(推荐方式,不再需要手动设置discovery.zen.minimum_master_nodes
)。
- 本质:防止脑裂 (
- 集群状态版本 (
Cluster State Version
):确保新主节点拥有最完整的集群视图,避免数据丢失或冲突。版本号最高的节点优先当选。
- 法定人数 (
2. 和raft的区别
Raft 算法是什么?
简单说,Raft 是一个 管理复制日志(Replicated Log)的一致性算法。
它的核心目标是:让一组计算机(服务器/节点)在部分节点可能故障或网络可能出错的情况下,依然能就一系列操作(状态变更)的顺序达成一致,并安全地复制这些操作。
为什么需要这个? 想象你要在多个图书馆(分布式系统)之间同步图书库存信息。每个图书馆都要记录“新书入库”、“书籍借出”、“书籍归还”这些操作。所有图书馆的操作记录本(日志)必须一模一样且顺序一致,否则有的图书馆认为书在库,有的认为已借出,就乱套了。Raft 就是保证所有管理员(节点)的记录本高度一致的规则。
Raft 的核心思想:领导与跟随
Raft 最大的特点是 强领导(Strong Leader) 模型。每个时刻,至多只能有一个有效的领导者(Leader)。所有决策都由领导者发出,跟随者(Followers)只需接收和执行领导者的指令。这大大简化了协调过程。
Raft 中的节点角色:
-
Leader
(领导者/总管理员):- 职责: 接收客户端的所有操作请求(如“添加新书《算法导论》”);将这些操作写入自己的操作记录本(日志);强制要求所有跟随者复制这个操作记录;在确保操作被安全复制后,通知所有跟随者“可以正式执行这个操作了”(即提交该日志条目);响应客户端操作是否成功。
- 权力: 独裁者,所有写操作必须经过它。定期给所有跟随者发心跳(Heartbeat) 来维持权威。
-
Follower
(跟随者/分馆管理员):- 职责: 被动响应来自领导者的 RPC(远程调用),包括接收并复制领导者发来的操作记录(日志条目),执行领导者通知的已提交的操作(将操作实际应用到自己的状态机,比如更新本地库存表)。如果长时间没收到领导者的心跳,就“揭竿而起”变成候选人,尝试竞选新领导。
- 状态: 大部分时间节点都处于跟随者状态(一个健康的集群通常只有一个领导者,其余全是跟随者)。
-
Candidate
(候选人/竞选者):- 职责: 当跟随者怀疑领导者挂了(心跳超时),就变身候选人,发起一次选举(Election),拉票争取成为新领导者。
- 临时状态: 这个角色是临时的,只在选举期间存在。选举结束后,成功则成为新领导者,失败则变回跟随者。
Raft 如何工作?(两大核心流程)
一、领导人选举(Leader Election):(目标是选出一个唯一的领导)
-
触发: 每个跟随者内部都有一个 选举定时器(Election Timeout)(通常是 150ms-300ms 的随机值)。当这个定时器超时前都没有收到当前领导者的心跳或任何有效RPC,跟随者就认为:“领导可能挂了!”,于是它:
- 增加自己的 任期号(Term)(一个单调递增的整数,代表不同的朝代)。
- 切换到
Candidate
状态。 - 为自己投票。
- 向 其他所有节点 发送 请求投票 RPC(RequestVote RPC),请求他们投自己一票。
-
投票: 其他节点收到
RequestVote RPC
后,判断:- 任期号是否比自己的小? 是 → 拒绝投票。
- 在同一个任期是否已投过票? 是 → 拒绝投票(一人一票)。
- 候选人的日志至少和自己一样新吗?(Raft 的 日志完整性检查:比较最后一条日志条目的 Term 和 Index)否 → 拒绝投票(保证新领导的数据不能比跟随者旧)。是 → 同意投票。
- 如果同意投票,该节点会重置自己的选举定时器(避免自己也发起选举)。
-
当选:
- 候选人收到同一任期内超过半数的投票(包括自己那一票),它就赢得选举,成为该任期的新领导人。
- 新领导人立即向所有节点发送心跳(AppendEntries RPC,但不包含日志条目),宣告自己掌权,并阻止新的选举。
- 法定人数(Quorum)规则: 要求多数票(N/2 + 1) 才能当选。这确保了:
- 领导唯一性: 同一任期内不可能有两个节点都获得多数票。
- 防止脑裂: 即使网络分区,分裂成两个小群体,也只有包含多数节点的那边能选出新领导(达到多数票),另一边因节点数不足无法选出有效领导,避免了同时有两个领导发号施令。
-
选举僵局(分裂投票):
- 如果第一次选举没有任何候选人获得多数票(比如多个跟随者同时超时竞选),所有候选人超时,增加任期号,重新发起新一轮选举(随机超时时间的设计大大降低了再次冲突的概率)。
- 最终总有一个候选人能先获得多数票当选。
二、日志复制(Log Replication):(目标是安全、一致地复制操作)
- 接收请求: 客户端向领导者发送一个操作(如“Set
x=5
”)。 - 追加本地日志: 领导者将操作作为一个新的日志条目(Log Entry) 追加到自己的操作记录本末尾。该条目包含:
- 客户端的命令 (
Set x=5
) - 领导者的当前任期号 (
Term
) - 在日志中的索引位置 (
Index
)
- 客户端的命令 (
- 复制日志: 领导者通过 追加条目 RPC(AppendEntries RPC) 将此新日志条目并行发送给所有跟随者,要求他们复制。
- 跟随者响应:
- 如果跟随者成功复制该条目到本地日志(还没真正执行),则向领导者返回成功。
- 如果跟随者失败(如网络中断、日志冲突),则返回失败。领导者会尝试发送更早的日志条目(或进行冲突解决),直到跟随者追上领导者的日志。
- 领导者提交:
- 当领导者收到超过半数跟随者的成功响应后,认为该日志条目已经安全复制(Committed)。领导者将这个条目应用到自己的状态机(执行命令:把
x
设置成5
)。 - 领导者通知所有跟随者该条目已提交(在后续的
AppendEntries RPC
中包含最新的已提交索引)。
- 当领导者收到超过半数跟随者的成功响应后,认为该日志条目已经安全复制(Committed)。领导者将这个条目应用到自己的状态机(执行命令:把
- 跟随者应用: 所有跟随者收到提交通知后,也将该日志条目应用到自己的状态机(执行
Set x=5
)。 - 响应客户端: 领导者应用成功后,响应客户端操作成功。
关键保障机制:
- 日志匹配特性(Log Matching Property): Raft 保证:
- 如果两个节点在相同索引位置有日志条目,且它们的
Term
也相同,那么它们包含的命令相同。 - 如果一个日志条目在某个
Term
被提交(Committed),那么所有更高任期的领导者的日志中一定包含这个条目,且位置相同。
- 如果两个节点在相同索引位置有日志条目,且它们的
- 领导者的日志完整性(Leader Completeness): 选举时强制要求候选人日志必须比多数节点都新(或一样新)。这保证了:
- 任何已提交的日志条目,最终一定会出现在后续所有领导者的日志中(已提交的条目不会丢失)。
- 新领导者拥有所有已提交的日志条目,并能强制让其他节点复制这些条目。
- 状态机安全(State Machine Safety): 如果一个跟随者在其状态机中应用了一个给定索引的日志条目,那么任何其他节点在其状态机中应用相同索引的日志条目时,必定是同一个命令。这保证了所有节点最终看到的状态一致。
ES (Zen2) vs Raft:
- 相似点:
- 都需要法定人数(多数票) 来选举和提交操作,防止脑裂。
- 都有任期号(Term)的概念。
- 不同点:
- 主要目标不同: ES 的主节点选举(Zen1/Zen2)主要解决的是选一个管理元数据的领导。而 Raft 主要解决的是在多个副本之间安全复制操作日志并达成一致状态。ES 7.x 之后的选主机制(基于类似 Raft 的实现)融合了 Raft 的思路(如基于日志的选举投票,而不是 Zen1 简单的版本比较),使其更健壮。
- 复杂性: Raft 的核心是日志复制协议,选举只是其中的一部分(虽然很关键)。ES 的 Zen 机制相对更专注于选举本身。
- 领导角色: Raft 的领导者是强领导,负责处理所有客户端请求和日志复制。ES 的协调节点(Coordinating Node)接收请求并路由,主节点(Master)只负责集群状态管理,数据节点(Data Node)负责数据分片上的实际数据操作和搜索。
总结:
Raft 就像一个精心设计的议会民主制+强执行力的领导机制:
- 选举: 大家通过投票(要求多数票支持)选出一个唯一的领导(任期制)。
- 治理: 所有政策(操作命令)都由领导发起,写入议案记录本(日志)。
- 立法: 领导必须说服议会超过半数成员(跟随者)同意并记录(复制)该议案。
- 生效: 议案获得多数支持后,才正式生效(提交),并通知全国所有地方政府(所有节点)执行该政策(应用到状态机)。
- 容错: 领导挂了(心跳超时),立即启动新选举。只要议会(集群)超过半数成员还活着,就能选出新领导继续治理,确保国家(系统)稳定运行。
它的清晰结构(强领导、日志复制、安全性约束)使其比 Paxos 等算法更容易理解和工程实现,成为构建可靠分布式系统(如 etcd, Consul, TiKV, CockroachDB,也包括 ES 7.x+ 的选主模块)的基石。