Skip to content
ES
集群架构

集群架构

ES 集群的核心:水平扩展、高可用、分布式计算。本章涵盖节点角色、集群搭建、数据路由、故障转移和生产运维。


为什么需要集群

单节点面临两个根本问题:

问题 集群解决方案
海量数据超过单节点磁盘容量 索引拆分为 N 个 Shard,分布到多节点
单点故障(节点宕机导致数据不可用) 分片数据在不同节点上副本冗余

image-20260611102838312


节点角色

角色 配置参数 默认值 职责
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 压力、搜索延迟抖动等问题。

角色分离最佳实践

image-20260611104941622

集群规模 建议
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 同时存在。

image-20260611145128394

解决原理:法定人数

选票数需超过 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。

image-20260611145202463

自定义路由

# 写入时指定路由
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 条。分片越多、分页越深、开销越大。


故障转移

image-20260611145220998

流程

步骤 说明
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 Awarenesscluster.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 所有副本可能在同一物理机架/可用区
🚨 快照未定期执行 集群故障时无恢复手段