三节点实验集群能验证发现、选举和分片复制,但不能直接推导出生产拓扑。生产设计必须同时回答四个问题:哪些节点参与控制面,哪些节点承担数据面,故障域如何隔离,以及任一节点或可用区退出后,剩余容量能否继续承载业务。

本文面向自建 Elasticsearch 8.x/9.x 集群,给出从小型混合角色集群到分层数据节点集群的参考拓扑。具体节点数、分片数和硬件规格仍应以目标版本和真实负载压测为准;Elastic Cloud Serverless 的资源模型不适用这里的自建配置。

先选拓扑,再写配置

生产集群没有一套固定节点清单。轻负载可以让节点混合承担 master、data 和 ingest;规模扩大后,再把控制面和高消耗的数据处理职责拆开。角色拆分的目标是隔离资源竞争和故障影响,不是让节点名字更多。

图 1:从实验集群到分层生产集群的演进路径

flowchart LR
  A[单节点开发环境<br/>全部默认角色] --> B[三节点小型生产集群<br/>master + data + ingest]
  B --> C[控制面分离<br/>3 个专用 master-eligible]
  C --> D[数据面分层<br/>content / hot / warm / cold]
  D --> E[按瓶颈增配<br/>ingest、coordinating、ML]

一种常见的大型自建拓扑如下。三台专用 master-eligible 节点分布在三个故障域;客户端通过负载均衡器访问数据入口;ingest、coordinating 和各数据层按实际压力决定是否独立扩容。

图 2:跨三个可用区的生产参考拓扑

flowchart TB
  Client[应用客户端] --> LB[四层或七层负载均衡]
  LB --> C1[coordinating-1]
  LB --> C2[coordinating-2]
  C1 --> I1[ingest-1]
  C2 --> I2[ingest-2]
  I1 --> H1[data_hot + data_content<br/>AZ-a]
  I2 --> H2[data_hot + data_content<br/>AZ-b]
  H1 <--> H2
  H1 --> W1[data_warm<br/>AZ-a]
  H2 --> W2[data_warm<br/>AZ-b]
  W1 --> COLD[data_cold<br/>跨故障域]
  COLD --> REPO[(快照仓库)]
  M1[master-eligible<br/>AZ-a] --- M2[master-eligible<br/>AZ-b]
  M2 --- M3[master-eligible<br/>AZ-c]
  M3 --- M1
  M1 -. cluster state .-> H1
  M2 -. cluster state .-> H2
  M3 -. cluster state .-> W1

这张图是职责关系,不是固定采购单。若查询和写入压力不高,coordinating、ingest 与数据角色可以合并;若没有时序数据,也不需要 hot、warm、cold 全套数据层。

节点角色不是机器类型

node.roles 决定节点能够承担的职责。一台机器可以列出多个角色;显式配置后,未列出的角色不会启用。每个节点都具备请求协调能力,coordinating 不是可配置角色,node.roles: [] 只是把其他角色全部移除后的 coordinating-only 节点。

图 3:常用节点角色与主要职责

flowchart LR
  R[node.roles] --> M[master<br/>选举与集群状态]
  R --> D[data / data_*<br/>持有分片并执行查询]
  R --> I[ingest<br/>执行 ingest pipeline]
  R --> ML[ml / transform<br/>后台计算任务]
  R --> RC[remote_cluster_client<br/>跨集群连接]
  R --> V[voting_only<br/>投票但不当选]
  CO[node.roles: 空列表] --> C[coordinating-only<br/>路由、归并、Bulk 分发]

常用配置形态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 专用 master-eligible 节点
node.roles: [master]

# 热数据与非时序内容混合节点
node.roles: [data_hot, data_content, ingest]

# 专用 warm 节点
node.roles: [data_warm]

# 专用 ingest 节点
node.roles: [ingest]

# coordinating-only 节点
node.roles: []

Elasticsearch 不存在 search 节点角色。搜索由持有目标分片的 data 节点执行,接收请求的任意节点负责 scatter、gather 和最终归并。专用 master 节点仍有协调能力,但生产业务入口不应指向它,以免大查询和 Bulk 请求干扰控制面。

