跳到正文

集群管理与分片 ​

一句话:ES 8.x 用「master 候选节点 + 多数派投票」维持单一集群状态,用 allocation decider 决定分片放哪;排障的核心不是手动 reroute,而是用 _cluster/allocation/explain 看清哪个 decider 拒绝了分配。

适用版本:Elasticsearch 8.x。7.0 起已移除 discovery.zen.minimum_master_nodes,6.x 的脑裂配置不要照搬。

为什么需要 ​

集群里有两个独立问题:

  1. 谁来管状态:索引创建、分片分配、节点上下线,需要一个唯一的 master。多个 master 同时决策会导致元数据冲突。
  2. 分片放哪:节点故障、磁盘水位、机架感知、副本分布,都会影响分片能否分配和恢复速度。

ES 的答案是:master 由多数派选举产生,集群状态变更走单一 master;分片分配由一组 decider 投票,任何一个 decider 说「不行」,分片就不会落到该节点。

核心机制 ​

节点角色与最小拓扑 ​

yaml
# 专用 master 节点(3 个,奇数)
cluster.name: prod
node.name: master-1
node.roles: [ master ]
discovery.seed_hosts: ["master-1:9300", "master-2:9300", "master-3:9300"]
cluster.initial_master_nodes: ["master-1", "master-2", "master-3"]   # 仅首次启动
yaml
# 数据节点
node.name: data-1
node.roles: [ data_hot, data_content, ingest ]
discovery.seed_hosts: ["master-1:9300", "master-2:9300", "master-3:9300"]
  • cluster.initial_master_nodes 只在首次 bootstrap 时使用;集群形成后必须移除,否则可能误引导出第二个集群。
  • 生产至少 3 个 master 候选节点,跨机架/可用区;2 个候选节点挂一个就没有多数派。
  • master 与 data 角色尽量分离;小集群可以混部,但不要让 master 承担重写入/重聚合。

选举与「脑裂」 ​

  • 7.x+ 使用基于投票配置(voting configuration)的多数派选举:候选人必须拿到多数票;少数派分区选不出 master。
  • 因此 7.x+ 不会出现「两边各选一个 master」的经典脑裂;真正的风险是多数派丢失导致集群无法选主,或 cluster.initial_master_nodes 配错导致启动出两个独立集群。
  • 6.x 的 discovery.zen.minimum_master_nodes = N/2+1 在 7.0 起已移除,不要再配。

分片分配与 allocation explain ​

分片分配由 decider 链决定,常见的有:

Decider关心什么
disk磁盘水位是否超过 low/high/flood_stage
awareness机架/可用区属性是否满足
same_shard主副本是否落在同一节点
filterindex.routing.allocation.* 过滤规则
max_retry分配失败重试次数
throttled并发恢复是否达到上限
bash
# 某个分片为什么没分配
GET _cluster/allocation/explain
{
  "index": "orders",
  "shard": 0,
  "primary": true
}

返回里的 allocate_explanation 会直接说明原因,例如 disk watermark [95%] exceeded、node does not match index setting [_name]、replica shard cannot be allocated to the same node as primary。先看 explain,再决定扩盘、加节点、调副本还是改过滤规则。

磁盘水位 ​

水位默认行为
low85%不再向该节点分配新分片
high90%尝试把分片迁走
flood_stage95%该节点上的索引变为只读
json
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.threshold_enabled 默认 true;不要为了「先写进去」关掉它,关掉只会把磁盘写满。

恢复与再平衡 ​

  • 节点故障后,master 把该节点上的主分片提升副本或重建副本;恢复受 cluster.routing.allocation.node_concurrent_recoveries(默认 2)和 indices.recovery.max_bytes_per_sec(默认 40mb,以官方文档为准)限制。
  • cluster.routing.rebalance.enable 默认 all;可以按需设为 primaries/replicas/none。
  • 机架感知:设置 cluster.routing.allocation.awareness.attributes: zone,并在各节点配置 node.attr.zone,主副本会尽量分散到不同 zone。

常用命令 ​

bash
GET _cluster/health?pretty
GET _cat/nodes?v&h=name,node.role,heap.percent,disk.used_percent,master
GET _cat/shards?v&h=index,shard,prirep,state,unassigned.reason,node

实战场景 ​

一、cluster.initial_master_nodes 没删,重启引导出第二个集群 ​

现象:3 节点 master 集群做滚动重启(配置调优)。第二台重启完成后,监控里出现两个 prod 集群、数据节点分成两组各自选举;其中一个集群缺了一个 master 候选,随即无法选主,索引变红。运维最初以为是“网络分区导致脑裂”。

排查:7.x+ 的多数派投票不会出现“两边各选一个 master”,真正的风险在 bootstrap 配置。

