集群是高可用的基石。本章深入拆解 ElasticSearch 集群的内部机制——从节点角色分工到选举算法,从分片调度到故障自愈,帮你看清集群运转的全貌。
一、集群节点角色详解
一个 ElasticSearch 节点从启动那一刻起,就身兼数职。理解每种角色的职责边界,是驾驭集群的第一步。
1.1 四种核心角色概览
┌─────────────────────────────────────────────────────────┐
│ ElasticSearch 集群 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Master │ │ Data │ │Coordinating│ │ Ingest │ │
│ │ Node │ │ Node │ │ Node │ │ Node │ │
│ │ │ │ │ │ │ │ │ │
│ │·集群管理 │ │·数据存储 │ │·请求路由 │ │·数据预处理│ │
│ │·索引创建 │ │·文档CRUD │ │·结果合并 │ │·Pipeline │ │
│ │·分片分配 │ │·搜索聚合 │ │·负载分发 │ │·格式转换 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────┘
1.2 主节点 (Master Node) —— 集群的大脑
主节点不处理文档的增删改查,它的全部精力都放在集群级别的管理工作上。
核心职责:
| 集群状态管理 | 维护最新的 Cluster State,包含所有索引的 mapping、setting、分片路由表 |
| 索引生命周期 | 创建/删除索引,打开/关闭索引 |
| 分片调度 | 决定哪些分片分配到哪些节点,触发分片迁移 |
| 节点管理 | 监听节点加入/离开,更新集群拓扑 |
| 集群级配置 | 管理索引模板、ILM 策略、Pipeline 定义等 |
配置方式:
# elasticsearch.yml
node.roles: [ master ]
# 或在新版本中
node.roles: [ master ]
# 纯主节点不存数据,不预处理文档
node.data: false
node.ingest: false
关键认知: 主节点虽然叫"主",但它并不处理任何搜索或写入请求。它是纯粹的"管理者",把计算和存储任务完全委托给其他节点。一个常见的误解是以为主节点承载了搜索流量——实际上,即使主节点宕机,只要数据节点和副本还在,搜索照常进行。
主节点的资源需求:
主节点对 CPU 和内存的要求远低于数据节点,但对网络延迟极度敏感。生产环境建议使用中等规格实例(如 4C8G),并保证主节点之间网络延迟 < 1ms。
1.3 数据节点 (Data Node) —— 数据的仓库
数据节点是集群的"苦力",承担所有数据相关的重型操作。
核心职责:
- 存储分片数据:每个数据节点持有若干主分片和副本分片
- 执行数据操作:文档的索引、更新、删除均在数据节点完成
- 执行搜索与聚合:在本地分片上执行查询、聚合,返回中间结果
- 分片恢复:从对端副本恢复数据,或从快照还原
配置方式:
# elasticsearch.yml
node.roles: [ data ]
# 或包含多种角色
node.roles: [ data, ingest ]
数据节点的分层策略(Hot-Warm-Cold 架构):
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Hot 节点 │───▶│ Warm 节点 │───▶│ Cold 节点 │
│ │ │ │ │ │
│ SSD / 高IOPS │ │ HDD / 中等IO │ │ 归档存储 │
│ 近7天数据 │ │ 7-30天数据 │ │ 30天+ 数据 │
│ 频繁读写 │ │ 偶尔查询 │ │ 极少访问 │
└──────────────┘ └──────────────┘ └──────────────┘
# Hot 节点配置
node.attr.temperature: hot
node.roles: [ data_hot ]
# Warm 节点配置
node.attr.temperature: warm
node.roles: [ data_warm ]
# Cold 节点配置
node.attr.temperature: cold
node.roles: [ data_cold ]
关键认知: 数据节点的故障影响范围最大。一个数据节点宕机,其上所有主分片都会失去写入能力(直到副本提升为主),而其上的副本分片丢失会降低集群的冗余度。因此数据节点的副本策略至关重要。
1.4 协调节点 (Coordinating Node) —— 流量的调度员
协调节点是客户端请求的入口和汇聚点,它是一个"聪明的代理"。
工作流程(以搜索请求为例):
客户端
│
▼
┌─────────────────┐
│ 协调节点 │ ① 接收请求
│ (Coordinating) │ ② 解析目标索引/分片
└────────┬────────┘ ③ 将请求广播到相关数据节点
│ ④ 收集各节点返回的结果
┌────┼────┐ ⑤ 合并/排序/聚合
▼ ▼ ▼ ⑥ 返回最终结果给客户端
┌──┐ ┌──┐ ┌──┐
│D1│ │D2│ │D3│ 数据节点
└──┘ └──┘ └──┘
两阶段查询机制:
| Query Phase | 协调节点向所有相关分片发送查询 | 每个分片返回 doc_id + sort_key |
| Fetch Phase | 协调节点根据排序结果选取 top N | 向对应分片请求完整文档内容 |
配置方式:
# 纯协调节点 — 不存数据、不当 master、不做预处理
node.roles: [ ]
# 等同于
node.master: false
node.data: false
node.ingest: false
关键认知: 协调节点是内存和 CPU 密集型的。它在合并大量分片返回结果时需要充足的堆内存。在高并发场景下,部署专属协调节点可以从数据节点上剥离路由和合并的开销,让数据节点专注于 I/O。
1.5 预处理节点 (Ingest Node) —— 数据的流水线
预处理节点在文档被索引之前对原始数据进行加工。
典型场景:
// 原始日志(非结构化)
{"message": "192.168.1.1 – – [06/Jun/2026:10:15:32 +0800] \\"GET /api/v1/users HTTP/1.1\\" 200 1234"}
// 经过 Ingest Pipeline 处理后
{
"message": "…",
"client_ip": "192.168.1.1",
"timestamp": "2026-06-06T10:15:32+08:00",
"method": "GET",
"path": "/api/v1/users",
"status": 200,
"bytes": 1234
}
核心 Processor 类型:
| set | 设置字段值 | { "set": { "field": "env", "value": "prod" } } |
| grok | 正则解析非结构化文本 | { "grok": { "field": "message", "patterns": ["%{COMBINEDAPACHELOG}"] } } |
| geoip | IP 地理位置解析 | { "geoip": { "field": "client_ip" } } |
| date | 日期格式转换 | { "date": { "field": "timestamp", "formats": ["dd/MM/yyyy"] } } |
| remove | 删除字段 | { "remove": { "field": "temp_field" } } |
| script | 自定义 Painless 脚本 | 复杂逻辑处理 |
| pipeline | 调用另一个 Pipeline | 模块化组合 |
Pipeline 定义示例:
PUT _ingest/pipeline/nginx_log_pipeline
{
"description": "Parse nginx access logs",
"processors": [
{
"grok": {
"field": "message",
"patterns": ["%{COMBINEDAPACHELOG}"]
}
},
{
"date": {
"field": "timestamp",
"formats": ["dd/MMM/yyyy:HH:mm:ss Z"]
}
},
{
"geoip": {
"field": "clientip"
}
},
{
"remove": {
"field": "message"
}
}
]
}
故障转移机制:
ES 7.9+ 引入了 index.default_pipeline 和 index.final_pipeline,支持索引级别的默认处理管道。同时,预处理失败可以通过 on_failure 子句优雅处理:
{
"grok": {
"field": "message",
"patterns": ["%{COMBINEDAPACHELOG}"]
},
"on_failure": [
{
"set": {
"field": "parse_error",
"value": "grok parse failed: {{ _ingest.on_failure_message }}"
}
}
]
}
关键认知: 预处理节点消耗大量 CPU。如果预处理逻辑过重(如复杂的 Grok 解析或 Script 处理),建议在写入链路的前端(如 Logstash 或 Fluentd)完成预处理,而不是把压力放到 ES 集群内部。
1.6 远程集群节点 (Remote Cluster Node) —— 跨集群的桥梁
ES 6.7+ 支持 Cross-Cluster Search (CCS),远程集群节点用于连接多个独立的 ES 集群。
# 配置远程集群连接
cluster:
remote:
cluster_one:
seeds: 192.168.1.10:9300
cluster_two:
seeds: 192.168.2.10:9300
1.7 多角色混合 vs 专用角色:选型指南
| 小集群 (1-3节点) | master + data + ingest 混合 | 简化运维,资源利用率高 |
| 中型集群 (6-15节点) | 3个专用 master + 数据节点 + 2个协调节点 | 主节点稳定性优先 |
| 大型集群 (15+节点) | 3-5 master + 数据节点分层 + 专用协调 + 专用 ingest | 职责完全隔离 |
二、主节点选举机制与脑裂问题
主节点选举是 ElasticSearch 集群最核心的分布式协调机制。理解选举算法,才能真正理解集群的稳定性保障。
2.1 Zen Discovery 与选举算法
ElasticSearch 7.x 之前使用 Zen Discovery,7.x 之后使用基于 Raft 变体的新版协调层。选举过程如下:
选举触发条件:
选举流程:
集群启动
│
▼
┌──────────────────────┐
│ 所有 master-eligible │ ① 每个有资格节点启动
│ 节点加入集群 │ ping 所有已知节点
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ 收集所有 master- │ ② 构建候选节点列表
│ eligible 节点 ID │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ 按 node ID 排序 │ ③ 选 ID 最小的作为
│ 选出临时 master │ 临时 master
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ 临时 master 发起 │ ④ 等待达到 quorum
│ 投票请求 │ (多数派) 数量的
│ │ 节点确认
└──────────┬───────────┘
│
▼
┌─────┴─────┐
│ 达到多数派? │
└─────┬─────┘
yes │ no ────▶ 重新选举
▼
┌──────────────────────┐
│ 临时 master 正式 │ ⑤ 成为正式 master
│ 成为主节点 │
└──────────────────────┘
ES 8.x 的选举机制更为成熟,避免了 Zen Discovery 的一些历史问题。核心算法始终围绕一个原则:必须获得 majority(多数派)的投票才能成为主节点。
2.2 法定人数 (Quorum) 的数学本质
为什么是 majority?
法定人数 quorum = floor(N / 2) + 1
节点数 N=1 → quorum = 1 (无法容错,不推荐)
节点数 N=3 → quorum = 2 (允许 1 个节点故障)
节点数 N=5 → quorum = 3 (允许 2 个节点故障)
节点数 N=7 → quorum = 5 (允许 3 个节点故障)
关键公式: 集群能容忍的故障节点数 = floor((N-1) / 2)
这就是为什么官方强烈建议 master-eligible 节点数为奇数(3、5、7),偶数个(如 2、4)并不会提高容错能力,反而增加脑裂风险。
3 个 master 节点:
├─ 容忍 1 个故障 ✓
├─ 2 个故障 → 无法达到 quorum ✗
└─ 数据安全:集群不可写,但可读
2 个 master 节点:
├─ quorum = 2
├─ 故障 1 个 → 无法选举 ✗ (与 3 节点相比更差)
└─ 没有容错能力!
2.3 脑裂 (Split Brain):集群的致命问题
什么是脑裂?
网络分区 (Network Partition)
┌───────────┐ ┌───────────┐
│ Node A │ │ Node B │
│ (Master) │ ✕ │ (Master) │ ← 两个集群各自
│ Node C │ │ Node D │ 选出独立的 master
└───────────┘ └───────────┘
两个 master 同时接受写入
→ 数据冲突 + 数据丢失
脑裂场景:
| 网络交换机故障 | 机房内网络分区 | 两个子集群各自选举 master |
| 节点假死 (GC 停顿) | 主节点长时间 Full GC | 被误判为宕机,触发重新选举 |
| 跨机房部署 | 机房间专线中断 | 两边独立运行 |
解决方案一:minimum_master_nodes (ES 6.x 及之前)
# 至少需要这么多 master-eligible 节点才能选举
discovery.zen.minimum_master_nodes: 2 # 对应 N=3
公式:minimum_master_nodes = floor(N/2) + 1
解决方案二:discovery.seed_hosts + cluster.initial_master_nodes (ES 7.x+)
# 候选 master 节点列表
discovery.seed_hosts:
– master01:9300
– master02:9300
– master03:9300
# 首次启动时的初始 master 集合
cluster.initial_master_nodes:
– master01
– master02
– master03
解决方案三:自动法定人数 (ES 8.x+)
ES 8.x 不再需要手动配置 minimum_master_nodes,系统基于参与选举的 master-eligible 节点数自动计算法定人数。
2.4 脑裂的自动恢复机制
当网络分区恢复后,旧 master 发现集群中已存在新 master:
旧 master (Node A) 重新连上集群
│
▼
发现存在更高 term 的 master (Node B)
│
▼
自动降级 → 将未 commit 的写入回滚
│
▼
从新 master 同步最新 Cluster State
│
▼
重新加入集群,作为普通 master-eligible 节点
2.5 生产环境最佳实践
# 推荐配置(3 节点 master 集群)
cluster.name: es–production
# 节点名称 — 建议用主机名或可读的标识
node.name: ${HOSTNAME}
# 角色
node.roles: [ master ]
# 网络绑定 — 不要用 localhost
network.host: 0.0.0.0
network.publish_host: 192.168.1.11
# 发现种子节点 — 列出所有 master-eligible 节点
discovery.seed_hosts:
– 192.168.1.11:9300
– 192.168.1.12:9300
– 192.168.1.13:9300
# 初始 master 节点(仅首次集群初始化使用)
cluster.initial_master_nodes:
– master01
– master02
– master03
避坑指南:
三、分片分配、迁移与恢复机制
分片是 ElasticSearch 数据分布和并行处理的基本单元。分片的生命周期管理决定了集群的性能和可靠性。
3.1 分片基础:Primary + Replica
索引: orders (5 个主分片, 1 个副本)
┌────────────────────────────────────────────┐
│ 集群 (3 节点) │
│ │
│ ┌ Node-1 ────────────┐ ┌ Node-2 ────────┐│
│ │ P0 R1 R3 │ │ P1 R2 R4 ││
│ │ P4 │ │ P3 ││
│ └────────────────────┘ └───────────────┘│
│ ┌ Node-3 ────────┐ │
│ │ P2 R0 │ │
│ └────────────────┘ │
│ Px = 主分片 (Primary Shard) │
│ Rx = 副本分片 (Replica Shard) │
└────────────────────────────────────────────┘
规则:
– 主分片和它的副本绝不放在同一节点
– 每个节点尽量均衡分布主分片
– 主分片数在索引创建后不可修改
3.2 分片分配 (Shard Allocation)
分配器 (Allocator) 的两个阶段:
Shard Allocation 流程
═══════════════════════════
Phase 1: 分片分配决策
│
├─ 检查集群健康状态
├─ 识别未分配的分片 (unassigned shards)
├─ 评估每个分片的分配约束
│ ├─ 分片分配过滤 (shard allocation filtering)
│ ├─ 分片感知 (shard allocation awareness)
│ └─ 磁盘水位线 (disk watermark)
└─ 为每个分片选择最优节点
Phase 2: 执行分配
│
├─ 从对端副本恢复数据 (peer recovery)
├─ 从快照恢复 (snapshot recovery)
└─ 标记分片为 STARTED
分片分配感知 (Allocation Awareness):
跨机架/可用区部署时,确保主分片和副本分布在不同物理位置。
PUT _cluster/settings
{
"persistent": {
"cluster.routing.allocation.awareness.attributes": "rack_id",
"cluster.routing.allocation.awareness.force.rack_id.values": ["rack1", "rack2", "rack3"]
}
}
机架感知分配:
rack1 rack2 rack3
┌────────┐ ┌────────┐ ┌────────┐
│ Node-A │ │ Node-B │ │ Node-C │
│ P0 P3 │ │ P1 P4 │ │ P2 │
│ R1 R4 │ │ R2 R0 │ │ R3 │
└────────┘ └────────┘ └────────┘
如果 rack1 宕机:
– P0 丢失 → Node-B 上的 R0 提升为主分片
– P3 丢失 → Node-C 上的 R3 提升为主分片
– 集群自动在 rack2/rack3 上创建新的副本
分片分配过滤 (Allocation Filtering):
// 将索引 pin 到特定节点
PUT my_index/_settings
{
"index.routing.allocation.include.temperature": "hot"
}
// 将热数据从旧节点迁出
PUT _cluster/settings
{
"transient": {
"cluster.routing.allocation.exclude._ip": "192.168.1.100"
}
}
3.3 分片迁移 (Shard Relocation)
分片迁移是集群动态平衡的核心机制。Balancer 持续监控各节点的分片分布,当不均衡超过阈值时触发迁移。
迁移触发场景:
| 节点加入 | 将部分分片迁移到新节点以平衡负载 |
| 节点即将下线 | exclude 某个节点后,其上的分片需迁移 |
| 磁盘使用不均 | 高水位节点上的分片迁移到低水位节点 |
| 手动 reroute | 管理员手动指定的迁移命令 |
迁移过程(零停机):
源节点 (Node-A) 目标节点 (Node-B)
│ │
│ ① Master 发起迁移指令 │
│◄──────────────────────────────────────│
│ │
│ ② 创建目标分片(空) │
│─────────────────────────────────────▶│
│ │
│ ③ 文件级增量同步 │
│ [索引文件] ─────────────────────────▶│
│ │
│ ④ 追增量写入 │
│ [Translog] ─────────────────────────▶│
│ │
│ ⑤ 锁住源分片写入,最终同步 │
│ [最后一段 Translog] ────────────────▶│
│ │
│ ⑥ 目标分片激活 (STARTED) │
│ │ ← 流量切换到目标分片
│ ⑦ 删除源分片 │
│ [删除] │
控制迁移速度:
PUT _cluster/settings
{
"persistent": {
"cluster.routing.allocation.node_concurrent_recoveries": 2,
"indices.recovery.max_bytes_per_sec": "50mb",
"cluster.routing.allocation.cluster_concurrent_rebalance": 2
}
}
| node_concurrent_recoveries | 2 | 控制单节点并发恢复数,避免 IO 过载 |
| max_bytes_per_sec | 40mb | 限制恢复带宽,避免影响正常读写 |
| cluster_concurrent_rebalance | 2 | 控制集群级别并发迁移数 |
3.4 分片恢复 (Shard Recovery)
三种恢复类型:
| Peer Recovery | 节点重启后追赶增量 | 差异部分 | 快 |
| Snapshot Recovery | 从快照恢复 | 完整分片 | 慢(取决于快照存储) |
| Local Recovery | 节点重启,本地数据完整 | 仅验证 | 极快 |
Peer Recovery 详细流程:
阶段 1: Init
├─ 源分片和目标分片握手
└─ 确定恢复起点 (基于 SeqNo)
阶段 2: Index (文件拷贝)
├─ 拷贝 Lucene segment 文件
└─ 涉及大量磁盘 IO 和网络传输
阶段 3: Translog (增量追赶)
├─ 重放在拷贝期间积累的 translog 操作
└─ 可能循环多次直到差距足够小
阶段 4: Finalize
├─ 锁住源分片写入
├─ 重放最后的 translog
└─ 目标分片激活,切换流量
监控恢复进度:
GET _cat/recovery?active_only=true&v&h=index,shard,time,type,stage,source_node,target_node,files_percent,bytes_percent
恢复期间的读写行为:
| STARTED | ✅ | ✅ | 正常运行 |
| INITIALIZING | ❌ | ❌ | 刚创建,正在恢复数据 |
| RELOCATING | ✅ | ✅ | 迁移中,正常服务 |
| UNASSIGNED | ❌ | ❌ | 未分配 |
3.5 磁盘水位线保护
PUT _cluster/settings
{
"persistent": {
"cluster.routing.allocation.disk.watermark.low": "85%",
"cluster.routing.allocation.disk.watermark.high": "90%",
"cluster.routing.allocation.disk.watermark.flood_stage": "95%",
"cluster.routing.allocation.disk.watermark.flood_stage.frozen": "95%"
}
}
| low | 85% | 不再向该节点分配新分片 |
| high | 90% | 将该节点上的分片迁移到其他节点 |
| flood_stage | 95% | 所有该节点上的索引设为 read-only-allow-delete |
四、集群状态同步与健康监控
4.1 Cluster State:集群的"宪法"
Cluster State 是 ElasticSearch 集群最核心的数据结构,记录了集群的一切元信息。
Cluster State 包含的内容:
Cluster State
├── cluster_uuid # 集群唯一标识
├── version # 状态版本号(单调递增)
├── state_uuid # 状态变更唯一 ID
├── master_node # 当前主节点 ID
├── blocks # 集群级阻塞(如只读保护)
├── nodes # 所有节点信息(名称、IP、角色、属性)
├── metadata # 索引元数据
│ ├── indices # 每个索引的 mapping、setting、alias
│ ├── index_templates # 索引模板
│ ├── ingest_pipelines # 预处理管道
│ └── data_stream # 数据流定义
├── routing_table # 分片路由表
│ └── index → shards → [primary, replicas]
└── routing_nodes # 节点→分片映射
└── node → [shard_list]
Cluster State 的发布流程(两阶段提交):
Master Node Other Master-Eligible Nodes
│ │
│ ① 变更请求到达 master │
│ (创建索引/节点加入/映射变更) │
│ │
│ ② Master 计算新的 Cluster State │
│ 生成新的 version + state_uuid │
│ │
│ ③ 广播增量更新 (diff) │
│──────────────────────────────────────▶│
│ │
│ ④ 各节点验证 & ack │
│◀──────────────────────────────────────│
│ │
│ ⑤ 收到 quorum 数量 ack │
│ → Commit 阶段 │
│──────────────────────────────────────▶│
│ │
│ ⑥ 返回成功给发起方 │
为什么 Cluster State 必须轻量? Cluster State 的变化需要通过 master 广播到所有节点。如果 Cluster State 过大(比如几万个索引、大量字段膨胀的 mapping),每次变更都会产生巨大的网络开销。这就是为什么 ES 对单节点索引数量有软限制(建议不超过 1000 个分片每节点)。
4.2 集群健康状态三色灯
ElasticSearch 使用简单直观的三色灯模型反映集群健康度:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ GREEN │ │ YELLOW │ │ RED │
│ 🟢 │ │ 🟡 │ │ 🔴 │
│ │ │ │ │ │
│ 所有主分片和 │ │ 所有主分片 │ │ 部分主分片 │
│ 副本分片都已 │ │ 已分配,但 │ │ 未分配 │
│ 正确分配 │ │ 部分副本未 │ │ │
│ │ │ 分配 │ │ 数据可能丢失 │
└──────────────┘ └──────────────┘ └──────────────┘
正常运行 有风险但可用 灾难状态
查看集群健康:
# 概览
GET _cluster/health
# 详细
GET _cluster/health?level=indices
# 最详细
GET _cluster/health?level=shards
响应解读:
{
"cluster_name": "es-production",
"status": "yellow",
"timed_out": false,
"number_of_nodes": 5,
"number_of_data_nodes": 3,
"active_primary_shards": 42,
"active_shards": 84,
"relocating_shards": 0, // 正在迁移的分片
"initializing_shards": 0, // 正在初始化的分片
"unassigned_shards": 3, // 未分配的分片 ← 问题关键
"delayed_unassigned_shards": 0,
"number_of_pending_tasks": 0, // master 积压任务
"number_of_in_flight_fetch": 0,
"task_max_waiting_in_queue_millis": 0,
"active_shards_percent_as_number": 96.552
}
Yellow 状态的常见原因与处理:
| 单节点集群且有副本 | 增加节点,或设置 number_of_replicas: 0 |
| 节点刚刚加入,副本正在初始化 | 等待自动恢复 |
| 磁盘满导致副本无法分配 | 扩容磁盘或清理数据 |
| 分片分配过滤规则过严 | 检查 allocation filter 规则 |
Red 状态的紧急处理:
# 1. 定位未分配的主分片
GET _cat/shards?v&h=index,shard,prirep,state,node,unassigned.reason
# 2. 查看未分配原因
GET _cluster/allocation/explain
# 3. 尝试手动分配(慎用!)
POST _cluster/reroute
{
"commands": [
{
"allocate_stale_primary": {
"index": "my_index",
"shard": 0,
"node": "data-node-1",
"accept_data_loss": true
}
}
]
}
4.3 Cluster State 同步的异常场景
场景一:Cluster State 过大导致 OOM
# 检查 cluster state 大小
GET _cluster/state/_all/_all
# 排查过大原因
GET _cluster/stats
# 常见元凶
# – 过多索引 (数万个)
# – mapping 膨胀 (每个索引数百个字段)
# – 大量已关闭的索引残留
# – 过多的 index template / ILM policy
场景二:Master 与 Follower 状态不一致
如果某个节点因为网络问题短暂失联,
它可能持有过期的 Cluster State。
非 master 节点的自我保护机制:
– 如果发现自己持有过期状态
– 拒绝使用过期路由表处理请求
– 主动从 master 拉取最新状态
– 此期间对客户端返回重试
4.4 关键监控指标
集群级指标:
| status | YELLOW 持续 >5min, RED 任何时长 | 集群健康状态 |
| unassigned_shards | >0 持续 >10min | 未分配分片 |
| pending_tasks | >100 持续 | master 积压任务 |
| active_shards_percent | <100% | 分片完整度 |
节点级指标:
| jvm.mem.heap_used_percent | >85% | JVM 堆使用率 |
| thread_pool.write.rejected | >0 | 写入拒绝 |
| thread_pool.search.rejected | >0 | 搜索拒绝 |
| fs.total.disk_used_percent | >85% | 磁盘使用率 |
| gc.old_collection_time | >1s 频繁 | Old GC 耗时 |
五、节点上下线与故障自动容错
ElasticSearch 的设计哲学之一是 “故障是常态”。集群能在节点异常时自动检测、隔离和恢复,整个过程对上层应用透明。
5.1 节点上线的完整流程
新节点启动
│
▼
┌─────────────────────────────┐
│ ① 加载配置,绑定端口 │
│ 读取 elasticsearch.yml │
│ 绑定 HTTP(9200) + │
│ Transport(9300) 端口 │
└─────────────┬───────────────┘
│
▼
┌─────────────────────────────┐
│ ② 发现集群 │
│ Ping discovery.seed_hosts │
│ 加入集群 │
└─────────────┬───────────────┘
│
▼
┌─────────────────────────────┐
│ ③ 握手 & 身份确认 │
│ 交换节点信息、角色 │
│ 验证 cluster.name 一致 │
└─────────────┬───────────────┘
│
▼
┌─────────────────────────────┐
│ ④ 接收 Cluster State │
│ 从 master 同步完整状态 │
└─────────────┬───────────────┘
│
▼
┌─────────────────────────────┐
│ ⑤ Master 评估并分配分片 │
│ 根据负载均衡策略 │
│ 决定将哪些分片迁入 │
└─────────────┬───────────────┘
│
▼
┌─────────────────────────────┐
│ ⑥ 分片恢复 │
│ Peer Recovery 或 │
│ Snapshot Recovery │
└─────────────┬───────────────┘
│
▼
┌─────────────────────────────┐
│ ⑦ 投入服务 │
│ 接受写入和查询请求 │
└─────────────────────────────┘
5.2 节点下线的优雅流程
主动下线(运维操作):
# 方式一:API 调用(推荐)
PUT _cluster/settings
{
"transient": {
"cluster.routing.allocation.exclude._ip": "192.168.1.20"
}
}
# 监控分片迁移进度
GET _cat/shards?v&h=index,shard,prirep,state,node
# 确认该节点上无分片后,安全停止
# 在目标节点上执行:
kill -SIGTERM <pid>
SIGTERM 优雅关闭流程:
收到 SIGTERM
│
▼
├─ ① 停止接受新的 transport 请求
├─ ② 等待正在执行的请求完成(blocking)
├─ ③ 刷新 translog → refresh → flush
├─ ④ 迁移节点上的主分片到其他节点
├─ ⑤ 通知 master 节点即将离开
├─ ⑥ 关闭网络连接
└─ ⑦ JVM 退出
不要用 kill -9! 强制杀进程会导致分片数据不一致、translog 丢失,恢复过程更长且可能丢失已提交但未刷盘的数据。
5.3 故障检测 (Fault Detection)
ElasticSearch 使用两种独立的故障检测机制:
① Master → 其他节点的检测 (FollowerChecker):
Master 定期 ping 所有节点
│
├─ ping_interval: 默认 1s
├─ ping_timeout: 默认 30s
│
└─ 超时 3 次 → 标记节点为 offline
→ 从 Cluster State 移除
→ 触发分片重新分配
② 其他节点 → Master 的检测 (LeaderChecker):
所有节点定期 ping Master
│
├─ 超时 → 认为 Master 不可用
│
└─ 触发新一轮选举
故障检测参数调优:
# elasticsearch.yml
# Master 故障检测
discovery.zen.fd.ping_interval: 1s # ping 间隔
discovery.zen.fd.ping_timeout: 30s # ping 超时
discovery.zen.fd.ping_retries: 3 # 重试次数
# 7.x+ 等价参数
cluster.fault_detection.leader_check.interval: 1s
cluster.fault_detection.leader_check.timeout: 30s
cluster.fault_detection.follower_check.interval: 1s
cluster.fault_detection.follower_check.timeout: 30s
故障检测与网络抖动的权衡:
ping_timeout 太小 (如 5s):
├─ 优点: 故障检测快
└─ 缺点: 网络抖动时频繁误判
ping_timeout 太大 (如 120s):
├─ 优点: 减少误判
└─ 缺点: 故障检测太慢,数据窗口长
推荐: 30s – 60s,适应大多数生产环境
5.4 故障自动容错的完整时间线
以数据节点宕机为例:
T+0s Node-D 宕机
│
T+3s 连续 3 次 ping 超时
│
T+3s Master 检测到 Node-D 离线
│ ① 从 Cluster State 中移除 Node-D
│ ② 标记 Node-D 上的所有分片为 unassigned
│
T+3s 评估每个受影响的分片
│
├─ 主分片 P0 在 Node-D 上
│ → 检查 P0 的副本 R0 是否在健康节点上
│ → 若 R0 在 Node-E 上且数据完整
│ → 将 R0 提升为主分片 ← 恢复写入能力
│
└─ 副本分片 R1 在 Node-D 上
→ 在集群中找合适节点创建新的 R1 ← 恢复冗余度
T+5s 发起分片恢复
│
├─ Peer Recovery: R0(Node-E) → 新 P0
└─ 新副本分配: 在 Node-F 上创建新 R1
T+30s 恢复完成(取决于数据量)
│
集群回到 GREEN 状态
分片分配延迟 (Delayed Allocation):
对于短暂故障(如节点重启),频繁的分片迁移会导致不必要的网络开销。ES 提供了延迟分配机制:
PUT _all/_settings
{
"settings": {
"index.unassigned.node_left.delayed_timeout": "5m"
}
}
这样当节点在 5 分钟内恢复时,分片不会触发迁移,而是直接从原节点恢复——避免了完整的数据拷贝。
5.5 极端场景应对
场景一:Master 节点全部宕机
影响:
├─ 集群元数据不可变(不能创建/删除索引)
├─ 已有索引的读写正常(路由表已缓存在各节点)
└─ 无法处理节点上下线事件
应对:
├─ 至少保留一个 master-eligible 节点存活
├─ 紧急恢复一台 master 节点
└─ 如果所有 master 都不可恢复,使用 -Dignore_bootstrap_checks 强制启动
场景二:一半以上数据节点同时宕机
影响:
├─ 如果节点持有 primary shard → RED 状态,部分数据不可读写
├─ 如果节点只持有 replica → YELLOW 状态,可读写但不安全
应对:
├─ 尽快恢复宕机节点
├─ 如果无法恢复,接受数据丢失:
│ POST _cluster/reroute { "allocate_stale_primary": {…} }
└─ 从快照恢复是最安全的选择
场景三:磁盘满导致集群崩溃
防护机制:
├─ flood_stage (95%) → 所有索引 set read-only
├─ high (90%) → 迁移分片到其他节点
└─ low (85%) → 停止分配新分片到此节点
紧急处理:
├─ 清理磁盘空间
├─ 删除过期索引或日志
└─ 手动解除只读:
PUT */_settings { "index.blocks.read_only_allow_delete": null }
5.6 集群备份的最后防线——快照
# 1. 注册快照仓库
PUT _snapshot/my_backup
{
"type": "fs",
"settings": {
"location": "/mnt/backups/elasticsearch",
"compress": true
}
}
# 2. 创建快照
PUT _snapshot/my_backup/snapshot_20260606
{
"indices": "*",
"ignore_unavailable": true,
"include_global_state": true
}
# 3. 查看快照状态
GET _snapshot/my_backup/snapshot_20260606/_status
# 4. 从快照恢复(紧急恢复手段)
POST _snapshot/my_backup/snapshot_20260606/_restore
{
"indices": "critical_index",
"include_global_state": false
}
# 5. 设置 SLM (Snapshot Lifecycle Management) 自动快照
PUT _slm/policy/daily_backup
{
"schedule": "0 30 1 * * ?",
"name": "<daily-snap-{now/d}>",
"repository": "my_backup",
"config": {
"indices": "*",
"ignore_unavailable": true,
"include_global_state": true
},
"retention": {
"expire_after": "30d",
"min_count": 5,
"max_count": 50
}
}
本章小结
ElasticSearch 的集群高可用机制是一个精密的分层设计体系:
- 节点角色分离让每种资源都物尽其用
- 选举机制确保集群始终有一个唯一的决策者
- 分片管理将数据均匀分布并自动修复
- 健康监控让运维人员一目了然
- 故障容错让集群在异常中自我治愈
掌握这些基本原理后,下一章我们将进入实战——集群性能调优,包括 JVM 参数优化、索引设计最佳实践、读写性能优化策略。敬请期待!
本章完 · ElasticSearch从入门到架构师 · 第10章



