跳到主要内容

集群架构

问题

Elasticsearch 的集群架构是什么?分片和副本如何工作?写入和搜索的流程是什么?

答案

集群基本概念

ES 集群由多个 节点(Node) 组成,数据分布在多个 分片(Shard) 上。

分片(Shard)

主分片(Primary Shard)

  • 每个 Index 在创建时指定 主分片数,创建后 不可更改(7.0+ 默认 1 个)
  • 每个主分片是一个独立的 Lucene 索引
  • 文档通过路由算法分配到特定分片

副本分片(Replica Shard)

  • 主分片的精确副本,提供 高可用读扩展
  • 副本数可以随时调整
  • 副本分片不会与对应的主分片在同一个节点上

分片路由

文档被分配到哪个分片由路由公式决定:

shard=hash(routing)modnumber_of_primary_shards\text{shard} = \text{hash}(\text{routing}) \mod \text{number\_of\_primary\_shards}

默认 routing = _id。这也是主分片数不可更改的原因 —— 改了分片数,所有文档的路由都会变。

// 自定义路由(让同一用户的数据在同一分片上)
PUT /orders/_doc/1?routing=user123
{
"userId": "user123",
"amount": 100
}

分片数如何选择

建议说明
单个分片大小建议 10-50 GB
分片总数每个节点不超过 20 个分片/GB 堆内存
主分片数按预期数据量 / 50GB 估算
副本数生产环境至少 1 份副本
分片数过多的危害

每个分片维护独立的数据结构,消耗文件描述符、内存和 CPU。分片数过多(Over-sharding)会导致:

  • Master 节点管理负担重
  • 搜索时需要汇总更多分片结果
  • 小分片的 Segment Merge 效率低

反模式:为了未来扩展提前创建大量分片。正确做法是根据当前数据量设置合理分片数,需要时通过 _split_reindex 扩展。

节点角色

角色配置职责
Master Nodenode.roles: [master]管理集群状态、分片分配、索引创建
Data Nodenode.roles: [data]存储数据,执行 CRUD 和搜索
Coordinating Nodenode.roles: []接收请求、路由、汇总结果
Ingest Nodenode.roles: [ingest]数据预处理(Pipeline)
ML Nodenode.roles: [ml]机器学习任务

生产环境节点规划

节点类型建议数量硬件要求
Master3(奇数)低 CPU/内存即可
Data按数据量高磁盘、大内存
Coordinating2+大内存(汇总结果)

写入流程

关键参数

参数默认值说明
wait_for_active_shards1(仅主分片)写入前需要多少活跃分片
refreshfalse写入后是否立即 refresh
timeout1m等待分片超时时间

搜索流程

ES 的搜索分为两个阶段:Query PhaseFetch Phase

为什么分两个阶段

  • Query Phase:每个分片只返回轻量的 (doc_id, score) 对,减少网络传输
  • Fetch Phase:只获取最终需要的 Top N 个文档的完整内容

如果分片数为 S,请求 Top N,则 Query Phase 每个分片返回 N 个结果,协调节点需要处理 S × N 个结果进行全局排序。这就是 深分页性能差 的根本原因。

集群状态

状态颜色含义
Green🟢所有主分片和副本分片都正常
Yellow🟡所有主分片正常,部分副本分片未分配
Red🔴部分主分片不可用,数据可能丢失
// 查看集群健康状态
GET _cluster/health
// {
// "status": "green",
// "number_of_nodes": 3,
// "active_primary_shards": 5,
// "active_shards": 10,
// "unassigned_shards": 0
// }

// 查看分片分配
GET _cat/shards?v

// 查看节点信息
GET _cat/nodes?v&h=name,node.role,heap.percent,disk.used_percent

脑裂问题与预防

脑裂(Split Brain):网络分区导致集群分裂为两个独立的子集群,各自选出自己的 Master,导致数据不一致。

预防机制(7.0+ 自动处理):

  • ES 7.0+ 使用新的选主算法,自动设置法定人数(quorum),无需手动配置
  • 7.0 之前需要设置 discovery.zen.minimum_master_nodes >= (master节点数 / 2) + 1

常用运维操作

// 1. 调整副本数
PUT /my_index/_settings
{ "number_of_replicas": 2 }

// 2. 禁用分片分配(滚动重启前)
PUT _cluster/settings
{ "transient": { "cluster.routing.allocation.enable": "none" } }

// 3. 手动 flush
POST /my_index/_flush

// 4. 强制 Segment Merge(合并为 1 个 Segment)
POST /my_index/_forcemerge?max_num_segments=1

// 5. 查看大分片
GET _cat/shards?v&s=store:desc

// 6. 索引别名管理
POST /_aliases
{
"actions": [
{ "add": { "index": "products_v2", "alias": "products" } }
]
}

常见面试问题

Q1: ES 的分片数为什么创建后不能修改?

答案

因为文档到分片的路由依赖公式 hash(routing) % number_of_primary_shards。如果修改分片数,所有文档的路由都会变化,导致在错误的分片上查不到数据。

如果需要更多分片,可以:

  1. _split API:将每个分片分裂为 2/4/8 个(只能倍增)
  2. _reindex:创建新 Index(新分片数)+ 迁移数据
  3. _shrink API:减少分片数

Q2: ES 写入一条文档的完整流程?

答案

  1. 客户端发送写请求到任意节点(协调节点)
  2. 协调节点通过路由公式确定目标主分片
  3. 请求转发到主分片所在节点
  4. 主分片写入内存 Buffer + Translog(此时不可搜索)
  5. 主分片并行转发到所有副本分片
  6. 副本分片写入 Buffer + Translog
  7. 所有副本确认后,主分片返回成功给协调节点
  8. 协调节点返回成功给客户端
  9. 每 1 秒 refresh:Buffer → 新 Segment(可搜索)
  10. 每 30 分钟 flush:Segment 持久化到磁盘,清空 Translog

Q3: 如何解决 ES 集群的 Yellow 状态?

答案

Yellow 表示副本分片未分配。常见原因和解决方法:

原因解决方案
单节点集群添加节点或设副本数为 0
节点磁盘空间不足扩容磁盘或删除旧数据
分片分配规则限制检查 cluster.routing.allocation 设置
节点刚加入等待自动 rebalance
// 查看未分配分片的原因
GET _cluster/allocation/explain

Q4: 如何做 ES 集群的滚动重启?

答案

  1. 禁用分片分配cluster.routing.allocation.enable: "none"
  2. 停止节点:停止要重启的节点
  3. 重启节点:升级/配置变更后启动
  4. 等待节点加入
  5. 恢复分片分配cluster.routing.allocation.enable: "all"
  6. 等待 Green:确认所有分片分配完成
  7. 重复:对剩余节点重复上述步骤

Q5: 如何规划 ES 集群的容量?

答案

  1. 估算数据量:每日新增数据量 × 保留天数
  2. 计算存储:原始数据量 × 1.1(索引开销约10%)× (1 + 副本数)
  3. 分片规划:总存储 / 50GB = 主分片数
  4. 节点规划
    • 内存:JVM Heap 不超过 32GB,剩余留给 OS Cache
    • 磁盘:预留 30% 空间给 Merge 等操作
    • 每个节点 20-30 个分片为宜
  5. Master 节点:3 个专用 Master(不存数据)

相关链接