定位层取证据判定
bootstrap 配置是否残留各节点 elasticsearch.yml 里是否仍有 cluster.initial_master_nodes集群已形成后必须移除
集群 IDGET _cluster/health 与日志里的 cluster UUID两个不同 UUID 即引导出两套集群
是否满足引导条件只有“全新集群首次启动”才允许引导已有数据目录时被再次触发即危险
网络连通9300 端口跨节点是否互通、discovery.seed_hosts 是否正确分区会让节点看不到既有集群
多数派候选数当前存活 master 候选与配置要求的多数候选只剩 1 个时无法选主
版本是否仍在配 6.x 的 discovery.zen.minimum_master_nodes7.0 起该设置已移除,留着是误导
text
经典脑裂(两边各选一个 master)在 7.x+ 的投票配置机制下基本不会发生:
少数派分区选不出 master。所以看到“两个集群”要往 bootstrap 方向查,
而不是往“脑裂”方向调参数。

对症做法:

yaml
# 集群形成之后,从所有节点的配置里移除该行,只保留 seed_hosts
cluster.name: prod
node.name: master-1
node.roles: [ master ]
discovery.seed_hosts: ["master-1:9300", "master-2:9300", "master-3:9300"]
# cluster.initial_master_nodes: 只在首次 bootstrap 使用,之后必须删除
text
1. 配置管理:把 initial_master_nodes 标成“仅首次”的一次性配置,
   集群建成后由配置仓库统一移除,避免每个新节点带着它启动。
2. 重建集群时的正确顺序:
   先把所有节点停干净,确认要沿用哪个 cluster UUID(数据目录里的 state),
   只在一个节点上引导,其余节点以 join 方式加入。
3. 误起第二个集群的处置:
   立即停掉第二个集群的节点(防止继续接受写入),
   把数据节点拉回原集群,而不是在原集群里改 name 去“合集群”。
4. 变更纪律:master 节点重启前检查 seed_hosts 可达,
   且不要一次重启多数派成员。

复盘要点:判据是“生产配置里不存在 cluster.initial_master_nodes”(除首次引导窗口),可用配置巡检脚本检查所有节点。同时把“集群 UUID 数量”做成一个监控项:稳态下只应该有一个。

二、master 与 data 混部 + 高基数聚合,选主反复 ​

现象:6 节点集群为了省机器全部 master,data 混部。白天写入正常,一到报表时段(大 terms 聚合 + 深分页)就出现 master 反复重选举、分片不断 reroute,查询间歇性 503。GC 日志里 master 节点老年代几乎打满。

排查:master 的职责是维护集群状态,它慢会导致全集群状态更新排队。

定位层取证据判定
节点角色GET _cat/nodes?v&h=name,node.role,master全部混部即风险敞口
master 候选数与分布候选是否 ≥3、是否跨机架/可用区2 候选挂 1 个就没有多数派
堆与 GC_nodes/stats/jvm、GC 日志master 堆吃满即选举/发布状态失败
状态更新任务GET _cluster/pending_tasks数量持续增长说明 master 处理不过来
资源竞争源聚合/写入是否在 master 同节点跑报表时段与选举时间重合即命中
分片数GET _cluster/health 的 active_shards 与节点数比值见下一场景
text
7.x+ 用多数派投票,候选人必须拿到多数票,少数派分区选不出 master;
所以“挂 1 个没事、挂 2 个没事”取决于候选数与分布,不取决于节点总数。

对症做法:

yaml
# 1. 3 个专用 master(master-only),跨机架/可用区
node.roles: [ master ]

# 2. 数据节点不再参与选举
node.roles: [ data_hot, data_content, ingest ]
text
3. 角色分离后 master 堆给足但不必巨大(例如 4~8GB,取决于集群规模),
   关键是 CPU 与磁盘 IO 不被数据节点抢走。
4. 报表类高基数聚合走独立“协调节点/预热节点”或专用 data 角色(data_content),
   与写入热节点隔离,避免相互拖慢。
5. 生产至少 3 个 master 候选:2 个候选节点挂一个就没有多数派,
   “有备份”是错觉。
6. 小集群混部是可以接受的取舍,但要写清约束:
   不在 master 节点跑重聚合、重写入,并把 pending_tasks 纳入告警。

复盘要点:判据是“_cluster/pending_tasks 与 master 重选举次数”两条曲线在报表时段保持平稳;另加一条演练结论:kill 任意一个 master,集群仍能在 election 窗口内选主且状态更新不中断。多数派丢失的真实代价是“无法选主 → 集群只读旧状态”,不是数据丢失。

三、每个索引 50 个主分片,集群 6000 个分片后写入开始排队 ​

现象:日志类索引按“分片越多越并行”的习惯建成 50 主 50 副,索引 60 个,集群总份额约 6000。三个月后:mapping 更新与分片分配开始变慢,写入批量报 429 es_rejected_execution_exception,_cluster/health 长期 yellow,一次节点重启要 40 分钟才能把分片搬回来。

排查:master 的状态发布与每分片的固定开销(堆、文件句柄、段)都随分片总数增长。

