集群架构
ES 集群的核心:水平扩展、高可用、分布式计算。本章涵盖节点角色、集群搭建、数据路由、故障转移和生产运维。
为什么需要集群
单节点面临两个根本问题:
| 问题 | 集群解决方案 |
|---|---|
| 海量数据超过单节点磁盘容量 | 索引拆分为 N 个 Shard,分布到多节点 |
| 单点故障(节点宕机导致数据不可用) | 分片数据在不同节点上副本冗余 |

节点角色
| 角色 | 配置参数 | 默认值 | 职责 |
|---|---|---|---|
| master eligible | node.master |
true |
管理集群元数据(索引创建/删除)、分片分配、节点健康监控 |
| data | node.data |
true |
数据存储、搜索、聚合、CRUD |
| ingest | node.ingest |
true |
写入前预处理(Ingest Pipeline) |
| remote_cluster_client | cluster.remote.connect |
true |
跨集群搜索 / 跨集群复制(CCR) |
| coordinating only | 以上全为 false |
— | 仅路由请求、合并和精简结果 |
| ml | node.ml |
true |
机器学习作业(ES 7.x+) |
| transform | node.transform |
true |
Transform 数据透视(ES 7.7+) |
🚨 陷阱:ES 默认每个节点同时承担所有角色。在超越 3 节点的集群中,角色不分离会导致 GC 压力、搜索延迟抖动等问题。
角色分离最佳实践

| 集群规模 | 建议 |
|---|---|
| 3 节点(最小生产) | 3 个 master eligible + data(混合),设置 discovery.seed_hosts |
| 5-9 节点 | 3 个专用 master(不存数据),其余为 data 或 data+ingest |
| 10+ 节点(大型集群) | 独立 master(3 个)、data、ingest、coordinating 层 |
| 50+ 节点 | 加上专用 ml 节点、cold/frozen 数据层 |
专用节点配置示例
# 专用 master 节点
node.master: true
node.data: false
node.ingest: false
# 专用 data 节点
node.master: false
node.data: true
node.ingest: false
# 专用 coordinating 节点
node.master: false
node.data: false
node.ingest: false集群搭建
Docker 集群示例
# docker-compose-cluster.yml
version: '3.8'
services:
es01:
image: docker.elastic.co/elasticsearch/elasticsearch:9.3.4
container_name: es01
environment:
- node.name=es01
- cluster.name=my-cluster
- network.host=0.0.0.0
- discovery.seed_hosts=es02,es03
- cluster.initial_master_nodes=es01,es02,es03
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
- "9200:9200"
networks:
- es-net
es02:
image: docker.elastic.co/elasticsearch/elasticsearch:9.3.4
container_name: es02
environment:
- node.name=es02
- cluster.name=my-cluster
- network.host=0.0.0.0
- discovery.seed_hosts=es01,es03
- cluster.initial_master_nodes=es01,es02,es03
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
networks:
- es-net
es03:
image: docker.elastic.co/elasticsearch/elasticsearch:9.3.4
container_name: es03
environment:
- node.name=es03
- cluster.name=my-cluster
- network.host=0.0.0.0
- discovery.seed_hosts=es01,es02
- cluster.initial_master_nodes=es01,es02,es03
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
networks:
- es-net
networks:
es-net:关键配置
| 配置项 | 说明 | 示例 |
|---|---|---|
cluster.name |
集群名称,同名自动组网 | my-cluster |
node.name |
节点名(集群内唯一) | es01 |
discovery.seed_hosts |
种子节点列表(ES 7.0+) | ["es01", "es02", "es03"] |
cluster.initial_master_nodes |
首次启动时的 master 候选节点 | ["es01"] |
network.host |
绑定地址 | 0.0.0.0 |
http.port / transport.port |
HTTP / 内部通信端口 | 9200 / 9300 |
🔬 深入原理:
cluster.initial_master_nodes仅在集群首次启动时需要(引导投票)。集群稳定运行后,新加入的节点通过discovery.seed_hosts自动发现并加入,不再需要此参数。
脑裂(Split-Brain)
当 master 节点与部分节点网络隔离时,剩余节点可能选举出新 master,导致两个 master 同时存在。