控制面与数据面

master 节点负责索引元数据、路由表和节点成员关系等 cluster state 变更。文档写入和搜索不经过当选 master,而是由接收请求的节点依据本地 cluster state 直接找到目标分片。

图 4:控制面与数据面的通信边界

flowchart LR
  subgraph Control[控制面]
    API[创建索引或修改 mapping] --> Master[当选 master]
    Master --> State[发布 cluster state]
  end
  subgraph Data[数据面]
    Search[搜索或写入] --> Coord[接收请求的节点]
    Coord --> S1[目标 shard]
    Coord --> S2[目标 shard]
    S1 --> Coord
    S2 --> Coord
  end
  State -. 本地更新 .-> Coord
  State -. 本地更新 .-> S1
  State -. 本地更新 .-> S2

cluster state 发布也不是“master 每次把完整状态广播给所有节点”。正常情况下发送相对上一版本的 diff;新加入节点或缺少基础版本的节点才接收完整状态。master 取得足够 master-eligible 节点确认后提交,再等待节点应用。

图 5:Cluster state 的发布、提交与应用

sequenceDiagram
  participant M as 当选 master
  participant Q1 as master-eligible A
  participant Q2 as master-eligible B
  participant D as data / ingest 节点
  M->>Q1: 发布新状态(通常是 diff)
  M->>Q2: 发布新状态(通常是 diff)
  Q1-->>M: 确认接收
  Q2-->>M: 确认接收
  Note over M,Q2: 达到法定确认数后提交
  M->>Q1: commit
  M->>Q2: commit
  M->>D: 发布并通知应用
  D-->>M: applied
  Note over M,D: 缺少基础版本时改发完整状态

三个 master-eligible 节点解决什么

高可用集群至少需要三个 master-eligible 节点,其中至少两个不能是 voting-only。三个节点能在失去一个节点后保留多数派,避免双主。它们最好分布在三个独立故障域;若只跨两个可用区,2+1 布局只能承受特定一侧故障。

图 6:三个 master-eligible 节点的多数派

flowchart TB
  subgraph Healthy[正常:3 票中取得 2 票]
    A[master A<br/>AZ-a] --- B[master B<br/>AZ-b]
    B --- C[master C<br/>AZ-c]
    C --- A
  end
  subgraph Failed[AZ-a 故障后]
    X[master A 不可达] -. 网络中断 .-> Y[master B 存活]
    Y === Z[master C 存活]
    Q[2 票构成多数派<br/>可以继续选举]
    Y --> Q
    Z --> Q
  end

三台节点并不自动等于生产高可用。还要确认数据副本跨故障域、客户端入口有冗余、必要角色至少有两个实例,并且故障后的剩余节点仍有足够 CPU、内存、磁盘和恢复带宽。

首次引导与日常发现

discovery.seed_hosts 告诉节点去哪里寻找 master-eligible 节点。cluster.initial_master_nodes 只用于全新集群第一次引导,集群形成后必须移除;加入现有集群、滚动重启和全量重启都不应再次设置它。

图 7:发现设置与首次集群引导的生命周期

flowchart LR
  A[准备全新节点<br/>统一 cluster.name] --> B[配置 discovery.seed_hosts]
  B --> C[仅首次配置<br/>cluster.initial_master_nodes]
  C --> D[选出 master<br/>生成 cluster UUID]
  D --> E[移除 initial_master_nodes]
  E --> F[后续重启或扩容<br/>只通过 seed hosts 加入]
  C -. 名称不一致 .-> G[可能形成错误集群或引导失败]
1
2
3
4
5
6
7
cluster.name: search-prod
node.name: master-a
network.host: 0.0.0.0
discovery.seed_hosts: ["master-a", "master-b", "master-c"]

# 仅全新集群首次形成时存在,成功后删除
cluster.initial_master_nodes: ["master-a", "master-b", "master-c"]

搜索请求如何穿过集群

搜索分为 query 和 fetch 两阶段。协调节点将请求发到每个目标分片的一份副本,归并候选结果,再从命中所在分片获取文档。主分片和副本都能处理搜索,不存在永久绑定的“查询副本节点”。

