Elasticsearch集群节点磁盘使用分配不均解决办法
# Elasticsearch 集群节点磁盘使用分配不均解决办法
同一个集群里,有的节点磁盘 90%,有的还剩一半——这不是「ES 分配算法坏了」,而是分配的粒度是分片,不是字节。理解这一点,后面所有处置手段才有依据。
版本说明
本文写于 2022-03,2026-08 重写。
水位线参数(cluster.routing.allocation.disk.watermark.*)与 _cat/allocation 的行为基于 ES 7.x / 8.x 官方文档核对;8.x 起 transient 集群设置已废弃,本文按 persistent 口径给命令。
# 一、先搞清楚 ES 到底按什么分配
ES 的分片分配决策由一串 decider 串联完成,与磁盘不均直接相关的是两个:
ShardsLimitAllocationDecider:保证同一索引的分片尽量分散到不同节点,它数的是分片个数。DiskThresholdDecider:按节点磁盘水位线放行或拦截分配,它看的是字节。
默认情况下前者主导。于是最常见的成因是:
- 分片数与节点数不匹配。索引
number_of_shards为 3、集群有 5 个节点,那么无论怎么均衡,都有 2 个节点不持有这个索引的主分片。 - 分片本身大小悬殊。按时间滚动的索引里,某天流量暴涨,那一天的分片可能是平日的十倍——分片个数均衡了,字节依然不均衡。
- 冷热标签或
allocation.require把分片钉死在少数节点上。
别把第 2 条当成第 1 条治。 分片个数已经均衡却依然磁盘不均,说明问题在分片大小分布,调
number_of_shards对存量索引无效——它只对新建索引生效。
# 二、先取证,再动手
不要一上来就改水位线。先确认不均衡到底出在哪一层:
# 1. 每个节点用了多少磁盘、持有多少分片
GET _cat/allocation?v&s=disk.percent:desc
# 2. 分片级视图:谁大谁小,落在哪个节点
GET _cat/shards?v&h=index,shard,prirep,store,node&s=store:desc
# 3. 有没有分片处于 UNASSIGNED,以及 ES 自己给的原因
GET _cluster/allocation/explain
2
3
4
5
6
7
8
判读方法:
_cat/allocation里shards列相近但disk.percent差很多 → 成因 2(分片大小悬殊)。shards列本身就不均 → 成因 1 或 3,接着看allocation/explain给出的 decider 结论。
allocation/explain 是这里最有用的一个接口——它会直接告诉你是哪个 decider 拒绝了分配,不用猜。
# 三、水位线:三条线各自真正的含义
这三个参数经常被记错,逐条说清楚(以下为 7.x/8.x 官方语义):
| 参数 | 默认 | 达到后发生什么 |
|---|---|---|
...disk.watermark.low | 85% | 不再把新分片分配到该节点。注意:全新索引的主分片不受这条限制,它拦的是副本与已有索引的分片迁入。 |
...disk.watermark.high | 90% | ES 开始把该节点上的分片迁走(relocate away),而不是"往别处写"。 |
...disk.watermark.flood_stage | 95% | 给该节点上有分片的那些索引加上 index.blocks.read_only_allow_delete,索引转为只读。 |
三个常见的记错,正好对应三种误处置:
- ❌「low 到了就不能写入了」→ 实际是不再分配,写入照常,磁盘会继续涨。以为写入停了而不管,结果一路冲到 flood_stage。
- ❌「high 到了会写到别的节点」→ 实际是迁移分片,迁移本身要占用磁盘和 IO,短期内会让磁盘更紧张。
- ❌「flood_stage 会锁住所有索引」→ 只锁该节点上有分片的索引。7.4 起水位恢复后该 block 会自动解除,更早的版本需要手动解。
手动解除(确认磁盘已经降下来之后):
PUT /_all/_settings
{ "index.blocks.read_only_allow_delete": null }
2
# 四、按成因选处置
# 处置 A:调水位线——只是买时间
PUT /_cluster/settings
{
"persistent": {
"cluster.routing.allocation.disk.watermark.low": "80%",
"cluster.routing.allocation.disk.watermark.high": "85%",
"cluster.routing.allocation.disk.watermark.flood_stage": "95%",
"cluster.routing.allocation.cluster_concurrent_rebalance": "2",
"cluster.routing.allocation.node_concurrent_recoveries": "2"
}
}
2
3
4
5
6
7
8
9
10
这是止血,不是根治:把 low/high 调低只是让 ES 更早开始躲开满节点,磁盘总量没变。真正的作用是给你争取时间去做 B 或 C。
两个必须知道的代价:
- 调低 high 会立刻触发分片迁移,迁移期间集群 IO 和网络会明显上升,要挑低峰做。
cluster_concurrent_rebalance调大能加快均衡,但同样吃 IO,2 是个保守起点。
不要用
transient。 老文档里常见"transient",但它在集群完全重启后丢失,而且 ES 8.x 已将其标记为废弃。persistent才是对的。
# 处置 B:重建索引,改分片数——治成因 1
number_of_shards 不能原地修改,只能重建:
POST _reindex?slices=auto&wait_for_completion=false
{
"source": { "index": "my_index", "size": 5000 },
"dest": { "index": "my_index_new" }
}
2
3
4
5
分片数怎么定:让单个分片落在 10–50 GB 区间(取值口径与分片和副本一致:延迟敏感偏 10–20 GB,日志类偏 30–50 GB),而不是机械地"等于节点数"。分片开太多,每个分片都有独立的 Lucene 段和内存开销,几百个小分片对 master 和堆内存都是负担。
跑完之后用别名切换,避免改客户端:
POST _aliases
{
"actions": [
{ "remove": { "index": "my_index", "alias": "my_index_read" } },
{ "add": { "index": "my_index_new", "alias": "my_index_read" } }
]
}
2
3
4
5
6
7
坑:
slices=auto按源索引分片数切分,源索引分片少的时候并行度上不去,别指望它总能跑满。- reindex 期间源索引仍在写入的话,新索引会缺掉这段增量——要么先停写,要么补一轮按时间范围的增量 reindex。
- reindex 期间磁盘占用是翻倍的:新旧索引同时存在。磁盘已经 90% 的时候直接 reindex 会把集群推进 flood_stage,这时应该先做处置 A 或先删旧数据。
# 处置 C:手动搬分片——治急症
某个节点马上要满,等不及自动均衡:
POST _cluster/reroute
{
"commands": [{
"move": {
"index": "my_index", "shard": 3,
"from_node": "node-hot", "to_node": "node-idle"
}
}]
}
2
3
4
5
6
7
8
9
只在应急时用。手动 reroute 与自动均衡是会互相拉扯的——搬完记得确认 ES 没有又把它搬回去。
# 五、怎么确认处置生效
每一步都要能验证,否则等于没做:
# 磁盘占比是否收敛(关注 max 与 min 的差值,而不是单个节点)
GET _cat/allocation?v&s=disk.percent:desc
# 是否还有分片在迁移
GET _cat/recovery?v&active_only=true
# 集群是否回到 green,有没有未分配分片
GET _cluster/health?level=indices
2
3
4
5
6
7
8
预期:disk.percent 的极差持续收窄、_cat/recovery 的 active 记录逐步清空、_cluster/health 为 green 且 unassigned_shards 为 0。
# 六、坑与边界
- 本文的处置都假设集群还有可写节点。 如果所有节点都过了 high 水位,迁移无处可去,
_cluster/reroute也会被 decider 拒绝——那时只有扩容或删数据两条路,任何参数调整都不解决问题。 _cat/allocation的disk.avail是文件系统视角,如果数据目录和系统盘同一个分区,别的进程写日志一样会把水位推上去,别只盯着 ES。- 调低 flood_stage 是危险动作:这条线是最后的数据保护,把它往上调(比如 98%)等于把「只读保护」推迟到磁盘几乎写满,届时 Lucene 段合并可能因空间不足失败,代价远大于只读。
- 分片大小悬殊的根因常在写入侧(按天滚动 + 流量尖峰)。长期解法是按大小滚动(ILM 的
max_primary_shard_size)而不是按天,否则处置完下个月还会复发。
生产环境执行顺序
先 _cat/allocation 取证 → 再 allocation/explain 定成因 → 低峰期做处置 → 每步都用第五节的命令验证。跳过取证直接改水位线,是这类故障里最常见的返工原因。