解决原理:法定人数
选票数需超过 N / 2 + 1(N 为 eligible 节点数):
| eligible 节点数 | 法定人数 | 可容忍故障 |
|---|---|---|
| 3 | 2 | 1 节点宕机 |
| 5 | 3 | 2 节点宕机 |
| 2 | 2 | 0(2 节点集群不稳定!) |
💡 最佳实践:master eligible 节点数取奇数(3 或 5)。偶数(如 2)会导致任何一个节点宕机集群就不可用。
💡 最佳实践:ES 7.0+ 使用新的基于 Raft 的协调层,已内置法定人数机制,一般不会发生脑裂。但 master 节点数选奇数仍然是铁律。
分布式存储
路由算法
文档写入时,Coordinating Node 决定发往哪个分片:
shard_num = hash(_routing) % num_primary_shards| 参数 | 说明 |
|---|---|
_routing |
路由键(默认 _id) |
num_primary_shards |
主分片数(创建后不可变) |
🚨 陷阱:路由算法与主分片数绑定 → 分片数量创建后不可修改。规划不足时只能 Reindex。

自定义路由
# 写入时指定路由
POST /orders/_doc/1?routing=customer_123
{ "customer_id": "customer_123", "amount": 500 }
# 查询时必须提供相同路由值
GET /orders/_search?routing=customer_123
{ "query": { "match_all": {} } }💡 最佳实践:按用户 ID 路由 → 同一用户的所有文档在同一分片 → 查询同一用户时只需查询一个分片,性能成倍提升。
文档定位
数据写入流程:
1. Coordinating Node 接收请求
2. 计算路由目标分片
3. 转发到主分片所在节点
4. 主分片写入 → 同步到副本分片
5. 所有副本确认后返回成功💡 最佳实践:请求中加入
"explain": true可以看到数据来自哪个分片,便于调试路由问题。
分布式查询
查询流程:
┌───────────────────────────────────────┐
│ Scatter Phase(分散阶段) │
│ Coordinating Node 把请求广播到 │
│ 每个相关分片(主分片或副本均可读) │
├───────────────────────────────────────┤
│ Gather Phase(聚集阶段) │
│ 协调节点汇总各 Data Node 结果 │
│ 排序 / 分页 / 去重 → 返回给客户端 │
└───────────────────────────────────────┘🔬 深入原理:深度分页的代价就在 Gather 阶段——每个分片返回
from + size条,协调节点对所有结果排序并丢弃前 N 条。分片越多、分页越深、开销越大。
故障转移

