集群架构
问题
Elasticsearch 的集群架构是什么?分片和副本如何工作?写入和搜索的流程是什么?
答案
集群基本概念
ES 集群由多个 节点(Node) 组成,数据分布在多个 分片(Shard) 上。
分片(Shard)
主分片(Primary Shard)
- 每个 Index 在创建时指定 主分片数,创建后 不可更改(7.0+ 默认 1 个)
- 每个主分片是一个独立的 Lucene 索引
- 文档通过路由算法分配到特定分片
副本分片(Replica Shard)
- 主分片的精确副本,提供 高可用 和 读扩展
- 副本数可以随时调整
- 副本分片不会与对应的主分片在同一个节点上
分片路由
文档被分配到哪个分片由路由公式决定:
默认 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 Node | node.roles: [master] | 管理集群状态、分片分配、索引创建 |
| Data Node | node.roles: [data] | 存储数据,执行 CRUD 和搜索 |
| Coordinating Node | node.roles: [] | 接收请求、路由、汇总结果 |
| Ingest Node | node.roles: [ingest] | 数据预处理(Pipeline) |
| ML Node | node.roles: [ml] | 机器学习任务 |
生产环境节点规划
| 节点类型 | 建议数量 | 硬件要求 |
|---|---|---|
| Master | 3(奇数) | 低 CPU/内存即可 |
| Data | 按数据量 | 高磁盘、大内存 |
| Coordinating | 2+ | 大内存(汇总结果) |
写入流程
关键参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
wait_for_active_shards | 1(仅主分片) | 写入前需要多少活跃分片 |
refresh | false | 写入后是否立即 refresh |
timeout | 1m | 等待分片超时时间 |
搜索流程
ES 的搜索分为两个阶段:Query Phase 和 Fetch 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。如果修改分片数,所有文档的路由都会变化,导致在错误的分片上查不到数据。
如果需要更多分片,可以:
_splitAPI:将每个分片分裂为 2/4/8 个(只能倍增)_reindex:创建新 Index(新分片数)+ 迁移数据_shrinkAPI:减少分片数
Q2: ES 写入一条文档的完整流程?
答案:
- 客户端发送写请求到任意节点(协调节点)
- 协调节点通过路由公式确定目标主分片
- 请求转发到主分片所在节点
- 主分片写入内存 Buffer + Translog(此时不可搜索)
- 主分片并行转发到所有副本分片
- 副本分片写入 Buffer + Translog
- 所有副本确认后,主分片返回成功给协调节点
- 协调节点返回成功给客户端
- 每 1 秒 refresh:Buffer → 新 Segment(可搜索)
- 每 30 分钟 flush:Segment 持久化到磁盘,清空 Translog
Q3: 如何解决 ES 集群的 Yellow 状态?
答案:
Yellow 表示副本分片未分配。常见原因和解决方法:
| 原因 | 解决方案 |
|---|---|
| 单节点集群 | 添加节点或设副本数为 0 |
| 节点磁盘空间不足 | 扩容磁盘或删除旧数据 |
| 分片分配规则限制 | 检查 cluster.routing.allocation 设置 |
| 节点刚加入 | 等待自动 rebalance |
// 查看未分配分片的原因
GET _cluster/allocation/explain
Q4: 如何做 ES 集群的滚动重启?
答案:
- 禁用分片分配:
cluster.routing.allocation.enable: "none" - 停止节点:停止要重启的节点
- 重启节点:升级/配置变更后启动
- 等待节点加入
- 恢复分片分配:
cluster.routing.allocation.enable: "all" - 等待 Green:确认所有分片分配完成
- 重复:对剩余节点重复上述步骤
Q5: 如何规划 ES 集群的容量?
答案:
- 估算数据量:每日新增数据量 × 保留天数
- 计算存储:原始数据量 × 1.1(索引开销约10%)× (1 + 副本数)
- 分片规划:总存储 / 50GB = 主分片数
- 节点规划:
- 内存:JVM Heap 不超过 32GB,剩余留给 OS Cache
- 磁盘:预留 30% 空间给 Merge 等操作
- 每个节点 20-30 个分片为宜
- Master 节点:3 个专用 Master(不存数据)