_cluster/health 返回 yellow,多数人的第一反应是"再等等"。但 yellow 的语义很明确:主分片全在,有副本没分配——能读能写,只少了一层冗余。把它当"慢慢会好",代价是单节点故障时直接掉数据。运维功夫不在"重启试试",而在读完三个数字就知道下一步点哪里。
1. 三色语义与分片计数怎么读
$ curl -s "localhost:9200/_cluster/health?pretty"
{
"status": "yellow",
"number_of_nodes": 3,
"active_primary_shards": 12,
"active_shards": 18,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 6,
"active_shards_percent_as_number": 75.0
}
| 字段 | 含义 | 读法 |
|---|---|---|
status |
green/yellow/red | 见下表 |
unassigned_shards |
未分配总数 | 必须配合 _allocation/explain 看原因 |
initializing_shards |
正在恢复的分片 | 大于 0 说明仍在恢复,不要在这个窗口做变更 |
relocating_shards |
正在迁移的分片 | 滚动重启/扩容期间应为 0 才继续下一步 |
| 颜色 | 缺什么 | 影响 | 处置优先级 |
|---|---|---|---|
| green | 主、副本全部分配 | 正常 | 无 |
| yellow | 主全在,副本缺失 | 可读写,无冗余;节点挂掉可能丢分片 | 中:查副本为何没分配 |
| red | 至少一个主分片未分配 | 该分片对应数据不可读写,搜索返回部分结果 | 高:立刻 explain |
red 不等于数据没了:主分片未分配通常是节点离线、磁盘被拒或 mapping 冲突。先 explain 定位,再决定恢复节点还是 allocate_stale_primary。
2. 分配的基本约束与磁盘水位线
分片分配是"约束满足"问题:
| 约束 | 默认 | 后果 |
|---|---|---|
| 主副本不同节点 | 同 shard 编号的主与副本不能同节点 |
单节点时副本永远 yellow |
| 磁盘水位线 | low 85% / high 90% / flood 95% | 超线触发迁移或只读 |
| 节点角色 | 只有 data 节点能放分片 | coordinating 节点不承载数据 |
PUT _cluster/settings
{
"persistent": {
"cluster.routing.allocation.disk.threshold_enabled": true,
"cluster.routing.allocation.disk.watermark.low": "85%",
"cluster.routing.allocation.disk.watermark.high": "90%",
"cluster.routing.allocation.disk.watermark.flood_stage": "95%",
"cluster.info.update.interval": "30s"
}
}
三个水位线的行为差异是排障关键:
- low(85%):不再往这个节点新分配分片,已有分片不动;
- high(90%):开始把分片迁出该节点;
- flood(95%):自动给相关索引加
index.blocks.read_only_allow_delete: true,写入直接失败。
flood 的只读块不随磁盘下降自动解除,必须手动清:
PUT /kb_chunk/_settings
{ "index": { "blocks": { "read_only_allow_delete": null } } }
百分比相对数据盘;多路径
path.data时按各路径分别判定。多规格机器用绝对阈值("50gb")更可控。
3. _cluster/allocation/explain 怎么读
GET /_cluster/allocation/explain?pretty
{
"index": "kb_chunk",
"shard": 0,
"primary": false
}
返回里两个字段决定结论:
| 字段 | 内容 | 判读 |
|---|---|---|
allocate_explanation |
一句话结论 | 先看这句,通常是"cannot allocate because allocation is not permitted to any of the nodes" |
node_allocation_decisions[].deciders |
每个候选节点被哪条规则否掉 | decision: "NO" 的那条就是根因 |
常见 decider 与对应处置:
| decider | 含义 | 处置 |
|---|---|---|
same_shard |
同分片副本已在该节点 | 加节点,或临时降副本数 |
disk_threshold |
超 low/high 水位线 | 清盘、扩盘、迁索引 |
allocation_decider(并发迁入上限) |
cluster.routing.allocation.node_concurrent_incoming_recoveries 打满 |
等恢复完成,别叠加操作 |
filter / awareness |
分配过滤或机架感知不满足 | 检查 index.routing.allocation.* |
no_valid_shard_copy |
所有副本都损坏 | 从快照恢复 |
不带参数的 GET /_cluster/allocation/explain 会取第一个未分配分片,适合快速定位;精确排查必须带 index + shard + primary。
4. 节点角色怎么分
配置形态是 node.roles: [ master ]、[ data_hot, data_content, ingest ]、[ ] 三种。
| 角色 | 职责 | 不该承担 | 规模建议 |
|---|---|---|---|
master |
集群状态、分片决策 | 数据、查询 | 固定 3 台,专用 |
data |
存分片、执行查询与写入 | master 选举开销 | 按容量扩 |
ingest |
pipeline 预处理 | 重 CPU 的 grok 会拖慢写入 | 可与 data 混布 |
coordinating(空 roles) |
汇总各分片结果 | 不存数据,但吃内存与网络 | 大查询场景单独部署 |
node.roles: [] 是 coordinating-only,不是"什么都能干"。误配的表现是"加了机器,yellow 没好转"。
5. master 选举与脑裂
discovery.seed_hosts: ["10.0.0.11:9300", "10.0.0.12:9300", "10.0.0.13:9300"]
cluster.initial_master_nodes: ["es-master-1", "es-master-2", "es-master-3"] # 引导后删除
| 参数 | 语义 | 一次性? |
|---|---|---|
discovery.seed_hosts |
找候选 master 的地址列表 | 否,长期保留 |
cluster.initial_master_nodes |
首次引导用的 master 名单 | 是,集群成型后必须删除 |
discovery.type: single-node |
单节点开发模式 | 否,生产禁用 |
initial_master_nodes 残留的危害:集群已成型后重启,节点会按旧名单尝试重新引导,若名单里的节点已下线,可能形成两个独立的 master 视图,表现为同一集群名、不同数据。正确做法是引导完成后立刻从配置里删掉这一行并重启验证。
7.x 起取消 minimum_master_nodes 手工配置,quorum 由选举算法判定,(n/2)+1 不用再算。
6. 副本的作用与恢复流程
| 配置 | 写吞吐 | 读吞吐 | 冗余 | 说明 |
|---|---|---|---|---|
| 副本 0 | 最高 | 单点 | 无 | 仅测试或可重建数据 |
| 副本 1 | 略降 | +1 份读能力 | 1 节点故障可恢复 | 生产底线 |
副本不提升写吞吐:写请求要同步到所有副本,副本越多写放大越明显。它的价值是"高可用 + 读分摊"。
故障恢复链条:主分片节点失联 → master 把某副本提升为主(promote)→ 旧主回归后作为副本重同步 → 从主分片做 peer recovery(增量或全量)。恢复限速:
PUT _cluster/settings
{
"persistent": {
"indices.recovery.max_bytes_per_sec": "200mb",
"cluster.routing.allocation.node_concurrent_recoveries": 2,
"cluster.routing.allocation.node_initial_primaries_recoveries": 4
}
}
indices.recovery.max_bytes_per_sec 默认约 40mb/节点。调大能缩短 red→green,但挤占写入 IO——高峰调限速就是拿可用性换速度。恢复期看 GET /_cat/recovery?v&active_only=true 的 bytes_percent 与 stage。
7. 滚动重启的顺序
# 0) 先禁分片迁移,避免重启触发无谓的再平衡
curl -s -XPUT "localhost:9200/_cluster/settings" -H 'Content-Type: application/json' -d '
{"persistent":{"cluster.routing.allocation.enable":"primaries"}}'
# 1) 停一个节点(systemd / docker 均可)
systemctl stop elasticsearch
# 2) 重启后等它加入集群,再放开分配
curl -s -XPUT "localhost:9200/_cluster/settings" -H 'Content-Type: application/json' -d '
{"persistent":{"cluster.routing.allocation.enable":"all"}}'
# 3) 轮询到 green 且迁移归零,才动下一个
curl -s "localhost:9200/_cluster/health?wait_for_status=green&timeout=300s&pretty" \
| grep -E '"status"|relocating_shards'
| 步骤 | 目的 | 做错的后果 |
|---|---|---|
先设 allocation.enable: primaries |
阻止副本在重启窗口内乱迁 | 每个节点重启都触发全量再平衡,IO 打满 |
| 一次只停一个节点 | 保证 quorum 与副本可提升 | 停两台 → 3 副本场景也可能 red |
等 relocating_shards: 0 |
迁移完成再动下一个 | 副本反复 initializing,恢复永远追不上 |
恢复 allocation.enable: all 后再等 green |
让副本归位 | 直接进行下一台,副本重建叠加 |
重启窗口内 unassigned_shards 上升是正常现象;判断异常的标准是同一分片长时间停在 initializing,那说明限速或网络有问题,不是"再等等"。
8. 扩容与再平衡
加节点后分片不会自动"匀",取决于分配决策与再平衡阈值。
再平衡由 cluster.routing.rebalance.enable(默认 all)控制,阈值由 cluster.routing.allocation.balance.* 决定;cluster_concurrent_rebalance 控制并发迁移数,调大换速度。
| 场景 | 手段 | 注意 |
|---|---|---|
| 新节点分不到分片 | 检查 cluster.routing.allocation.exclude._name |
排除规则常被忘掉 |
| 分片太散、单节点过多 | index.routing.allocation.total_shards_per_node |
设太小会导致分配失败 |
| 想把某索引挪走 | _cluster/reroute 的 move |
只能搬已分配分片 |
| 冷热分层 | index.routing.allocation.require.data |
与 ILM 的 allocate 动作配合 |
POST /_cluster/reroute?dry_run=true 先验证再执行,这是唯一安全的用法:直接 move 失败会让集群进入"两边都没有该分片"的中间态。
9. 排错
| 现象 | 原因 | 处理 |
|---|---|---|
| 长期 yellow,副本全未分配 | 单节点 / 副本数 > 数据节点数 | 加数据节点,或把副本降到 1 |
red 且 explain 报 no_valid_shard_copy |
副本全损 / 快照缺失 | 从 _snapshot 恢复该索引 |
| 两个节点各自成集群 | initial_master_nodes 残留或 seed 网络不通 |
删配置行,检查 9300 连通性 |
10. 边界
| 场景 | 更合适的手段 | 理由 |
|---|---|---|
| 分片数规划 | 按"单分片 10–50GB"估算后一次性建好 | 分片数改不了,只能 reindex |
| 单索引超大数据量 | ILM + rollover | 靠单索引扩容会撞分片上限 |
| 频繁全量恢复 | 快照仓库(S3/共享存储) | 副本重建比快照慢一个量级 |
一句话:green 不是目标,可预期的恢复时间才是。副本数、水位线、恢复限速三个参数决定了故障时你要等多久。
11. 自测
yellow与red在"缺什么"上的区别是什么?unassigned_shards有值但initializing_shards也为 0,说明卡在哪一步?- 水位线 low / high / flood_stage 分别触发什么动作?
cluster.initial_master_nodes为什么在集群成型后必须删掉?- 副本数从 1 加到 2,写吞吐会变好吗?
- 滚动重启时先设
allocation.enable: primaries的目的是什么?