流程
| 步骤 | 说明 |
|---|---|
| 1. 监控 | Master 节点持续监控所有节点心跳 |
| 2. 检测 | 节点超时无响应 → 标记为宕机 |
| 3. 选举 | 如有必要,eligible 节点选举新 master |
| 4. 迁移 | 将宕机节点的分片重新分配到健康节点 |
| 5. 恢复 | 新主分片从剩余副本同步数据 |
分片分配感知(Shard Allocation Awareness)
在生产环境中,确保主分片和副本分布在不同物理机架/可用区:
# elasticsearch.yml
cluster.routing.allocation.awareness.attributes: rack_id
node.attr.rack_id: rack1 # 每台机器设置自己的 rack_id💡 最佳实践:
- Rack Awareness:主分片和副本永远不会分配到
rack_id相同的节点- Forced Awareness:
cluster.routing.allocation.awareness.force.rack_id.values: rack1, rack2强制分片只在指定机架之间分配- 云环境用可用区(
zone_id)代替物理机架
延迟感知(Shard Allocation Filtering)
# 将索引迁出特定节点(如准备下线维护)
PUT /_cluster/settings
{
"transient": {
"cluster.routing.allocation.exclude._name": "es-old-node"
}
}快照与恢复(Snapshot & Restore)
注册快照仓库
# 以文件系统为例
PUT /_snapshot/my_backup
{
"type": "fs",
"settings": {
"location": "/mount/backups/my_backup",
"compress": true
}
}| 仓库类型 | 说明 |
|---|---|
fs |
共享文件系统(NFS) |
s3 |
Amazon S3 |
gcs |
Google Cloud Storage |
azure |
Azure Blob Storage |
hdfs |
Hadoop HDFS |
创建快照
# 全集群快照
PUT /_snapshot/my_backup/snapshot_1?wait_for_completion=true
{
"indices": "user,order*",
"ignore_unavailable": true,
"include_global_state": true
}
# 查看快照状态
GET /_snapshot/my_backup/snapshot_1/_status
# 查看所有快照
GET /_snapshot/my_backup/_all恢复快照
# 恢复全部
POST /_snapshot/my_backup/snapshot_1/_restore
# 选择性恢复
POST /_snapshot/my_backup/snapshot_1/_restore
{
"indices": "user",
"rename_pattern": "user",
"rename_replacement": "user_restored",
"include_global_state": false
}💡 最佳实践:
- 快照是增量式的——只有变化的部分会传输
- 每天一次 Snapshot + 保留最近 30 天(SLM 自动生命周期管理)
- 恢复时监控
GET /_recovery查看进度
SLM(Snapshot Lifecycle Management)
PUT /_slm/policy/daily-backup
{
"name": "<daily-{now/d}>",
"schedule": "0 30 1 * * ?",
"repository": "my_backup",
"config": {
"indices": ["*", "-.monitoring-*"],
"ignore_unavailable": true
},
"retention": {
"expire_after": "30d",
"min_count": 5,
"max_count": 50
}
}Hot-Warm-Cold 分层架构
对于时序数据(日志/Metrics),按热度分层存储:
| 层 | 用途 | 硬件 | 分片策略 |
|---|---|---|---|
| Hot | 近 1-3 天的数据,高写入高查询 | SSD / 高性能 | 多分片多副本 |
| Warm | 近 1-4 周数据,偶尔查询 | HDD / 中等 | 合理分片,1 副本 |
| Cold | 更早的数据,极少查询 | HDD / 低成本 | shrink 后低分片 |
| Frozen | 归档数据,几乎不查 | 对象存储 | 搜索时临时加载 |
# 将索引从 hot 迁移到 warm
PUT /logs-2025.07.01/_settings
{
"index.routing.allocation.require.data_tier": "data_warm"
}💡 最佳实践:配合 ILM(Index Lifecycle Management) 自动执行 Rollover → 迁移 → 删除,详见 08-最佳实践与陷阱。
滚动升级(Rolling Upgrade)
升级集群而不停机(ES 7.x+ 兼容小版本之间的滚动升级):
| 步骤 | 操作 |
|---|---|
| 1 | 暂停分片分配:PUT /_cluster/settings { "transient": { "cluster.routing.allocation.enable": "primaries" } } |
| 2 | 停止一个节点 → 升级 ES 版本 → 启动节点 → 等待加入集群 |
| 3 | 等待 GET /_cluster/health 状态变绿 |
| 4 | 重新启用分片分配 |
| 5 | 对每个节点重复步骤 1-4 |
🚨 陷阱:大版本升级(如 7.x → 8.x)不支持滚动升级,需要完全重启集群或使用 Reindex + 切换方案。
跨集群复制(CCR)
ES 6.7+ 的商用功能(Basic License 可用),实现异地容灾。
┌─────────────────┐
│ Leader Cluster │ → 同步复制 → ┌─────────────────┐
│ (Primary DC) │ │ Follower Cluster │
│ │ │ (DR Site) │
└─────────────────┘ └─────────────────┘# 在 Follower 集群执行
PUT /logs/_ccr/follow
{
"remote_cluster": "leader_cluster",
"leader_index": "logs"
}集群健康诊断
# 集群状态:green / yellow / red
GET /_cluster/health?level=indices
# 分片分配解释(排查 unassigned shards)
GET /_cluster/allocation/explain
# 节点统计
GET /_nodes/stats
# 待处理的任务
GET /_cluster/pending_tasks
# 热点线程
GET /_nodes/hot_threads| 状态 | 含义 | 处理 |
|---|---|---|
| green | 所有主分片和副本都已分配 | ✅ 正常 |
| yellow | 所有主分片已分配,但有副本未分配 | ⚠️ 可读可写,但存在单点风险 |
| red | 有主分片未分配 | 🚨 部分数据不可用 |
常见陷阱
| 陷阱 | 说明 |
|---|---|
| 🚨 分片数创建后不可改 | 路由算法绑定 num_primary_shards |
| 🚨 默认节点角色混杂 | 生产环境应分离 master / data / coordinating |
| 🚨 master eligible 偶数 | 2 节点集群宕机一台就不可用 |
| 🚨 副本与主分片同节点 | 虽然 ES 自动避免,但单节点集群下副本始终 unassigned |
| 🚨 集群 yellow 状态忽略 | 副本未分配意味着数据无冗余,此时再宕机会真正丢数据 |
| 🚨 滚动升级跳过中间大版本 | 7.x → 9.x 不支持直接滚动升级 |
| 🚨 忘记配置 Rack Awareness | 所有副本可能在同一物理机架/可用区 |
| 🚨 快照未定期执行 | 集群故障时无恢复手段 |