定位层取证据判定
分片总数GET _cluster/health 的 active_shards / unassigned_shards与节点数比值过大即过量
每节点分片数GET _cat/shards 按节点聚合计数经验上单节点 65GB 堆不宜承载过万分片(需按实测校准)
分片大小_cat/indices?v 的 pri.store.size / 每分片平均大小日志类单分片建议几十 GB 量级,过碎就是开销
分配解释GET _cluster/allocation/explain直接给出为什么分片没分配上
磁盘水位_cat/allocation 的 used_percent85%/90%/95% 三档会停分配/迁移/只读
写入拒绝_nodes/stats/thread_pool 的 write.queue/rejected队列堆积说明分片成为并行度的假象来源

对症做法:

bash
# 1. 用 allocation explain 定位未分配原因(磁盘水位 / 副本数 > 节点数 / 过滤规则)
curl -s "$ES/_cluster/allocation/explain?pretty" | head -40
curl -s "$ES/_cat/shards?v&h=index,shard,prirep,state,docs,store,node" | awk '{print $NF}' | sort | uniq -c | sort -rn | head

# 2. 新建索引按“数据量 ÷ 目标分片大小”定主分片数,副本 1 份足够
PUT /logs-2026.11
{ "settings": { "number_of_shards": 3, "number_of_replicas": 1,
                "index.routing.allocation.total_shards_per_node": 60 } }

# 3. 已经建碎的老索引:走 ILM 收进更少的大分片索引(reindex 后切换别名)
POST /logs-*/_reindex?dest=_aliases&refresh=true
text
4. 分片数不是并行度旋钮:写入并行度受“主分片数 × 节点数”与线程池限制,
   过多小分片反而放大段文件、mapping、master 状态发布成本。
5. 用 total_shards_per_node 与分片分配感知(shard allocation awareness)
   把副本分到不同机架,避免一次机架故障丢副本。
6. 时间序列索引交给 ILM/数据流:按滚动 + 强制合并 + 冷热分层控制总量,
   而不是靠“每个索引多分片”扛容量。

复盘要点:判据是“平均每分片大小”与“每节点分片数”两个数字要有预算,超出即禁止新建同类索引;unassigned_shards 长期非零要当成故障而不是背景噪声。另外要纠正一个常见误解:分片多不等于查询快——from+size 深分页时每个分片都要返回 from+size 条再归并,分片越多协调成本越高,见 ES 生产踩坑。

最小上手 ​

bash
# 三节点本地验证(每个节点用不同 node.name / 端口)
docker network create esnet
for i in 1 2 3; do
  docker run -d --name es$i --network esnet \
    -e node.name=es$i \
    -e cluster.name=demo \
    -e discovery.seed_hosts=es1,es2,es3 \
    -e cluster.initial_master_nodes=es1,es2,es3 \
    -e xpack.security.enabled=false \
    docker.elastic.co/elasticsearch/elasticsearch:8.15.0
done

curl "localhost:9200/_cluster/health?pretty"
curl "localhost:9200/_cat/nodes?v"

预期 status 为 green(至少一个副本可分配)或 yellow(副本未分配),_cat/nodes 中只有一个 * master。

生产动作 ​

  • master 候选:3 个奇数节点,跨机架;cluster.initial_master_nodes 只在首次启动配置。
  • 角色分离:master 不承担 data;大集群把 coordinating/ingest 独立。
  • 磁盘:75% 告警,85/90/95 是 ES 自己的水位;不要等只读才处理。
  • 分配排障:先 _cluster/allocation/explain,确认 decider 原因;reroute 只作为最后手段,且要带 dry_run。
  • 恢复限速:大集群恢复时关注 node_concurrent_recoveries 和 max_bytes_per_sec,避免恢复流量打满业务网络。
  • 监控:_cluster/health 的 unassigned_shards、relocating_shards、initializing_shards;_cat/nodes 的堆和磁盘。

边界 ​

  • 2 节点不等于高可用:挂一个就没有多数派,无法选主;至少 3 个 master 候选。
  • 副本不是备份:副本在同一集群,误删/逻辑损坏会一起丢;备份用 snapshot。
  • 手动 reroute 有风险:绕过 decider 可能把分片放到不满足机架/磁盘条件的节点。
  • 关闭水位保护不可取:只会把「分配失败」推迟成「磁盘写满、索引只读」。
  • 分片数不是越多越好:master 管理集群状态的开销随分片数增长,单节点分片数受堆大小约束。

讲清楚 ​

ES 8.x 的集群管理就两件事:master 和分片分配。master 由多数派选举,7.x 起用投票配置,少数派分区选不出 master,所以经典脑裂基本消失;真正的坑是 master 候选少于 3 个,或者 cluster.initial_master_nodes 没在集群形成后移除。分片分配由 decider 链决定,磁盘水位、机架感知、同节点主副本、过滤规则,任何一个不满足都会拒绝分配。

排障顺序是 _cluster/health → _cat/shards → _cluster/allocation/explain,先看清原因再动手。磁盘 85/90/95 三档水位分别对应「不再分配」「尝试迁走」「索引只读」,75% 就该告警。副本不是备份,手动 reroute 也不是常规操作。

把一门技术讲透到既懂也会用