文章目录
-
- 前言
- [一、 分布式架构全景图](#一、 分布式架构全景图)
-
- [1.1 核心组件思维导图](#1.1 核心组件思维导图)
- [1.2 主分片与副本分片](#1.2 主分片与副本分片)
- [二、 路由算法:文档归属的数学原理](#二、 路由算法:文档归属的数学原理)
-
- [2.1 路由公式](#2.1 路由公式)
- [2.2 路由流程图解](#2.2 路由流程图解)
- [三、 核心问题:为什么主分片数创建后不能修改?](#三、 核心问题:为什么主分片数创建后不能修改?)
-
- [3.1 场景模拟](#3.1 场景模拟)
- [3.2 可视化对比](#3.2 可视化对比)
- [四、 分布式交互过程:协同节点 (Coordinating Node)](#四、 分布式交互过程:协同节点 (Coordinating Node))
-
- [4.1 写操作时序图](#4.1 写操作时序图)
- [4.2 读操作时序图](#4.2 读操作时序图)
- [五、 应对数据增长的扩展方案](#五、 应对数据增长的扩展方案)
前言
Elasticsearch 之所以能处理 PB 级数据并提供近实时的搜索能力,其核心在于其分布式架构设计。对于开发者而言,理解数据如何在集群中分布、如何被路由到特定的节点,是优化查询性能、规划集群容量的基石。
本文将聚焦于 Elasticsearch 最核心的两个概念:分片 (Sharding) 与 路由 (Routing),揭开"文档去哪儿了"的谜题。
一、 分布式架构全景图
在深入算法之前,我们需要建立宏观的认知。Elasticsearch 将数据分散存储在多个"分片"中,这些分片分布在集群的不同节点上。
1.1 核心组件思维导图
Elasticsearch
分布式架构
索引 Index
逻辑命名空间
指向一个或多个分片
分片 Shard
主分片 Primary Shard
负责写入和读取
数量创建时固定
副本分片 Replica Shard
负责读取 高可用
数量可动态调整
节点 Node
Master Node
Data Node
Coordinating Node
1.2 主分片与副本分片
- 主分片 (Primary Shard):数据的"正本"。文档首先被索引到主分片,随后同步到副本。
- 副本分片 (Replica Shard) :主分片的拷贝。它有两个作用:
- 高可用:当主分片所在节点宕机,副本会升级为主分片。
- 吞吐量:搜索请求可以在副本上执行,分担读取压力。
二、 路由算法:文档归属的数学原理
当你索引一个文档时,Elasticsearch 如何知道这个文档应该存储在哪个分片上?这并非随机分配,而是遵循一个确定的公式。
2.1 路由公式
java
shard_num = hash(routing) % number_of_primary_shards
routing:路由参数。默认情况下,使用文档的_id,也可以自定义。number_of_primary_shards:索引创建时指定的主分片数量。hash:一个高效的哈希算法(Murmur3)。
2.2 路由流程图解
下图展示了一个文档写入时的路由计算过程:
默认情况
自定义
Hash('123') = 50
主分片数 = 5
写入文档 Doc_ID: 123
提取 Routing 值
Routing = '123'
Routing = 'User_A'
计算 Hash 值
取模运算
50 % 5 = 0
存储于 Shard 0
三、 核心问题:为什么主分片数创建后不能修改?
这是面试中最高频的问题之一,也是理解 ES 架构的关键。
3.1 场景模拟
假设我们创建了一个索引 users,设置主分片数为 3 (P0, P1, P2)。
我们存入一个 ID 为 8 的文档。假设 hash(8) = 8。
- 当前计算 : 8 % 3 = 2 8 \% 3 = 2 8%3=2。文档存储在 P2。
现在,假设我们将主分片数修改为 4。
- 检索计算 :当我们再次查询 ID 为
8的文档时,系统计算路由: 8 % 4 = 0 8 \% 4 = 0 8%4=0。 - 结果 :系统会去 P0 寻找该文档,但文档实际躺在 P2 中。结果是:文档找不到了。
3.2 可视化对比
场景 B: 若扩容至 4
Doc ID: 8
Hash: 8
8 % 4 = 0
去 Shard 0 寻找
❌ 404 Not Found
场景 A: 主分片数 = 3
Doc ID: 8
Hash: 8
8 % 3 = 2
存入 Shard 2
结论:路由公式强依赖于主分片数量。如果数量变化,之前所有数据的映射关系都会失效。因此,Elasticsearch 限制了主分片数一旦创建不可更改(除非重建索引)。
四、 分布式交互过程:协同节点 (Coordinating Node)
了解了路由,我们来看看一个真实的请求如何在节点间流转。
4.1 写操作时序图
当客户端发送写请求到集群的任意一个节点(该节点即变为协同节点),流程如下:
Node 3 (Replica R0) Node 2 (Primary P0) Node 1 (Coordinating) Client Node 3 (Replica R0) Node 2 (Primary P0) Node 1 (Coordinating) Client 计算路由: Hash(1) % N = 0 确定 P0 在 Node 2 par [并行复制] PUT /index/_doc/1 转发请求到 Primary Shard 写入本地 Lucene 索引 同步到 Replica Shard 确认写入成功 确认写入成功 200 OK
一致性 (Consistency): 默认情况下,主分片会等待规定数量的副本分片(ISR 列表内)同步成功后,才向客户端返回成功。这保证了数据的强一致性或最终一致性(取决于配置)。
Translog (事务日志): 为了防止节点宕机导致内存数据丢失,ES 采用 Write Ahead Log 机制,先写 Log 再落盘。
4.2 读操作时序图
读操作支持负载均衡。协同节点可以根据负载情况,选择主分片或副本分片进行读取。
Node 3 (Replica R0) Node 2 (Primary P0) Node 1 (Coordinating) Client Node 3 (Replica R0) Node 2 (Primary P0) Node 1 (Coordinating) Client 计算路由确定 Shard 0 发现 P0(Node2) 和 R0(Node3) 都有数据 负载均衡策略 (Round-robin) GET /index/_doc/1 转发请求到 Replica R0 返回文档内容 返回结果
五、 应对数据增长的扩展方案
既然主分片数不能改,如果数据量暴增,单个分片过大(超过 50GB 建议值)怎么办?
-
Reindex (重建索引) :
创建一个新的、分片数更多的索引,使用
_reindexAPI 将旧数据迁移过去。jsonPOST _reindex { "source": { "index": "old_index" }, "dest": { "index": "new_expanded_index" } } -
Split API (仅限只读索引) :
ES 提供了一个
_splitAPI,可以在不移动数据的情况下将分片裂变(例如 5 变 10),但前提是目标分片数必须是源分片数的倍数,且索引必须先设为只读。 -
Rollover (滚动索引) :
对于日志类时序数据,不要使用单一的大索引。使用 Rollover API,当索引达到一定大小或时间后,自动创建新索引。这是处理海量数据的最佳实践。
Elasticsearch 的分布式魅力在于其看似简单的路由公式背后,隐藏着精妙的一致性设计。
- 记住公式 :
shard = hash(routing) % primary_shards。 - 理解限制:因为取模运算的特性,主分片数一经定义不可修改。
- 掌握策略:合理规划分片数量,利用 Rollover 和 Reindex 应对数据增长,利用自定义 Routing 优化特定场景的查询性能。