图 8:搜索请求的 scatter-gather 路径

sequenceDiagram
  participant C as Client
  participant Q as Coordinating node
  participant S0 as shard 0 的一份副本
  participant S1 as shard 1 的一份副本
  C->>Q: _search
  par Query phase
    Q->>S0: query + top K
    Q->>S1: query + top K
  end
  S0-->>Q: doc IDs + scores
  S1-->>Q: doc IDs + scores
  Q->>Q: 全局归并与排序
  par Fetch phase
    Q->>S0: fetch 命中文档
    Q->>S1: fetch 命中文档
  end
  Q-->>C: hits + aggregations

专用 coordinating 节点能隔离归并和请求入口,但它也接收 cluster state,并消耗堆内存处理大聚合和 Bulk 请求。只有当入口或归并成为可测量瓶颈时,才值得单独部署。

写入请求与 ingest pipeline

Bulk 请求先在协调节点按目标主分片拆分;若配置 ingest pipeline,则由 ingest 角色执行预处理;主分片完成写入后复制到 in-sync 副本。大规模 pipeline 处理适合独立 ingest 节点,简单轻量写入可以由数据节点兼任。

图 9:Bulk 写入、Ingest 与主副本复制路径

sequenceDiagram
  participant C as Client
  participant Q as Coordinating node
  participant I as Ingest node
  participant P as Primary shard
  participant R as Replica shard
  C->>Q: _bulk
  Q->>I: 执行 pipeline
  I-->>Q: 处理后的文档
  Q->>P: 按 routing 写入
  P->>R: 复制操作
  R-->>P: ack
  P-->>Q: item result
  Q-->>C: bulk response

Content 与时序数据分层

data_content 面向商品、文章、用户档案等非时序内容,数据通常长期驻留。时序数据才沿 hot、warm、cold、frozen 生命周期迁移。cold 层既可以存普通索引及副本,也可以使用 fully mounted searchable snapshots;frozen 层只承载 partially mounted searchable snapshots,并依赖快照仓库。

图 10:Content 常驻路径与时序数据生命周期

flowchart TB
  App[业务索引] --> Content[data_content<br/>长期驻留]
  Stream[日志与指标] --> Hot[data_hot<br/>写入与高频查询]
  Hot --> Warm[data_warm<br/>只读与较低频查询]
  Warm --> Cold[data_cold<br/>普通索引或 fully mounted]
  Cold --> Frozen[data_frozen<br/>partially mounted]
  Frozen --> Delete[删除]
  Cold --> Repo[(快照仓库)]
  Frozen --> Repo

内建 data tier 角色配合 ILM 使用。Elasticsearch 通过 _tier_preference 选择可用层,例如 data_cold,data_warm,data_hot 表示优先 cold,无法使用时再尝试 warm 和 hot;它不是磁盘满后的自动溢出机制,其他分配限制仍可能阻止分片迁移。

1
2
3
4
5
6
7
8
# hot 节点
node.roles: [data_hot]

# warm 节点
node.roles: [data_warm]

# cold 节点
node.roles: [data_cold]

旧式 node.attr.data: hot 加自定义 allocation filter 仍然有效,但新建集群优先采用内建 data tier 角色。迁移旧集群时,需要同时检查节点角色、索引模板、ILM policy 和存量索引过滤条件,避免新旧规则互相约束。

让主副本跨故障域

节点属性和 allocation awareness 可以让 Elasticsearch 在分配分片时感知可用区。主分片和副本应落在不同故障域,否则单个机架或可用区故障可能同时带走两份数据。

图 11:Allocation awareness 下的跨可用区分片布局

flowchart LR
  subgraph AZA[AZ-a]
    A0[shard 0 primary]
    A1[shard 1 replica]
  end
  subgraph AZB[AZ-b]
    B0[shard 0 replica]
    B1[shard 1 primary]
  end
  A0 <--> B0
  B1 <--> A1
1
2
3
4
5
# 每个节点设置自己的 zone
node.attr.zone: az-a

# 集群动态设置
cluster.routing.allocation.awareness.attributes: zone

