欢迎光临
我们一直在努力

【ElasticSearch从入门到架构师】第10章:集群高可用核心原理

集群是高可用的基石。本章深入拆解 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 类型:

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: esproduction

    # 节点名称 — 建议用主机名或可读的标识
    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

    避坑指南:

  • 永远不要部署 2 个 master-eligible 节点 — 任意一个故障就会导致集群不可用
  • master 节点放在不同物理机/机架上 — 避免单点故障
  • 不要将 master 节点和重型数据节点混部 — 主节点的 GC 停顿可能触发误判
  • 合理配置 discovery.zen.ping_timeout — 避免因网络抖动误判节点宕机
  • 监控 master 节点的 GC 频率和时长 — Full GC 超过 30 秒可能触发重新选举

  • 三、分片分配、迁移与恢复机制

    分片是 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章

    赞(0)
    未经允许不得转载:171主机测评 » 【ElasticSearch从入门到架构师】第10章:集群高可用核心原理
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址