拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Elasticsearch磁盘水位告警后的分片迁移:原理、reroute实战与Java自动化运维

我接手这套Elasticsearch集群的时候规模不大不小3个数据节点每天从MySQL批量同步业务数据还有另一路埋点数据从Logstash灌进来。那段时间最频繁的告警就是disk watermark磁盘使用率一过85%ES就开始畏手畏脚——新分片分配不出去副本初始化卡住集群状态从green一路跌回yellow最后某一个主分片所在的节点直接被数据塞满整个索引变red。做Java后端的人大概都有过这种经历明明集群整体还有空闲磁盘但分片全堆在一个节点上自动均衡又慢又飘最后只能靠手动干预救火。这套手动干预的核心操作就是分片迁移。这篇文章我打算从一次真实的磁盘告警事故讲起完整梳理分片迁移的原理、执行方式、监控回滚以及如何把迁移能力封装成Java自动运维工具。内容适合刚接手ES集群的Java后端、兼职做ES运维的同学也适合准备面试时被问到集群分片不均怎么处理的人。1. 为什么运维第一步是分片迁移从磁盘水位告警说起1.1 一次真实的集群黄变红事故先复盘一下我当时踩坑的经过。集群里的索引主要分两类一类是业务同步过来的订单和用户数据有主分片有副本另一类是日志类索引为了省磁盘空间副本数设为0。问题恰恰出在这个为了省空间的配置上。业务高峰期日志索引疯狂增长集中写入到某个节点。那天下午我收到告警es-data-01磁盘使用率92%触发了flood stageES把所有分片强制置为只读。由于日志索引没有副本主分片所在的节点磁盘满了以后索引状态直接变成red日志写入报错紧接着业务查询也开始超时。我第一反应是清理数据删了一些过期索引但磁盘释放的效果没那么快。当时集群里其他节点磁盘使用率只有30%左右分片分布严重倾斜唯一能快速见效的办法就是把es-data-01上最大的几个分片手动迁移到空闲节点上。1.2 分片迁移到底在迁什么先把这个概念掰开揉碎讲清楚。ES里一个索引被分为多个主分片primary shard每个主分片可以有零到多个副本分片replica shard。分片是Lucene索引的完整实例有自己的目录、segment文件、translog。分片迁移本质上就是把这个Lucene实例从节点A搬运到节点B走的是正常的分片恢复recovery流程不是简单的文件复制。目标节点要先创建分片目录然后从源节点拉取数据或者在主分片迁移时通过文件级恢复加translog回放来保证数据一致最后更新集群路由表源节点再清理本地副本。理解这个流程你才会明白为什么分片迁移有并发限制为什么大分片迁移很慢为什么目标节点必须预留充足磁盘。1.3 一个关键约束主副本不能同节点同一个分片的主分片和副本分片不能分配在同一节点上。这一点在手动迁移时尤其要小心。我见过有同事手动move分片时把一个主分片从节点A迁到节点B结果发现节点B上已经有这个分片的副本迁移请求直接报错或者出现更隐蔽的问题——比如因为索引级别的allocation配置把节点排除了命令返回acknowledged但实际没有动作。所以迁移前搞清楚集群的分配规则、了解每个分片在哪些节点上比你直接发reroute命令重要得多。1.4 为什么Java开发者要懂这个很多Java后端觉得Elasticsearch只是中间件出问题应该有专人处理。但现实是中小团队根本没有专职ES运维集群出问题第一个被拉进群的就是写ES代码的人。你不仅要在代码里调搜索API关键时刻还得能看懂集群状态、定位分片异常、手动迁徙数据。而且Elasticsearch原生管理操作全部走REST APIJava生态里对应的客户端从早期的TransportClient到后来的RestHighLevelClient再到现在的Elasticsearch Java API Client这些工具如果你掌握分片迁移原理完全可以在Java服务里封装一个集群自愈模块定时扫描、检测倾斜、自动迁移把救火变成日常巡检。2. 迁移前先做体检三类API看清集群家底动手迁移之前我强烈建议先做一轮完整检查。我见过太多人上来就reroute结果把自己绕进更大的坑。分片迁移不是把卡片从左边挪到右边那么简单你得先知道哪些分片能迁、哪些不能迁、哪些节点能接、哪些节点不能接。2.1 三件套命令第一件事看集群健康状态GET /_cluster/health重点看status字段。green表示所有主分片和副本分片都正常yellow表示有副本未分配red表示有主分片未分配。如果集群不是green我会先定位到底哪些索引有问题而不是直接开始迁移。第二件事看当前分片分布GET /_cat/shards?vsindex GET /_cat/shards?vsnode按索引排序能快速锁定某个索引的分片分布按节点排序能看整个节点的分片总数、主分片数和副本数。第三件事看每个节点的磁盘和分片分配情况GET /_cat/allocation?v这个接口返回的信息非常直观node、shards、disk.used、disk.avail、disk.total、disk.percent。做迁移决策时我依赖的主要就是这张表。2.2 从分配表里读出真实需求这里有个容易犯的错只看节点磁盘使用率百分比不看剩余空间的绝对值。举个例子节点A disk.percent是80%但它是一块2T的盘剩余400G节点B disk.percent是70%但它是一块500G的盘剩余只有150G。这种情况下该优先把分片迁到A而不是B。因为迁移的目的是给源节点腾出可用空间目标节点必须有足够的绝对容量来承接数据。我再补充一个看分片大小的方法用GET /_cat/shards?vhindex,shard,prirep,store,nodesstore:descstore字段按降序排列后能一眼识别出最大的分片在哪个节点。磁盘告警时优先迁移大分片的收益远高于迁移一堆小分片。2.3 把体检工具封装成Java方法如果你习惯用Java操作ES可以把上面三条命令封装成一个体检方法定时执行。RestHighLevelClient的写法是ClusterHealthRequest healthRequest new ClusterHealthRequest(); ClusterHealthResponse health client.cluster().health(healthRequest, RequestOptions.DEFAULT); CatShardsRequest shardsRequest new CatShardsRequest().v(true); CatShardsResponse shards client.cat().shards(shardsRequest, RequestOptions.DEFAULT);需要说明的是RestHighLevelClient在7.15之后进入维护状态ES官方已经不再推荐新项目使用。8.x和9.x版本的官方客户端是Elasticsearch Java API ClientClusterHealthResponse health esClient.cluster().health();无论用哪个客户端最终目的都一样把集群状态转成可量化的数据。有了这些数据你后面的迁移决策才有依据而不是我觉得这个节点太满了挪一个分片试试。3. 手动分片迁移reroute的完整使用姿势体检做完确认要迁移了最直接的手段就是Cluster Reroute API。这个API的官方名称是reroute作用是手动控制分片的分配。3.1 reroute的几种动作别只会movereroute commands里最常用的几个move把已分配的分片从一个节点迁移到另一个节点这是分片迁移最标准的用法allocate_replica把某个未分配的副本分片手动分配到一个指定节点allocate_stale_primary把某个未分配的主分片分配到指定节点使用该节点上可能存在的过期数据前提是原主分片已经不可恢复cancel取消一个正在进行的恢复或迁移任务。我特别提醒一点allocate_empty_primary这个动作尽量不要在生产用它意味着你告诉ES这个节点上的空存储就当主分片用数据会丢。3.2 move命令的标准调用比如要把索引order_v1的第2个分片分片编号从0开始从es-node-1迁到es-node-3POST /_cluster/reroute { commands: [ { move: { index: order_v1, shard: 2, from_node: es-node-1, to_node: es-node-3 } } ] }响应里会有一个acknowledged字段只代表请求被集群接受了绝不代表迁移已经完成。很多新手在这里误判以为命令返回成功就万事大吉。实际上move提交后只是把分片加入恢复队列真正迁移可能还在排队。3.3 在Java代码里发起reroute先看RestHighLevelClient的写法ClusterRerouteRequest request new ClusterRerouteRequest(); request.addMoveCommand( new ClusterRerouteUtils.MoveCommand( order_v1, 2, es-node-1, es-node-3 ) ); AcknowledgedResponse response client.cluster().reroute(request, RequestOptions.DEFAULT); boolean acknowledged response.isAcknowledged();再看Elasticsearch Java API Client的写法MoveStep moveStep new MoveStep.Builder() .index(order_v1) .shard(2) .fromNode(es-node-1) .toNode(es-node-3) .build(); ClusterRerouteRequest rerouteRequest ClusterRerouteRequest.of(r - r .commands(c - c.move(moveStep)) ); ClusterRerouteResponse response esClient.cluster().reroute(rerouteRequest);如果是在本机跑这些Java示例先把JAVA_HOME环境变量配好。ES 7.x要求Java 8或118.x开始要求Java 179.x对Java版本的要求只会更高。Windows环境经常有同学卡在环境变量配置上我踩过很多次坑之后形成了个习惯任何一台要跑ES相关Java代码的机器第一件事就是确认java -version输出的是目标版本。3.4 批量迁移的优先级策略一个节点磁盘快满要迁出的分片往往不止一个。这时不要一次性往commands里塞几十个move命令原因有三个并发迁移数量有限默认单节点同时接收和发送的分片数都是2提交太多命令大部分会被排队甚至忽略一个分片迁移失败时几十个命令混在一起定位问题非常痛苦回滚困难你根本分不清哪个分片被移动过、哪个还没有。我的经验做法是每次最多提交5个move命令按分片大小从大到小排列优先迁出最大的分片。为什么因为大分片在源节点上占用的磁盘和IO更明显迁走一个120G的分片比迁走十个12G的分片效果更显著而且大分片迁移成功后能快速把源节点的磁盘水位降到一个安全区间。4. 不只有reroute自动分配与再平衡策略手动迁移是应急手段但如果集群的分配机制本身不健康你每周都得手动救火。ES其实内置了一套自动分配和再平衡机制只是默认参数往往不够敏锐。4.1 磁盘水位线ES的自我保护机制ES内置了三层磁盘水位线我用一张表对比一下水位线默认阈值触发行为low watermark85%低于该水位时ES不会为了平衡磁盘而自动迁移分片high watermark90%达到该水位ES不再向该节点分配新分片flood stage95%达到该水位该节点所有索引分片被强制置为只读这三个水位线都可以通过PUT /_cluster/settings动态调整。比如你的节点是1T盘数据增长很快可以把low调到82%、high调到88%、flood stage调到92%PUT /_cluster/settings { persistent: { cluster.routing.allocation.disk.watermark.low: 82%, cluster.routing.allocation.disk.watermark.high: 88%, cluster.routing.allocation.disk.watermark.flood_stage: 92% } }但要注意水位线不是越敏感越好。low一旦调太低ES会频繁做后台迁移占用的IO和网络带宽会影响线上查询。8X%是一个比较折中的配置。4.2 再平衡是后台过程和手动迁移是两码事很多刚接触ES的人以为集群分片不均衡时会自动触发迁移所以不用管。这个想法只对了一半。ES确实有自动rebalance机制它按照一组权重公式来评估集群是否需要重新分配分片例如cluster.routing.allocation.balance.shard分片数量权重默认0.45cluster.routing.allocation.balance.disk_usage磁盘占用权重默认2e-11cluster.routing.allocation.balance.awareness可用区感知权重默认0.0。但rebalance是一个非常保守的后台过程它需要持续观察、逐步调整而且受分配限流和磁盘水位的双重约束。当某个节点磁盘已经触发high watermark时rebalance大概率不会帮你解决燃眉之急。所以我的结论是日常靠自动分配保持整体均匀出问题时靠手动reroute快速纠偏两者结合。4.3 索引级别的分配过滤规则有些时候你手动move分片命令返回acknowledged但分片纹丝不动很可能是索引级别或集群级别设置了allocation过滤规则。比如某个索引被钉死在特定节点上PUT /order_v1/_settings { index.routing.allocation.require.node_name: [es-node-1, es-node-2] }这种情况下你想把它迁到es-node-3reroute会被拒绝或者被路由规则覆盖。遇到这种情况得先修改索引的allocation配置再执行move。5. 迁移过程中的监控、验证与回滚分片迁移一旦开始不是提交完命令就能甩手不管。我见过太多次迁移完发现集群状态不对又急急忙忙四处救火的情况。迁移过程必须有监控、有验证、有回滚预案。5.1 迁移进度怎么看迁移进度的第一入口是GET /_cat/recovery?vactive_onlytrue这个接口输出当前正在进行的恢复任务字段包括index、shard、stage、percent等。stage字段有几种取值INIT、INDEX、VERIFY_INDEX、TRANSLOG、FINALIZE、DONE。看到DONE才说明这个分片恢复完成。同时也要配合GET /_cat/shards?vsnode对比迁移前后每个节点的分片数和磁盘占用的变化。5.2 迁移完成后的冷静期一个分片显示DONE不代表它马上可以对外提供完整服务。ES在分片恢复完成后还需要做一些收尾操作比如清空缓冲、合并segment、加载到堆外缓存。这些不会立刻体现在recovery接口里。我的习惯是一批分片迁移完成后等10到15分钟观察集群状态是否稳定、磁盘占用是否继续增长、查询耗时是否出现抖动。确认没问题再执行下一批。5.3 迁移卡住或失败的排查路径分片迁移常见的失败原因我列一下目标节点磁盘空间不足这是最典型的并发恢复数量超限分片一直排队源节点和目标节点版本不一致跨版本集群对恢复协议有兼容性问题索引有自定义allocation规则move目标被排除reroute命令重复提交造成路由状态混乱。如果迁移长时间卡住我建议先看目标节点是不是空间不够然后看是不是有分片在其他节点上占用恢复队列。必要时可以取消正在执行的恢复任务POST /_cluster/reroute { commands: [ { cancel: { index: order_v1, shard: 2, node: es-node-3 } } ] }取消之后分片会保持原来分配状态源节点数据不会丢。这个操作相对安全但要注意cancel只对正在进行的分片恢复有效。5.4 迁移过程中的运维纪律最后说几条运维纪律都是我用真金白银换来的经验迁移前务必记录每个节点的分片数和磁盘百分比基线否则迁移完你无法判断是否有效一次只做一种操作不要一边手动move一边调水位线一边又做索引shrink变量太多出了问题很难定位迁移大分片时尽量选择业务低峰期因为恢复过程会占用大量磁盘IO和带宽如果集群已经有red索引先解决red问题再考虑迁移因为red状态下迁移会掺杂太多不可控因素。6. 一次完整的分片迁移实战复盘前面讲了一堆原理和命令最后用一个真实案例把整个流程串起来大家可以直接对照着走。6.1 事故场景环境是3节点ES集群节点名是es-data-01、es-data-02、es-data-03。es-data-01和es-data-02承担了90%的读写es-data-03磁盘占用长期在30%左右。某天告警es-data-01磁盘使用率92%触发flood stage多个索引变成只读部分日志索引因主分片所在节点没空间直接变成red。6.2 先解燃眉之急解除只读我做的第一步不是迁移而是先把flood stage的强制只读解除。原因很简单ES如果认为节点处于flood stage它不会接受任何新的分片分配你move过去也白搭。PUT /_cluster/settings { persistent: { cluster.routing.allocation.disk.watermark.flood_stage: 92% } }这里把flood stage从95%调到92%是临时的目的是解除强制只读同时留下一点缓冲。注意如果你把flood stage调回一个高于实际磁盘使用率的值只读状态会自动解除。6.3 找到es-data-01上的大分片然后定位大分片GET /_cat/shards?vhindex,shard,prirep,store,nodesstore:desc很快锁定一个120G的日志索引分片在es-data-01上而es-data-03上并没有这个分片。我决定把这个主分片迁过去。6.4 执行迁移并跟踪执行move命令POST /_cluster/reroute { commands: [ { move: { index: user_log_2024.10, shard: 3, from_node: es-data-01, to_node: es-data-03 } } ] }120G分片在千兆内网环境下大概跑了20分钟。期间我每隔1分钟刷一次cat/recovery确认percent稳定增长。迁移完成后es-data-01的磁盘使用率从92%降到84%回落到high watermark之下集群状态逐步恢复green。6.5 把手动操作升级成Java定时任务手动解决问题之后我意识到这种事故不会只发生一次于是把刚才的操作封装成了一个Java自动迁移工具核心思路是每5分钟读取一次/_cat/allocation数据找出磁盘使用率最高的节点和最低的节点如果最高节点超过85%最低节点低于75%就自动把最大分片迁过去。核心代码逻辑是public void autoMigrateIfNeeded() throws IOException { CatAllocationResponse allocation esClient.cat().allocation( new CatAllocationRequest().v(true), RequestOptions.DEFAULT ); ListCatAllocationRecord records allocation.getRecords(); if (records.isEmpty()) return; CatAllocationRecord source null; CatAllocationRecord target null; for (CatAllocationRecord r : records) { if (UNASSIGNED.equals(r.getNode())) continue; if (source null || r.getDiskPercent() source.getDiskPercent()) { source r; } if (target null || r.getDiskPercent() target.getDiskPercent()) { target r; } } if (source ! null target ! null) { double sourcePercent Double.parseDouble(source.getDiskPercent()); double targetPercent Double.parseDouble(target.getDiskPercent()); if (sourcePercent 85 targetPercent 75) { moveLargestShard(source.getNode(), target.getNode()); } } }这个工具上线后效果很明显后面再没出现过因为单节点磁盘打满导致集群红的问题。当然工具的触发阈值和执行逻辑要根据自己的集群实际情况调整不能生搬硬套。6.6 关于ES版本的一个提醒热词里出现了Elasticsearch 9.0.4这里多提一句。ES 8.x开始RestHighLevelClient进入维护期官方推荐Elasticsearch Java API Client包名也从org.elasticsearch.client换成了co.elastic.clientsAPI风格从命令式变成了流式构建。如果你在9.x集群里继续用旧客户端大概率会遇到版本兼容报错。但分片迁移这个操作本身在任何版本里核心逻辑都一样先体检、再决策、手动干预和自动策略结合、持续监控验证。把这条链路跑熟了ES集群的分片分布和磁盘水位问题基本就能稳得住。这套流程我在实际运维中反复用了很多次从最开始的手足无措到后来的有条不紊靠的就是把原理吃透、把监控做细、把工具化沉淀下来。希望这篇复盘能帮你少踩几个坑。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门