forced awareness 在一个可用区退出后允许副本保持未分配,而不是把所有副本挤到剩余区域。此时集群可能是 yellow,但主分片仍可读写;代价是故障期间副本冗余下降。

图 12:Forced awareness 在可用区故障时的取舍

flowchart LR
  A[AZ-a 故障] --> B[AZ-b 保留主分片]
  B --> C[副本保持 unassigned]
  C --> D[集群 yellow<br/>业务可能仍可用]
  C --> E[避免在 AZ-b 复制全量副本<br/>防止磁盘和网络过载]
  F[AZ-a 恢复] --> G[副本重新分配]
  G --> H[集群恢复 green]

容量要按故障状态计算

容量规划不能只算“日增量乘保留天数”。至少要加入副本系数、段合并与水位余量、单节点或单区故障后的承载能力,以及恢复期间的网络和磁盘吞吐。

图 13:生产容量预算的组成

flowchart TB
  Raw[原始数据量 × 保留周期] --> Index[索引膨胀或压缩系数]
  Index --> Replica[副本与快照策略]
  Replica --> Headroom[磁盘水位与 merge 余量]
  Headroom --> Failure[N-1 节点或单区故障余量]
  Failure --> Recovery[恢复带宽与恢复时间目标]
  Recovery --> Test[真实查询与写入压测]
  Test --> Size[节点数、分片数与硬件规格]

10–50GB 每分片、每分片少于约两亿文档可以作为初始检查范围,不是硬性最优值。最终参数应由真实文档、mapping、查询、聚合、写入速率和硬件压测决定。副本也不等于备份;受支持的集群备份方式是 snapshot,复制节点数据目录不能替代快照。

扩容、缩容和角色调整

增加数据节点后,Elasticsearch 会按分配规则迁移分片。缩容前必须先排空目标节点并等待 relocation 完成。移除 data 或 master 角色时,直接修改 YAML 后重启并不安全;使用新节点和空数据目录通常更简单。elasticsearch-node repurpose 会删除不再允许保留的磁盘数据,只适合明确理解后果的修复流程。

图 14:数据节点扩缩容与退役顺序

flowchart LR
  A[新增空数据节点] --> B[加入现有 cluster UUID]
  B --> C[观察 shard relocation]
  C --> D[确认健康与容量稳定]
  D --> E[对待退役节点设置 allocation exclude]
  E --> F[等待分片迁出]
  F --> G[停止并移除节点]
  G --> H[清理临时分配设置]

滚动维护一次只处理一个节点

滚动重启和滚动升级的基本约束相同:每次只停止一个节点,节点重新加入并恢复到可接受状态后,再处理下一台。升级顺序应按目标版本的官方文档执行;混合版本只允许存在于升级窗口,不能作为长期运行形态。

图 15:滚动维护的单节点闭环

flowchart LR
  A[记录健康状态与未分配原因] --> B[按文档调整临时 allocation]
  B --> C[停止一个节点]
  C --> D[维护或升级]
  D --> E[启动并重新加入]
  E --> F[等待节点和分片稳定]
  F --> G{还有节点?}
  G -- 是 --> C
  G -- 否 --> H[恢复临时设置并复核健康]

部署检查表

检查项 最低要求 常见错误
控制面 至少三个 master-eligible,至少两个非 voting-only 业务流量直接打到专用 master
数据冗余 必要数据至少两份,跨故障域分配 主副本落在同一机架或可用区
角色冗余 必要的 data、ingest、入口角色至少两个实例 单个 coordinating 或 ingest 成为入口单点
故障容量 节点或可用区退出后仍能承载峰值 只按正常状态平均利用率采购
恢复能力 有快照、恢复演练和恢复时间目标 把 replica 或数据目录复制当备份
网络安全 客户端使用 HTTPS,节点间 transport TLS 直接向不可信网络暴露 HTTP/transport 端口

部署完成后,至少检查 _cat/nodes 的实际角色、_cluster/health、_cluster/allocation/explain、快照状态和单节点退出场景。配置文件看起来正确,只能证明静态配置存在,不能替代故障演练和真实负载验证。

系列导航

参考资料