大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
集群架构:节点、分片与高可用
这一课你将能做到
- 说出 master、data、coordinating、ingest 等节点角色各自的职责
- 画图解释主分片(primary shard)与副本分片(replica shard)如何协作,以及一次写入在分片间的流转过程
- 解释脑裂(split-brain)的成因,以及 7.x 之后集群投票机制如何防住它;能对本站题库索引规划合理的分片数
为什么要学
单机 Lucene 的容量和吞吐有天花板:一块盘装不下、一个 CPU 算不过来、机器宕了服务就没了。ES 在 Lucene 外面套了一层分布式壳——把一个索引切成多个分片散布到多台机器,每个分片再放副本做冗余。理解这套结构,你才看得懂「为什么我建索引时指定 5 个主分片」「为什么一台机器挂了搜索没断」「为什么集群状态变红/变黄」。
人话:分片就像把一本厚题库拆成 5 本分册放在不同书架上,任何一个书架只扛五分之一的量;副本就是每本分册再复印一份放到别的书架,原书架塌了复印件顶上。本站当前规模一台机器就装得下,但知识必须按生产标准学——面试和工作中,分片规划不当(过度分片)是 ES 最常见的性能事故来源。
先建立图像
一个 3 节点集群、索引设 3 个主分片、每分片 1 个副本的布局:
节点A 节点B 节点C
┌──────────┐ ┌──────────┐ ┌──────────┐
│ P0 │ │ P1 │ │ P2 │
│ R1 │ │ R2 │ │ R0 │
└──────────┘ └──────────┘ └──────────┘
P = 主分片(primary) R = 副本分片(replica),R0 是 P0 的副本
规则:主分片和它的副本永不在同一节点(否则宕机一起没)
一次写入的流转(文档 ID 为 1,路由到 P2):
客户端 → 任意节点(协调节点)
→ 按 hash(_id) 算出该文档属于 P2,转发给节点B
→ P2 写入成功后,同步转发给节点C 上的 R2
→ P2 与 R2 都成功后,协调节点向客户端返回成功
一次搜索的流转则不同:协调节点把请求发给每个分片(主或副本任选其一),各分片返回本地 Top N,协调节点汇总归并出全局排序——这叫 scatter-gather(分散-汇聚)。
集群健康状态三色:
GREEN 所有主分片和副本分片都正常
YELLOW 主分片全正常,但有副本分片未分配(单节点集群必然 YELLOW:副本没地方放)
RED 有主分片未分配,部分数据无法读写
跟着做
步骤 1:查看集群健康与节点信息
# 健康状态
curl -u elastic:abcd1234 "http://localhost:9200/_cluster/health?pretty"
# 节点列表及角色,node.role 字段:m=master, d=data, i=ingest 等
curl -u elastic:abcd1234 "http://localhost:9200/_cat/nodes?v"
单节点环境下你会看到 status: yellow,原因就是上节说的:主分片有了,但 1 个副本无处安放。
步骤 2:建索引时指定分片数
curl -u elastic:abcd1234 -X PUT "http://localhost:9200/questions" -H "Content-Type: application/json" -d'
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}'
关键规则:主分片数在索引创建后不能修改(只能通过 split/拆分或 reindex 重建),副本数可以随时调整。所以分片数是需要提前想的决策。
查看分片分布:
curl -u elastic:abcd1234 "http://localhost:9200/_cat/shards/questions?v"
步骤 3:临时调副本数,观察健康状态变化
curl -u elastic:abcd1234 -X PUT "http://localhost:9200/questions/_settings" -H "Content-Type: application/json" -d'
{
"number_of_replicas": 0
}'
单节点集群立刻变成 GREEN——没有副本,主分片全活即绿。学习环境为了清爽常这么设,但生产环境至少 1 个副本。改回 1 后状态又回到 YELLOW。
步骤 4:用 Docker Compose 起一个三节点集群(选做)
新建 docker-compose.yml:
services:
es01:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
environment:
- node.name=es01
- cluster.name=es-lab
- discovery.seed_hosts=es02,es03
- cluster.initial_master_nodes=es01,es02,es03
- ELASTIC_PASSWORD=abcd1234
- xpack.security.http.ssl.enabled=false
ports: ["9200:9200"]
es02:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
environment:
- node.name=es02
- cluster.name=es-lab
- discovery.seed_hosts=es01,es03
- cluster.initial_master_nodes=es01,es02,es03
- ELASTIC_PASSWORD=abcd1234
- xpack.security.http.ssl.enabled=false
es03:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
environment:
- node.name=es03
- cluster.name=es-lab
- discovery.seed_hosts=es01,es02
- cluster.initial_master_nodes=es01,es02,es03
- ELASTIC_PASSWORD=abcd1234
- xpack.security.http.ssl.enabled=false
启动后再跑 _cat/shards?v,就能看到主分片和副本分散在三个节点上。discovery.seed_hosts 是启动时互相发现的地址列表;cluster.initial_master_nodes 只在首次引导集群时使用,确定第一批有 master 资格的节点。
讲清楚要点
- 节点角色可以兼任也可以分离:小集群里每个节点什么角色都当(默认);大集群会拆分——专用 master 节点只维护集群元数据(索引设置、分片位置),不存数据不接查询,3 个保证高可用;data 节点负责存数据和执行查询;coordinating 节点只负责接收请求、转发、归并结果,扛流量。
- 主分片定容量上限,副本定可用性和读吞吐:文档按
shard = hash(_routing) % number_of_primary_shards路由,所以主分片数定死不能改(改了旧文档的路由全乱)。加副本可以分散搜索压力并在节点故障时顶上,但副本不增加容量。 - 分片不是越多越好:每个分片都是一个 Lucene 实例,占文件句柄、堆内存和集群元数据;分片数过多(mapping/分片爆炸)会让 master 维护元数据变慢、查询 fan-out 到大量分片反而更慢。官方经验:单分片大小控制在 10GB–50GB,每 GB 堆内存对应的分片总数不超过 20 个是常见参考线。
- 写一致性与故障转移:写操作默认等主分片写成功即可返回(
wait_for_active_shards可配置等待多少个活跃分片副本)。节点宕机时,master 把该节点上的主分片提升它在其他节点上的副本为主,再在别处补建副本,整个过程自动。 - 8.x 的数据分层(data tiers):data_hot(高频写读)、data_warm(只读少查)、data_cold(只读、更少副本)、data_frozen(可搜索快照),配合 ILM 自动迁移,运维课会展开。
你可能会懵的地方
-
脑裂到底是怎么发生的? 老版本(7.0 以前)靠
discovery.zen.minimum_master_nodes配置,配错时网络分区两边可能各自选出 master,两个 master 同时接受写入,网络恢复后数据冲突无法合并。7.0 起正式移除该配置,改为基于 Raft 思想的投票机制:一个 term 内多数派投票才能选出 master,集群自动维护「投票配置」,少数派分区不可能选出 master,脑裂从机制上被根除。 -
主分片数为什么不能改,副本数却能改? 文档路由公式对主分片数取模,改了主分片数,老文档算出来的归属和实际位置全对不上。副本只是主分片的拷贝,多一份少一份不改变文档归属,所以随时可调。
-
集群 YELLOW 要不要紧? 看情况。单节点学习环境 YELLOW 正常;多节点生产环境 YELLOW 说明有副本没分配,可能是某节点宕机、磁盘超水位线,需要查
_cluster/allocation/explain。YELLOW 期间读写正常,但已经没有冗余,再坏一台可能就 RED。 -
协调节点是一种特殊节点吗? 每个节点默认都是协调节点——任何节点收到请求都会承担转发和归并。也可以配出只做协调、不当 master 不存数据的专用节点,放在负载均衡后面承接外部流量。
-
分片大小按什么估? 先估总量:题库 5 万题,每题含解析约 5KB,原始数据 250MB,ES 索引含倒排约放大到 0.5–1GB。远不到单分片 10GB 的下限,所以本站 1 个主分片加 1 个副本就够。容量增长后再用 split 或按新索引滚动,不要一开始就给几十个分片。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 索引默认 1 分片就完事,不评估增长 | 单分片无法靠加节点拆分扩展 | 按 10–50GB/分片估算,留合理余量 |
| 一上来 30 个分片「以后扩容方便」 | 元数据膨胀、查询 fan-out 变慢 | 分片宁少勿多,配合 rollover 增长 |
| 生产只设 1 个 master 节点 | master 宕机集群无法管理 | master 候选至少 3 个 |
| 副本设 0 省空间 | 节点故障即丢数据 | 生产至少 1 副本,重要索引 2 |
| 主分片和副本落同一物理机 | 物理机宕机双杀 | 分片感知(shard awareness)感知机架/可用区 |
卡点急救
- 集群长期 YELLOW:执行
GET _cluster/allocation/explain,返回会直接写明未分配原因:节点数不足、磁盘水位超 85%(flood_stage 还会触发只读)、分配规则过滤。按原因处理后通常自动恢复。 - 节点加入不了集群:检查三台机器的
cluster.name是否一致(不一致视为不同集群)、9300 端口(集群内部通信,注意是 9300 不是 9200)是否互通、discovery.seed_hosts地址是否正确。 - 集群 RED 有主分片未分配:先确认 data 节点是否存活;磁盘满导致的先清空间解除只读;实在无法恢复的分片,确认有快照后再考虑删除损坏索引,切勿在无备份时直接删。
面试常问
-
主分片和副本分片的区别?索引写入流程说一下。 主分片承载文档的权威写入,副本是主分片的拷贝,主从数据同步且不与主分片在同一节点。写入流程:客户端请求到协调节点,协调节点按路由公式算出目标主分片并转发;主分片本地写入成功后并行同步到副本;全部成功后返回。搜索时协调节点在每个分片组的主/副本中轮询选择,再 scatter-gather 归并。
-
ES 怎么防止脑裂? 7.0 之前需要手动配置 minimum_master_nodes 为 master 候选数/2+1,配错就可能脑裂;之后重构为协调子系统,master 由多数派投票产生并维护正式投票配置,任何分区中的少数派无法当选 master,脑裂被从协议层面消除,不再需要手动配置。
-
分片数怎么规划? 先估数据总量和增长,按单分片 10–50GB 的经验区间反推主分片数;主分片数创建后不可改,宁少勿多;副本数按可用性和读吞吐定;还要控制分片总量与堆内存比例,避免过度分片。本站题库总量不到 1GB,1 主 1 副即可。
自测三问
- 文档 ID 为 9527,你能说清它是怎么被路由到某个主分片的吗?这解释了什么限制?
- 节点 B 宕机后,上面的 P1 和 R2 分别发生什么?集群如何自愈?
- 为什么说分片不是越多越好?除了占内存,还会怎么拖慢查询?
小结一句话
ES 用「主分片水平拆分 + 副本分片冗余 + 多数派选主」三件事同时获得容量扩展、高可用和防脑裂,而分片规划的核心纪律是:主分片数不可改、宁少勿多。
延伸
- Nodes and shards
- Cluster health API
- 下一课:Mapping 深入——为什么字段类型决定了「能怎么搜」,text 与 keyword 选错会发生什么。