数据库基础体系 · 第 42/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。
Elasticsearch 分片与集群:路由、副本、恢复、容量和故障诊断
Elasticsearch 的索引不是一个不可再分的文件,而是由多个 主分片(primary shard) 组成;每个主分片还可以有一个或多个 副本分片(replica shard)。多个节点通过集群协调、分片分配和请求路由共同提供数据写入、查询、故障转移与扩展能力。
理解这些机制,需要先区分四个层次:
- 索引:面向用户的逻辑数据集合。
- 分片:索引数据的物理分区,也是搜索和恢复的基本单位。
- 节点:运行 Elasticsearch 进程并承载分片、协调请求或维护集群状态的实例。
- 集群:由节点共同维护的集群状态、索引元数据和分片布局。
本文讨论 Elasticsearch 官方稳定版本中通用的语义。具体默认值、节点角色和配置项可能随版本变化,生产环境应以对应版本的官方文档为准。
一、为什么需要分片和集群
假设一个索引有 1 TB 数据,而单台节点的磁盘、内存或 CPU 无法承载全部数据。可以将索引拆成多个主分片:
logs
├── primary shard 0
├── primary shard 1
├── primary shard 2
└── primary shard 3
这些分片可以分布到不同节点:
node-a: shard 0, shard 2
node-b: shard 1
node-c: shard 3
这样做带来三类能力:
- 容量扩展:数据可以分布到多个节点和磁盘。
- 并行查询:不同分片可以并行执行搜索和聚合。
- 故障隔离与恢复:主分片损坏或节点离线时,可以从副本分片恢复服务。
但分片不是免费的。一个搜索请求可能需要访问多个分片;分片越多,协调、文件句柄、内存、集群状态和恢复任务越多。因此“分片越多越好”是错误的,分片数量应由数据容量、写入并发、查询模式和恢复要求共同决定。
二、集群中的节点、集群状态与主节点
2.1 节点角色
当前 Elasticsearch 通常通过节点角色决定节点承担的职责。常见角色包括:
master:参与选举并维护集群状态。data_content、data_hot、data_warm、data_cold、data_frozen:承载不同数据生命周期阶段的分片。ingest:执行 Ingest Pipeline。remote_cluster_client:参与跨集群连接。ml、transform等:承担相应功能。
节点也可以承担多个角色。没有专门角色的节点通常可作为协调节点,接收请求、路由到数据节点并合并结果。
在中大型集群中,常见部署方式是:
3 个专用 master 节点
多个 data 节点
可选的 ingest 或协调节点
专用 master 节点不一定承载数据,但它们必须能够稳定保存集群状态并相互通信。
2.2 集群状态
集群状态包含但不限于:
- 节点列表;
- 索引和数据流元数据;
- 分片数量和分配结果;
- 索引设置与映射;
- 路由表;
- 集群级配置。
主节点负责发布集群状态。其他节点收到状态后,才能知道某个索引有哪些分片、分片当前在哪个节点以及哪些节点可以处理请求。
因此,主节点故障不等于数据立即不可用。只要剩余节点能选出新的主节点,数据节点仍可能继续提供服务;但如果集群无法形成法定多数,集群状态通常无法继续更新,索引创建、分片重新分配等操作会受到影响。
2.3 为什么通常需要三个 master-eligible 节点
集群选主依赖多数派。假设有三个可参与选主的节点:
节点 A、B、C
法定多数 = 2
A 离线后,B 和 C 仍可形成多数。
如果只有两个节点:
节点 A、B
法定多数 = 2
任意一个节点离线,剩下一个节点都不能安全地自动确认自己拥有集群控制权。这是为了避免网络分区时出现两个集群同时接受写入,即“脑裂”。
cluster.initial_master_nodes 只用于新集群的首次引导。集群已经形成后,不应把它当成每次重启都需要保留的普通配置;误用或把不同集群的节点混在一起,可能导致错误的集群引导。
三、主分片、副本分片和分片状态
3.1 主分片与副本分片
创建索引时可以指定主分片和副本数量:
curl -X PUT 'http://localhost:9200/orders' \
-H 'Content-Type: application/json' \
-d '{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}'
该索引的逻辑分片数为:
主分片数 = 3
每个主分片的副本数 = 1
物理分片总数 = 3 × (1 + 1) = 6
副本不是“整个索引的一份备份”,而是每个主分片的副本。例如:
shard 0:
primary -> node-a
replica -> node-b
shard 1:
primary -> node-b
replica -> node-c
shard 2:
primary -> node-c
replica -> node-a
同一个分片的主分片和副本分片通常不会分配到同一个节点。否则节点故障时,主副本会同时丢失。但这不是把副本放在不同机房的自动保证;若需要机架或可用区隔离,应使用节点属性和 allocation awareness 或 forced awareness。
3.2 主分片数与副本数的可变性
副本数可以在线调整:
curl -X PUT 'http://localhost:9200/orders/_settings' \
-H 'Content-Type: application/json' \
-d '{
"index": {
"number_of_replicas": 2
}
}'
这会要求集群创建或删除副本分片,并触发分配和恢复。
主分片数量通常不能像副本数一样直接修改。原因是文档的默认路由依赖主分片数量:
目标分片 = hash(routing_value) mod number_of_primary_shards
如果主分片数突然从 3 变成 6,同一个 routing value 的目标分片可能改变,原有数据路由表就不再一致。
需要改变主分片数量时,常见方式包括:
- 新建目标索引并使用
_reindex; - 使用 shrink 将索引缩小;
- 使用 split 将索引拆分;
- 通过 ILM 的 rollover 创建后续索引。
这些操作有不同的前置条件和数据移动成本,不能把它们等价为“在线修改一个整数”。
3.3 分片状态
使用 _cat/shards 可以查看分片状态:
curl 'http://localhost:9200/_cat/shards/orders?v'
典型结果类似:
index shard prirep state docs store ip node
orders 0 p STARTED ... ... 10.0.0.1 node-a
orders 0 r STARTED ... ... 10.0.0.2 node-b
orders 1 p STARTED ... ... 10.0.0.2 node-b
orders 1 r STARTED ... ... 10.0.0.3 node-c
常见状态包括:
STARTED:分片已分配并可服务请求。INITIALIZING:正在创建或恢复。RELOCATING:正在从一个节点迁移到另一个节点。UNASSIGNED:没有分配到节点。
UNASSIGNED 不一定立即表示数据损坏,也可能是:
- 没有满足副本的节点;
- 磁盘水位限制阻止分配;
- allocation rule 不允许分配;
- 节点刚重启,恢复尚未完成;
- 主分片丢失,且没有可用副本。
四、路由:文档写入究竟进入哪个分片
4.1 默认文档路由
写入文档时,Elasticsearch 需要决定它属于哪个主分片。默认使用文档 _id 作为 routing value:
routing_value = _id
target_shard = hash(routing_value) mod primary_shard_count
例如,假设索引有 4 个主分片:
_id = "user-100"
hash("user-100") mod 4 = 2
该文档会写入主分片 2,而不是由客户端随机选择。
这里的关键点是:客户端不应该自行实现 Elasticsearch 的哈希算法来推测分片。算法细节和兼容行为属于 Elasticsearch 的实现和 API 语义,应用应通过 API 指定 routing 或让 Elasticsearch 完成路由。
4.2 自定义 routing
对于具有明显租户或用户边界的数据,可以指定 routing:
curl -X PUT 'http://localhost:9200/orders/_doc/order-001?routing=tenant-a' \
-H 'Content-Type: application/json' \
-d '{
"tenant_id": "tenant-a",
"amount": 120
}'
此时决定分片的是:
hash("tenant-a") mod primary_shard_count
而不是 _id。
查询时必须使用相同的 routing,才能将请求限制到正确的分片:
curl -X GET 'http://localhost:9200/orders/_search?routing=tenant-a' \
-H 'Content-Type: application/json' \
-d '{
"query": {
"term": {
"tenant_id": "tenant-a"
}
}
}'
如果写入时使用了自定义 routing,而查询时不提供 routing,查询可能无法找到文档,因为它默认只按普通方式向所有相关分片发起搜索;对于某些按 routing 定位的读写场景,错误或缺失 routing 会直接导致找不到文档或访问错误分片。
routing 的收益
如果一个租户的所有文档都使用租户 ID 路由,那么只查询该租户时,可以减少搜索分片数:
没有 routing:访问 0、1、2、3 共 4 个分片
使用 routing:只访问 hash(tenant-a) 对应的 1 个分片
routing 的风险
自定义 routing 可能产生热点:
tenant-a -> shard 0
tenant-b -> shard 0
tenant-c -> shard 0
如果某个大租户远大于其他租户,单个分片会积累更多数据和请求,即使整个集群看起来还有大量空闲容量。
因此 routing 的本质是一个取舍:
- 更少的查询 fan-out;
- 更强的数据局部性;
- 更高的热点风险;
- 应用必须在每次相关读写中正确传递 routing。
4.3 写入请求的主副本路径
一个普通写入的逻辑过程如下:
客户端
|
v
协调节点计算目标主分片
|
v
目标节点上的 primary
|
+--> 写入本地 Lucene 索引和 translog
|
+--> 转发给 replica shard
|
v
根据 wait_for_active_shards 返回成功或失败
写入首先由主分片处理。主分片将操作复制到副本分片,副本成功响应的数量会影响请求何时返回。
wait_for_active_shards 可以限制写入需要等待多少个活动分片。例如:
curl -X PUT 'http://localhost:9200/orders/_doc/order-002?wait_for_active_shards=2' \
-H 'Content-Type: application/json' \
-d '{
"tenant_id": "tenant-a",
"amount": 80
}'
这里的 2 表示主分片和至少一个副本都处于活动状态时才满足条件。它不是事务提交,也不代表数据已经跨机房持久化。
Elasticsearch 的 Bulk 请求也不是数据库事务:Bulk 中某一条失败,不会自动回滚其他已成功的条目。应用必须逐条检查响应中的 items。
五、查询路由与分布式搜索
5.1 Scatter-Gather
一次查询通常经过两个阶段:
Scatter:
协调节点将查询发送到每个目标分片
Gather:
各分片返回局部结果,协调节点合并、排序、截取
例如一个索引有 5 个主分片、每个主分片 1 个副本。一次普通搜索不会同时查询 10 个物理分片,而是为每个逻辑分片选择主分片或副本中的一个副本:
shard 0 -> primary
shard 1 -> replica
shard 2 -> primary
shard 3 -> replica
shard 4 -> primary
具体选择会受到节点负载、请求 preference 和集群状态影响。
如果没有 routing,查询通常需要访问索引中的所有相关逻辑分片。分片数量越多,协调阶段越复杂,尤其是:
from + size较大的分页;- 高基数聚合;
- 多索引搜索;
- 大量并发查询。
size 为 100 时,每个分片可能需要返回候选结果,再由协调节点合并;这不是“只从整个集群取 100 条”的单节点操作。
5.2 深分页为什么昂贵
对于按相关性或排序字段排序的搜索,协调节点要从每个分片获取候选结果,再做全局排序。
假设有 20 个分片,请求:
{
"from": 10000,
"size": 100
}
每个分片可能需要准备至少约 from + size = 10100 个候选位置,协调节点再合并它们。实际执行还会受到排序字段、查询类型和优化策略影响,但其成本随分片数和分页深度增长。
连续遍历大量结果时,应优先考虑:
search_after;- Point in Time(PIT)配合
search_after; - 适合离线处理的
scroll。
这些属于查询层面的机制,不能用增加副本或增加节点解决。
六、副本的作用、限制和一致性边界
6.1 副本提供什么
副本主要用于:
- 故障转移:主分片所在节点故障时,副本可被提升为新的主分片。
- 查询并行:多个副本可以承接查询。
- 读吞吐扩展:在查询负载合适时,增加副本可以增加可用的查询执行位置。
6.2 副本不提供什么
副本不是以下机制的替代品:
- 不是长期备份;
- 不是跨集群灾难恢复;
- 不是防止误删除的历史版本;
- 不是事务提交日志;
- 不能保证主副本一定位于不同机房。
如果应用误执行删除,副本通常也会复制这个删除。要恢复误删数据,需要快照或其他独立备份。
快照存储的是索引数据和元数据的备份视图,适合灾难恢复、迁移和长期保留;副本则主要用于在线可用性。二者解决的问题不同。
6.3 副本数量与可用性
假设:
3 个主分片
1 个副本
正常情况下每个逻辑分片有两个物理副本。一个节点故障后,只要每个主分片至少还有一个可用副本,集群可以通过提升副本继续提供完整数据服务。
但如果副本数为 0:
- 主分片所在节点故障;
- 没有可提升的副本;
- 对应主分片会变成
UNASSIGNED; - 集群健康可能变为
red; - 该分片上的数据和请求不可用,除非从快照或其他来源恢复。
七、故障转移与分片恢复
7.1 节点故障时发生什么
以 shard 0 为例:
故障前:
node-a: shard 0 primary
node-b: shard 0 replica
当 node-a 离线后,集群会经历大致过程:
1. 主节点检测到 node-a 不再属于当前集群
2. shard 0 primary 被标记为不可用
3. node-b 上的 replica 被提升为新的 primary
4. 如果还有其他副本,集群尝试补建新的 replica
5. 集群状态从 red 或 pending 状态恢复
检测速度受节点间探活和故障检测配置影响,不能简单理解为“网络断开后立即完成切换”。
如果原节点恢复上线,Elasticsearch 不一定直接把它当作新的主分片继续使用,而是根据当前集群状态决定是否进行 peer recovery、重建副本或重新分配。
7.2 恢复类型
常见恢复来源包括:
- Peer recovery:从同一集群中的其他分片复制数据。
- Replica promotion:将现有副本提升为主分片。
- Gateway recovery:节点重启或集群重建时,从本地已存在的数据恢复。
- Snapshot recovery:从快照仓库恢复索引。
恢复过程通常涉及:
- 分配目标节点;
- 检查分配决策;
- 创建分片目录;
- 复制 Lucene segment;
- 同步 translog 或执行必要的操作重放;
- 校验并切换为可服务状态;
- 重新建立副本或继续迁移。
恢复期间,分片可能处于 INITIALIZING 或 RELOCATING。大量恢复会同时消耗:
- 磁盘读写;
- 网络带宽;
- CPU;
- 堆内存和文件句柄;
- 正常查询与写入的资源。
7.3 sequence number 与恢复的一致性
Elasticsearch 使用 sequence number、checkpoint 等机制跟踪分片操作进度。副本并不是简单地“定时复制一个完整目录”,而是由主分片协调操作复制和确认。
这使得恢复时可以根据已有数据判断哪些操作已经存在、哪些操作需要补齐。但这不应被理解为任意情况下都能无限保留增量;如果副本落后时间过长、历史操作不可用或数据不满足安全条件,可能需要更大范围的文件复制,甚至从快照恢复。
写入的持久化还受 translog durability 配置影响:
request:每次请求按持久化语义处理后再确认,通常更安全但写入成本更高。async:按周期异步刷写 translog,节点在周期内突然崩溃时可能丢失尚未持久化的最近操作。
这仍然不是跨节点事务。副本确认、磁盘持久化和快照恢复分别对应不同层次的可靠性。
八、如何诊断分片没有分配
8.1 先看集群健康
curl 'http://localhost:9200/_cluster/health?pretty'
典型结果:
{
"cluster_name": "prod-es",
"status": "yellow",
"number_of_nodes": 3,
"active_primary_shards": 12,
"active_shards": 20,
"unassigned_shards": 4
}
状态含义:
green:主分片和副本都已分配。yellow:主分片已分配,但至少有副本未分配。red:至少有一个主分片未分配。
yellow 不等于数据已经丢失,但表示副本保护不完整。red 需要优先确认是否影响具体索引和请求。
查看未分配分片:
curl 'http://localhost:9200/_cat/shards?v&h=index,shard,prirep,state,unassigned.reason,node'
8.2 使用 allocation explain
不要只根据 _cat/shards 猜原因。对具体分片执行:
curl -X GET 'http://localhost:9200/_cluster/allocation/explain' \
-H 'Content-Type: application/json' \
-d '{
"index": "orders",
"shard": 0,
"primary": false
}'
返回结果会说明:
- 当前分片为何未分配;
- 候选节点;
- 每个节点的 allocation decider;
- 哪个决策是
NO; - 是否需要等待;
- 是否受到磁盘、过滤规则、同分片限制或节点角色影响。
常见决策原因包括:
磁盘水位
Elasticsearch 使用磁盘水位避免把分片分配到磁盘即将耗尽的节点。具体默认阈值和版本行为应以当前版本为准。达到 flood-stage 时,相关索引可能被设置为:
index.blocks.read_only_allow_delete = true
这会表现为写入失败。修复磁盘空间后,还要检查并按需解除索引块:
curl -X PUT 'http://localhost:9200/orders/_settings' \
-H 'Content-Type: application/json' \
-d '{
"index.blocks.read_only_allow_delete": null
}'
解除之前必须确认磁盘水位问题已经解决,否则很快会再次进入保护状态。
分配过滤和 awareness
例如配置了:
index.routing.allocation.require.zone = zone-a
但集群中没有符合条件的节点,分片就不会分配。
又或者副本和主分片被要求分布到不同可用区,但当前节点数量不足,也会导致副本 UNASSIGNED。
同分片约束
同一个逻辑分片的 primary 和 replica 不应放到同一节点。若只有一个符合条件的数据节点,而副本数大于 0,副本会保持未分配。
节点角色不匹配
索引要求某类 data 节点,但当前集群没有满足角色条件的节点,分配也会被拒绝。
8.3 诊断节点和分片分布
curl 'http://localhost:9200/_cat/nodes?v&h=name,ip,roles,heap.percent,ram.percent,cpu,load_1m,disk.used_percent'
curl 'http://localhost:9200/_cat/allocation?v'
这些命令可以帮助判断:
- 某个节点是否承载了明显更多分片;
- 磁盘是否接近水位;
- JVM heap 是否长期过高;
- 节点是否实际具备所需角色;
- 分片是否正在大量迁移。
不要只看平均值。分片和数据通常并不均匀,单个热点节点可能已经达到瓶颈,而整个集群的平均 CPU 仍然很低。
九、容量规划:不能只按磁盘总量计算
9.1 物理容量公式
最基础的物理分片数量是:
物理分片数 = 索引数 × 主分片数 × (1 + 副本数)
例如:
100 个日索引
每个索引 5 个主分片
每个主分片 1 个副本
则物理分片数为:
100 × 5 × 2 = 1000 个
如果每个主分片平均 20 GB,数据本体大致为:
100 × 5 × 20 GB = 10 TB
加上一个副本后,分片数据本体约为:
10 TB × 2 = 20 TB
但生产容量不能只按这个数字购买。还要考虑:
- Lucene segment 和合并临时空间;
- translog;
- 索引元数据;
- doc values、倒排索引和 stored fields 的实际膨胀;
- 快照或导出空间;
- 节点故障后的重分配空间;
- 磁盘水位保留空间;
- 未来增长;
- 热数据、温数据和冷数据的不同存储策略。
更接近实际的估算可以写成:
所需原始容量
≈ 数据源容量
× 索引结构膨胀系数
× (1 + 副本数)
+ 恢复和合并余量
+ 未来增长容量
其中“索引结构膨胀系数”没有通用固定值,必须用目标映射、字段类型、压缩方式和真实数据压测得到。
9.2 分片大小没有万能阈值
分片过小会产生:
- 更多分片元数据;
- 更多搜索 fan-out;
- 更多 segment;
- 更多恢复任务;
- 更多文件句柄和调度开销。
分片过大则会产生:
- 恢复耗时长;
- 节点故障后数据迁移慢;
- 单个分片成为查询或写入热点;
- shrink、迁移和快照恢复粒度过粗。
因此不能脱离版本、硬件、查询、写入和恢复目标,给出一个适用于所有集群的固定“最佳分片大小”。更可靠的方法是:
- 用真实或接近真实的数据创建目标索引;
- 观察单分片的写入吞吐和查询延迟;
- 模拟节点故障;
- 测量分片恢复速度;
- 检查在正常业务负载下磁盘和堆内存余量;
- 再确定模板和 rollover 策略。
9.3 分片数量和 JVM 堆
分片会占用节点资源,尤其是:
- 分片元数据;
- Lucene segment 相关结构;
- 文件句柄;
- 查询缓存和请求上下文;
- translog 和恢复状态。
“每 GB 堆支持固定数量分片”的经验法则只能作为粗略预警,不能代替压测和监控。实际问题往往不是分片数量本身,而是大量小分片叠加了高并发查询、聚合、segment 数量或不合理的索引生命周期。
十、分片分配、迁移与运维风险
10.1 临时关闭分配
在节点维护时,可以暂时关闭分片分配,避免节点短暂重启触发大量迁移:
curl -X PUT 'http://localhost:9200/_cluster/settings' \
-H 'Content-Type: application/json' \
-d '{
"persistent": {
"cluster.routing.allocation.enable": "primaries"
}
}'
这个配置允许主分片分配,但禁止普通副本分配。维护完成后必须恢复:
curl -X PUT 'http://localhost:9200/_cluster/settings' \
-H 'Content-Type: application/json' \
-d '{
"persistent": {
"cluster.routing.allocation.enable": null
}
}'
如果忘记恢复,集群可能长期处于 yellow,新副本也不会建立。
配置分配过滤、节点属性和 awareness 时,应优先使用集群设置或索引设置,并在变更后通过 _cluster/allocation/explain 验证实际决策,而不是只检查配置是否被接受。
10.2 手工强制分配的危险
当主分片永久丢失且没有副本时,可能看到需要手工分配的建议。某些强制分配操作需要显式接受 accept_data_loss。
这类操作的含义不是“修复 Elasticsearch”,而是:
告诉 Elasticsearch:即使无法证明已有完整数据,也允许把一个本地分片目录作为主分片使用。
可能后果包括:
- 丢失尚未存在于该目录的数据;
- 产生数据缺口;
- 后续副本复制错误数据;
- 让应用误以为数据完整。
执行前应优先尝试:
- 确认节点只是暂时离线;
- 查找可用副本;
- 查看最近快照;
- 检查磁盘和数据目录;
- 评估是否可以接受数据丢失;
- 恢复后通过业务校验检查数据完整性。
10.3 滚动重启的边界
滚动重启的目标是始终保留足够的 master 节点和数据副本,使集群继续工作。一般流程是:
1. 确认集群有健康余量
2. 暂停或限制不必要的分片迁移
3. 一次只维护一个节点
4. 等待节点重新加入集群
5. 等待必要的分片恢复完成
6. 检查 master、数据和副本状态
7. 再处理下一个节点
如果副本数为 0,维护一个承载主分片的节点可能直接导致 red。如果有副本但所有副本都集中在同一故障域,机房级故障仍可能造成数据不可用。
十一、恢复速度与可用性的取舍
恢复涉及两个不同目标:
- 尽快恢复服务:优先提升已有副本,减少等待。
- 尽快恢复冗余:尽快重建缺失副本,避免再次故障时没有保护。
集群会受到恢复并发、网络和磁盘吞吐限制。可以查看恢复状态:
curl 'http://localhost:9200/_recovery?active_only=true&pretty'
也可以查看节点和分片级统计:
curl 'http://localhost:9200/_cat/recovery/orders?v'
输出中的常见信息包括:
stage:恢复阶段;files:文件总数、已恢复数量;bytes:字节总量、已恢复数量;time:恢复耗时;source_host和target_host:恢复来源与目标。
恢复期间,如果同时把并发调得过高,可能造成:
- 正常查询延迟上升;
- 写入延迟增加;
- 磁盘队列过长;
- 网络拥塞;
- 节点因资源压力再次离线。
因此恢复设置不是越大越好。应根据磁盘类型、网络带宽、业务低峰期和故障演练结果调整,并观察业务指标而不是只追求恢复百分比增长。
十二、常见故障现象与诊断路径
12.1 集群为 yellow
可能原因:
- 副本数大于可用数据节点数;
- 主副本被要求分布到不同 zone,但 zone 不足;
- 某节点离线;
- 磁盘水位阻止副本分配;
- 索引 allocation filter 不满足。
诊断顺序:
curl 'http://localhost:9200/_cluster/health?level=indices&pretty'
curl 'http://localhost:9200/_cat/shards?v'
curl -X GET 'http://localhost:9200/_cluster/allocation/explain' \
-H 'Content-Type: application/json' \
-d '{
"index": "orders",
"shard": 0,
"primary": false
}'
不要简单地把副本数改成 0 来“修复” yellow。这样只是删除保护层,只有在明确接受风险或临时降低资源压力时才应使用。
12.2 集群为 red
重点确认是哪些主分片未分配:
curl 'http://localhost:9200/_cat/shards?v&h=index,shard,prirep,state,unassigned.reason,node'
然后对主分片解释:
curl -X GET 'http://localhost:9200/_cluster/allocation/explain' \
-H 'Content-Type: application/json' \
-d '{
"index": "orders",
"shard": 0,
"primary": true
}'
不同原因的处理完全不同:
- 节点暂时离线:恢复节点或等待故障转移;
- 磁盘满:释放空间并确认数据目录完整;
- 分配规则冲突:修正规则;
- 主副本永久丢失:从其他副本或快照恢复;
- 集群状态未更新:先诊断 master 选举和节点通信。
在没有确认数据来源之前,不要直接执行带数据丢失风险的强制分配。
12.3 写入失败但集群仍是 green
green 只表示分片分配完整,不代表所有写入都成功。还应检查:
- 索引是否被
read_only_allow_delete阻塞; - 请求是否使用了错误的 routing;
- 映射冲突;
- Bulk 中是否只有部分条目失败;
wait_for_active_shards是否无法满足;- 磁盘是否刚刚越过 flood-stage;
- 应用是否遇到版本冲突。
检查索引设置:
curl 'http://localhost:9200/orders/_settings?flat_settings=true&pretty'
检查 Bulk 响应时,不能只看 HTTP 状态码。必须读取每个 items 的结果:
{
"errors": true,
"items": [
{
"index": {
"_id": "order-001",
"status": 201
}
},
{
"index": {
"_id": "order-002",
"status": 400,
"error": {
"type": "mapper_parsing_exception"
}
}
}
]
}
这里第一条已经成功,第二条失败;重试时必须避免无条件重复写入。
12.4 查询延迟突然升高
应区分是协调节点、数据节点、特定分片还是查询本身的问题:
curl 'http://localhost:9200/_nodes/stats/jvm,indices,thread_pool,fs?pretty'
curl 'http://localhost:9200/_cat/thread_pool?v'
curl 'http://localhost:9200/_cat/shards?v'
重点观察:
- search 线程池是否排队;
- 某个节点 CPU、堆或磁盘是否异常;
- 是否正在进行大量 recovery;
- 是否只有某个分片数据量异常;
- 查询是否无意中 fan-out 到大量索引和分片;
- 聚合是否产生大量桶;
- 深分页是否导致协调节点内存压力。
副本可以帮助分担部分查询,但如果根因是查询访问了数千个小分片、聚合结果过大或单个 routing key 形成热点,盲目增加副本通常只能掩盖问题。
十三、容量、故障域和数据生命周期的组合设计
一个可持续的集群设计通常把以下因素一起考虑:
13.1 索引生命周期
时间序列数据不应无限写入一个索引。可以通过 rollover 和 ILM 让索引按大小、时间或文档量切换:
logs-000001 -> logs-000002 -> logs-000003
这样可以控制单个分片大小,并将旧数据迁移到 warm 或 cold 节点。
但 rollover 也可能制造大量小索引。若每天只有很少数据,却固定创建很多主分片,长期会积累大量空闲分片。因此 rollover 条件和主分片数必须结合真实写入量。
13.2 多可用区分布
如果集群跨多个可用区,应让节点带有 zone 属性,并通过 allocation awareness 使同一逻辑分片的主副本尽量分散。
目标不是让每个副本“看起来在不同节点”,而是让它们位于不同的故障域:
zone-a: primary
zone-b: replica
否则单个机架、交换机或可用区故障可能同时带走主副本。
13.3 副本和写入吞吐
增加副本会增加:
- 磁盘占用;
- 写入网络流量;
- 副本写入成本;
- 恢复和迁移工作量。
它可能提高查询并行度,但不能保证写入吞吐提升。对写入密集型索引,副本数需要和持久化、跨区网络以及可接受的数据安全目标一起评估。
十四、一个完整的最小实验
下面通过三个节点模拟分片、副本和故障观察。假设节点已使用相同的 cluster.name 加入同一个集群,并且节点具备数据角色。
创建索引:
curl -X PUT 'http://localhost:9200/demo' \
-H 'Content-Type: application/json' \
-d '{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 1
}
}'
写入几条文档:
curl -X PUT 'http://localhost:9200/demo/_doc/1' \
-H 'Content-Type: application/json' \
-d '{"user":"alice","amount":10}'
curl -X PUT 'http://localhost:9200/demo/_doc/2' \
-H 'Content-Type: application/json' \
-d '{"user":"bob","amount":20}'
查看分片:
curl 'http://localhost:9200/_cat/shards/demo?v'
预期能看到两个主分片和两个副本:
demo 0 p STARTED ...
demo 0 r STARTED ...
demo 1 p STARTED ...
demo 1 r STARTED ...
查看集群健康:
curl 'http://localhost:9200/_cluster/health/demo?pretty'
正常情况下应为:
{
"status": "green",
"active_primary_shards": 2,
"active_shards": 4,
"unassigned_shards": 0
}
停止一个承载分片的节点后,再执行:
curl 'http://localhost:9200/_cluster/health/demo?pretty'
curl 'http://localhost:9200/_cat/shards/demo?v'
如果对应副本位于其他节点,预期过程是:
原 primary 节点离线
->
副本提升为 primary
->
短时间内可能出现 INITIALIZING 或重新分配
->
相关主分片恢复可用
->
集群尝试补建新的 replica
此时可能短暂出现 yellow,因为副本尚未补齐;如果提升副本成功且所有分片最终都有副本,状态会回到 green。
这个实验不能证明生产集群一定能在任意故障下无损恢复,因为真实结果还取决于:
- 节点故障时副本是否已同步;
- 故障域是否独立;
- 是否存在未分配的其他分片;
- 磁盘和网络是否有恢复余量;
- 业务是否允许短暂不可用;
- 是否有快照和恢复演练。
十五、需要牢牢记住的边界
- 分片数决定初始数据分布,副本数决定物理复制数量。
- 副本能提高可用性,但不是备份。
- 自定义 routing 能减少查询 fan-out,也可能制造数据和请求热点。
green只说明分片分配完整,不说明查询、写入、磁盘和业务数据都正常。yellow通常表示副本未分配,不能简单视为“没有问题”。red说明至少一个主分片不可用,应优先定位具体索引和分片。- 主分片数量通常不能直接在线修改,调整容量需要 reindex、split、shrink 或生命周期设计。
- 恢复是磁盘、网络、CPU 和业务请求之间的竞争过程。
- 快照负责独立恢复能力,副本负责在线故障转移;两者不能互相替代。
- 分片、节点和集群的诊断必须结合实际 allocation decision、磁盘水位、节点角色、线程池和恢复状态,而不是只看一个健康颜色。
给主人留下些什么吧
系列导航与关联阅读
- 系列入口:数据库完整学习路线:从关系模型、事务索引到分布式与向量检索
- 上一篇:Elasticsearch 查询与聚合:Query DSL、相关性、分页和统计
- 下一篇:Elasticsearch 数据写入与运维:Bulk、Ingest、ILM、快照和升级
官方资料
本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。

评论
0 条讨论