资讯详情

Elasticsearch分片分配机制详解:从原理到故障排查实战

📅 2026/10/13 12:21:13 | 华诺云谱 👁 阅读
Elasticsearch分片分配机制详解:从原理到故障排查实战
很多跑Elasticsearch的团队应该都遇到过这样的场景集群状态突然从GREEN跌成YELLOW甚至RED排查起来头大GET _cat/indices?v刷一下全是UNASSIGNED或者是某个节点磁盘快写满了索引自动变成只读写入全部报错。这类问题十有八九都指向同一个模块——Shard Allocation分片分配。我不敢说这是ES里最复杂的模块但它绝对是最影响集群稳定性的一个。我过去几年调优和排查线上集群踩过最多的坑就在这个环节所以想把这套机制的底层逻辑、核心参数和实战排查经验完整梳理一遍希望对正在维护ES集群的朋友有帮助。这篇文章适合两类人看一类是刚接触ES运维、被分片分配问题折磨过的新手可以把这里当成一份速查手册另一类是已经维护了一段时间集群、想深入理解分配器为什么这么“想”的开发者我会在原理部分多讲一些为什么而不只是罗列配置。内容会覆盖机制定位、分配算法、关键参数、日常运维手法、故障排查案例和一套可以参考的配置模板基本都是可以直接拿过去用的经验。1. 分片分配机制的本质它到底在干什么1.1 三个核心职责分片分配说白了就是ES在集群里决定“每个分片的副本应该放在哪个节点上”的一套流程。但它的职责不只是“放上去”这么简单。从我的理解来看它至少要完成三件事第一是分配Allocation索引刚创建或者节点崩溃后有分片需要找新位置时分配器要把它们安排到合适的节点第二是平衡Rebalance集群运行一段时间后分片分布会越来越歪有的节点磁盘快满了有的节点闲得慌分配器需要做一定程度的搬家调整第三是恢复Recovery分片从无到有、或者从旧数据恢复成可用状态的过程本质上也是分配逻辑在驱动。所以别看它叫“Allocation”实际上集群的所有分片生命周期变化都被这个模块包揽了。理解了这一点就不难明白为什么磁盘水位、节点并发恢复数、网络限流这类看似不相干的配置最终都会被归类到cluster.routing.allocation这个命名空间底下。1.2 触发时机什么时候会触发重新分配分片分配不是一个持续运行的守护进程它是事件驱动的。只要集群状态发生变化分配器就会被唤醒重新计算一遍。常见的触发事件有节点加入或离开集群、索引被创建或删除、分片状态变成UNASSIGNED、磁盘水位数据刷新默认30秒一次、以及rerouteAPI被手动调用。关键点在于一次触发并不意味着只移动一个分片。分配器会尝试把当前所有“不舒服”的分片安排到位。比如滚动重启时大批节点同时下线重新上线后可能几百个分片同时处于未分配状态分配器会一次性把它们都排上恢复队列这才是并发控制参数有意义的原因。如果不懂得批量触发这个特点只看单个分片怎么会分配这么慢往往就会找错方向。1.3 为什么不能随机放硬约束和软约束有人可能会问分片放哪个节点不都一样吗反正数据一致。实际上完全不一样。ES的数据安全模型默认要求主分片和它的每个副本不能落在同一个节点上否则节点挂了主副本一起丢数据就真丢了。还有更复杂的约束比如集群跨多个机架或可用区部署时你可能希望主副本分布在不同的故障域里这样某个机架停电不会导致整个索引不可用。这些都是分配器内部的硬性约束优先级最高不满足条件的方案直接会被淘汰。硬约束之上还有软约束也就是“尽量让集群整体更均衡”。ES会用打分的方式比较候选节点谁的分数更优就选谁。所以分片分配本质上是一个带约束的优化问题不是简单的“随机放一个”或者“轮询分一下”。这一点理解透了后面看日志和Explain输出时就不会一头雾水。2. 分配器是怎么选中一个节点的三步走的核心逻辑2.1 过滤阶段先把不合格节点排除掉源码层面分配器处理每个分片时会走一个大循环对集群中每个节点做评估。第一步是过滤把不具备条件的节点直接踢出候选名单。常见的过滤条件包括节点磁盘超过高水位线、节点处于下线状态、节点设置了某些allocation filter不支持该分片所在索引、或者节点上有其他硬性限制比如某些分片类型不允许放在该节点。这一步是最高效的筛选因为过滤规则简单直接。我见过不少排查案例最后定位到的问题其实就是过滤条件写错了比如索引设置了index.routing.allocation.require._name指到了某个节点组结果这个节点组扩容后IP变了新节点没打对应tag导致分片一直挂着UNASSIGNED。过滤阶段解决的是“这个节点行不行”的问题而不是“哪个更好”的问题。2.2 打分阶段多维度权重比较过了过滤关的节点进入打分阶段。分配器会按照一组策略对每个节点打分这些策略覆盖了几个核心维度节点上现有的分片数量包括主分片和副本分开统计、每个分片的大小、节点剩余磁盘空间、是否正在执行恢复或迁移磁盘水位计算时会考虑正在迁入的分片、以及节点带宽负载等。打分结果最终会折算成一个排序不是简单地选磁盘最大的节点。比如一个节点分片数很少但磁盘只剩10%和一个节点分片数很多但磁盘还剩60%分配器会比较综合的权值最终更倾向于把分片放到那个磁盘充足但分片略多一点的节点上。这也是为什么有时你手动看了半天觉得“明明这个节点分片更少为什么新分片没给它”其实是磁盘维度在起作用。2.3 选择阶段确定最终安置节点并排除冲突打分完成后分配器并不是直接选最高分的节点就完事还会再跑一轮约束检查确保分配结果不会引发新的冲突。典型的例子就是主副本不能同节点主分片已经放到了节点A那么副本分片的候选节点里A会被直接排除。再比如同一节点上同时排队等待恢复的并发分片数如果已经达到上限多出来的分片会被延后处理而不是全部一股脑压上去。这个阶段还会做“目标分片数”的收敛检查。ES允许你给单个节点设置cluster.routing.allocation.total_shards_per_node上限超过上限的节点即使打分排第一也不会被选择。换句话说打分是软性的“尽量”而总数限制是硬性的“不能超过”。搞懂这三步逻辑后你再去看_cluster/allocation/explain接口返回的deciders列表就能逐条看懂“为什么这个分片没被分配到这个节点”而不是只看最后那句NO。3. 核心配置与调优让分配器符合你的预期3.1 磁盘水位线最容易被忽视的底层红线磁盘水位的默认值相信很多人背得出来low是85%high是90%flood_stage是95%。但它们的实际行为我想强调一下因为这关系到事故的预防。当节点磁盘使用率超过low时该节点不再被分配新的分片超过high时该节点上已有的分片会被逐步迁移到其他节点超过flood_stage时更严重——ES会强制将节点上所有索引设置为只读read_only_allow_delete这时候写入直接失败直到磁盘降下来并且手动解除只读块。我踩过的坑是早期我们直接用了默认水位结果某天一批节点磁盘增长极快超过了95%触发flood_stage整个集群瞬间拒绝写入。当时所有人的第一反应是“为什么索引变只读了”根本没反应过来是磁盘水位的事。后来我们学乖了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: 90% } }注意一点水位线不一定是百分比也可以写成绝对值比如20gb表示剩余空间小于20GB时触发。对于大容量磁盘百分比设置容易失真比如10TB的盘95%还有500GB剩余其实完全够用但2TB的盘95%只剩100GB就很危险。所以更科学的做法是用绝对值给大磁盘机器设置下限。另外磁盘水位的刷新频率由cluster.info.update.interval控制默认30秒也就是说磁盘写满后最多有30秒的延迟才会触发保护高吞吐写入场景里这个延迟可能导致磁盘短暂写满需要心理有数。3.2 并发与限流恢复太快和太慢都是问题分片恢复、迁移都要走网络拷贝数据这里面有三个关键参数并行起作用cluster.routing.allocation.node_concurrent_incoming_recoveries单节点同时接收的分片数默认2。cluster.routing.allocation.node_concurrent_outgoing_recoveries单节点同时向外发送的分片数默认2。indices.recovery.max_bytes_per_sec每个节点的恢复带宽上限默认40MB/s。默认值适合小集群但对几十节点规模的中型集群来说太保守了。节点挂了以后几百个分片要恢复单节点一次只能同时接收2个后面全排队恢复时间被拉得很长。我见过一个集群因为默认并发太低节点故障后恢复用了3个多小时业务方心态直接崩了。所以实践上我们一般会调高PUT /_cluster/settings { persistent: { cluster.routing.allocation.node_concurrent_incoming_recoveries: 6, cluster.routing.allocation.node_concurrent_outgoing_recoveries: 6, indices.recovery.max_bytes_per_sec: 200mb } }但这里有个反向坑恢复本身就是高IO操作如果集群同时承担大量线上读写盲目把并发和带宽拉太高会让数据节点IO/网络打满反而拖慢正常请求。我的建议是分场景白天业务高峰保持默认或保守值夜间维护窗口调高并发快速恢复。ES支持动态修改完全可以按窗口切换。还有一个容易被忽略的参数是indices.recovery.concurrent_streams它控制每线程并发流数量对象存储场景下这个参数往往要调大些提高恢复吞吐。3.3 分配总开关什么时候必须一把关死cluster.routing.allocation.enable是个总开关可选值有all、primaries、new_primaries、none。默认是all即所有类型分片都可以分配。这个开关平时用不上但在两个场景异常重要一个是集群滚动重启另一个是节点逐个下线维护。滚动重启时如果你不关分配节点一停它上面的分片立刻变UNASSIGNED然后被分配到其他节点触发大量无意义的跨节点数据搬迁。等原节点重启回来又要搬回去。这一来一回集群带宽被白白耗尽恢复时间翻好几倍。正确做法是重启前把所有节点设成none不去分配任何未分配分片重启完所有节点后再设回all让集群一次性恢复正常。这里有个细节如果是大集群分批重启不是所有节点都下线也可以设置成primaries让主分片优先分配等全部节点恢复后再开启副本分配。类似地节点维护前把分配关掉维护完再打开能避免大量分片在维护窗口内反复搬移。但要注意设置none后新创建索引的主分片也不会分配所有新建索引都会处于YELLOW状态这不是故障是预期行为。做完维护一定要记得改回来。3.4 分配过滤按节点属性定向摆放分片cluster.routing.allocation.include、exclude、require这组参数允许你按节点attribute做定向分配。attribute可以是节点自定义的tag比如box_type、zone等。举一个实际场景集群里有一部分节点是SSD一部分是机械盘你希望某个冷索引只放在机械盘节点其他索引尽量用SSD就可以给节点打tag// 节点配置中设置 node.attr.zone: warm node.attr.storage: hdd // 索引级别设置 PUT /my_cold_index/_settings { index.routing.allocation.require.storage: hdd }这个机制在成本优化和物理资源隔离上非常好用。但千万注意require和exclude别同时设置冲突条件我们线上就出过一次事故新加入的节点打了zone: warm的tag但老节点标签是zone: hot给某索引设置require.zone: warm后扩容时发现新节点没有node role分片始终UNASSIGNED。排查了很久才发现是节点角色配置漏了不是过滤条件的问题。所以用这类参数时建议把tag设计和节点角色规划一起纳入上线流程。3.5 延迟分配给“临时故障”一个缓冲期一个很容易被低估的参数是index.unassigned.node_left.delayed_timeout默认1分钟。它的作用是当某个节点离开集群时它上面的分片不会立刻重新分配而是等待一段配置的时间。为什么需要这个缓冲因为很多节点离开是短暂的比如JVM GC卡顿被误判、进程重启、网络闪断。如果你让分片立刻分配走马上又会有一次跨节点的数据搬迁等原节点回来了分片又搬回来白耗资源。我看到过ugly的案例由于设置成了0节点每次抖动集群都要经历千级分片迁移运维同事半夜被集群告警轰炸。后来把延时调整到5分钟节点抖动基本不影响集群稳定性。配置方式PUT /my_index/_settings { index.unassigned.node_left.delayed_timeout: 5m }如果你希望集群里所有新索引都生效可以在索引模板里设置。但延迟分配也存在副作用如果节点真挂了回不来故障恢复会被拖慢5分钟。所以权衡点是“误判率”和“恢复时效”对高可用要求极高的核心索引建议缩短延时对普通业务索引延长延时更划算。3.6 冷启动恢复集群重启后的首批分片ES集群全部重启后数据节点需要从磁盘恢复分片数据。这一过程受gateway.recover_after_nodes、gateway.expected_nodes、gateway.recover_after_time控制。简单说集群重启后会等待满足一定数量的节点加入后再开始恢复而不是第一个节点起来就开始抢分片。这能避免“只起来2个节点就开始疯狂恢复后面节点再加入时又产生大量分片移动”的低效局面。我一个客户的集群有30个数据节点默认配置下每次机房级重启恢复耗时超过40分钟因为每个批次的节点加入都会触发一次重新分配。后来配置成gateway.recover_after_nodes: 25 gateway.expected_nodes: 30 gateway.recover_after_time: 10m恢复耗时压到了15分钟以内。这里有个容易踩的坑expected_nodes必须包含master和data节点的总数漏一个参数不生效恢复逻辑就退化成默认行为。4. 分片平衡与reroute日常运维的两把刷子4.1 平衡机制为什么分片总是不均匀很多集群明明节点规格一样运行一段时间后分片数却相差不少。平衡机制是为解决这个问题存在的但它不是“完全平均分配”而是基于权重打分尽量缩小差异。有两个参数直接影响平衡力度cluster.routing.rebalance.enable控制哪些类型分片参与平衡默认all可设primaries/replicas/nonecluster.routing.allocation.allow_rebalance控制平衡触发的时机默认indices_all_active即所有索引的主副本都可用时才启动平衡。日常工作里如果加了新节点想让它自动分担分片前提是rebalance没有被关掉。我遇到过不止一次某人排查问题改过rebalance.enable之后忘改回来结果集群节点间分片数差异越来越大。如果你发现新节点加了很久分片数还是0先查这个开关再查磁盘水位最后再查过滤条件。另一个经常被问的问题是cluster.routing.allocation.cluster_concurrent_rebalance默认2表示全集群同时最多执行2个分片的平衡移动。这参数对大集群太抠门几十上百个分片需要平衡时会被拖得很慢。按经验节点数超过20的集群建议调大到5以上否则新节点扩容后分片要很久才能均衡。4.2 reroute API临时挪动分片的手动档自动机制满足不了所有场景有时某个分片就卡在某节点上一直不迁走而那个节点磁盘马上要满了。这时候rerouteAPI派上用场POST /_cluster/reroute?explain { commands: [ { move: { index: my_index, shard: 0, from_node: node-a, to_node: node-b } } ] }几乎没有任何按需分配比自动平衡更直接。但我必须提醒reroute千万不要频繁脚本化使用。手动移动完自动分配器下一轮就可能重新评估并把分片再挪回去反而加剧抖动。正确用法是用于紧急场景比如某节点磁盘要爆了先把分片挪走、或者配合cluster.routing.allocation.enablenone时做定向调度。如果是磁盘水位引发的紧急迁移先解决根本问题清磁盘、扩容不然光靠reroute救火是治标不治本。4.3 节点排空与滚动重启的标准操作节点排空是指把某台节点上的所有分片都转移出去最典型的场景是下线一台物理机或做硬件升级。我们验证下来最好用的流程是先设置分配暂停cluster.routing.allocation.enablenone防止转移过程中出现新的分片分配。逐个执行reroute的move命令把该节点上的分片手动迁移到目标节点。确认该节点分片数为0且状态空闲后执行节点下线。等所有操作完成把allocation.enable改回all。有人偷懒跳过第1步直接reroute就会出现一边迁一边分配新分片的情况最后节点都快停服了还在往上面写新分片。按这个流程操作过的人应该都体会过“看起来多了一步但省了半夜起来加白班”的快乐。5. 故障排查实录遇到问题怎么定位5.1 UNASSIGNED分片先查Explain再说集群出现UNASSIGNED分片第一步不是猜而是让ES自己告诉你原因。最有效的工具是Explain APIGET /_cluster/allocation/explain?pretty { index: my_index, shard: 0, primary: true }如果不带index参数它默认返回集群中第一个未分配分片的详细原因适合不知道从哪入手时用。输出的deciders部分会罗列每个节点被接受/拒绝的原因常见的拒绝原因包括node has no enough disk space——磁盘水位超限。node is marked as read-only——flood_stage触发的只读。same shard already allocated to this node——主副本同节点冲突。filtered by allocation filter——过滤条件或tag不匹配。node excluded by [max shards per node]——超出节点分片配额。我排查的习惯是先看current_state和explanation这两个字段一般能定位到80%的问题。剩下20%比较隐蔽会显示no valid node found这种这时候就要检查是不是所有候选节点都因为某个全局因素被过滤掉了比如磁盘水位全集群超限或者所有节点的角色都不对。5.2 磁盘水位引发的只读事故恢复步骤如果集群突然报错写入拒绝同时看到index.blocks.read_only_allow_delete: true大概率是磁盘打到了flood_stage。恢复步骤我建议按这个顺序立刻清理磁盘删除临时文件、滚动旧索引、缩小分片数任何方式先让磁盘低于95%。手动解除只读块PUT /my_index/_settings {index.blocks.read_only_allow_delete: null}。注意这里用null而不是false因为有的版本用false可能不生效。等集群状态恢复GREEN后再观察确认不会再次触发。这个事故里最容易犯的错是只清理磁盘忘记第2步解只读块结果磁盘明明降下来了写入还是一直失败。我们团队第一次遇到时折腾了一个小时才反应过来。记住flood_stage触发的只读设计上就是故意“卡住写入”来保护集群必须手动解锁。5.3 恢复慢与并发瓶颈看队列而不是只看进度分片恢复进度慢很多人第一反应是网络带宽不够。其实更常见的是并发恢复数太小。节点故障后成百上千个分片要恢复但单节点一次最多同时接收2个默认值后面的全部排队。怎么判断看集群恢复线程池和恢复队列状态GET /_cat/recovery?vactive_onlytrue GET /_cat/thread_pool?v如果recovery线程池显示排队任务非常多而活跃任务只有2个说明并发参数卡住了。调整node_concurrent_incoming_recoveries后恢复速度立竿见影。还有一个常被忽略的点是恢复期间集群自身也在执行merge分段合并磁盘IO会被合并操作分走极端情况下要调低indices.merge.scheduler.max_thread_count给恢复让路。5.4 典型的配置失误我见过的几个高频坑把多年遇到的高频配置失误列个表给大家避雷症状根因解决新节点分片数为0rebalance.enable被关过没恢复确认并恢复all节点重启后分片大量跨节点搬迁没关allocation.enable重启窗口设置none分片反复分配/移动delayed_timeout设成0调成3~5分钟索引设置tag后一直UNASSIGNED新节点打tag不一致用_cluster/allocation/explain核对attribute集群恢复极慢node_concurrent_recoveries默认2按节点规模调大磁盘没满但分片不分配low水位线设太低调整水位策略这些坑的共同点是它们都不是“ES坏了”而是“人没搞懂分配器的行为逻辑”。理解机制以后排查效率完全是另一个级别。6. 一套防事故的配置模板与上线检查清单6.1 中小集群推荐配置如果你维护的是10~20节点规模的集群建议先按这套模板起步再根据业务调整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: 90%, cluster.routing.allocation.node_concurrent_incoming_recoveries: 4, cluster.routing.allocation.node_concurrent_outgoing_recoveries: 4, cluster.routing.allocation.cluster_concurrent_rebalance: 3, indices.recovery.max_bytes_per_sec: 100mb, indices.recovery.concurrent_streams: 3 } }模板里强调几条水位线建议留出至少15%的余量因为高峰期索引增长可能很猛并发恢复数4对中小集群是比较平衡的值兼顾恢复速度和线上IO带宽限流100mb通常够用具体要看节点网卡和磁盘性能。6.2 大规模集群与跨可用区场景节点数超过50、或者跨可用区部署的集群需要额外考虑三点。第一启用节点分片总数配额cluster.routing.allocation.total_shards_per_node防止某些节点堆积过多分片导致热点。第二跨可用区场景一定要通过require参数把分片副本分散到不同可用区避免失去故障域隔离。第三冷启动参数必须配置否则大集群重启的恢复时间会成灾难gateway.recover_after_nodes: 48 gateway.expected_nodes: 50 gateway.recover_after_time: 10m还有一点大集群建议显式设置cluster.routing.allocation.allow_rebalance为indices_all_active让所有分片可用后再开始平衡避免恢复过程中又叠加平衡移动导致集群状态剧烈抖动。6.3 上线前检查清单通过这套配置上线的集群我每次还会跑一遍自检清单几分钟就能查出隐患GET _cluster/health确认GREEN且没有UNASSIGNED分片。GET _cat/allocation?v查看各节点磁盘使用率、分片数是否均衡。GET _cat/nodes?v核对所有节点都已加入、角色正确。GET _cluster/settings?flat_settingstrue确认关键参数不是遗留的测试值。抽查几个索引的_settings确认没有历史遗留的错误过滤条件。关闭allocation.enable验证一次节点重启确认恢复流程顺畅且不会误搬分片。这套检查几乎救了我们好几次大促前的场子强烈建议也纳入你的上线流程。分片分配机制调到这里核心要点基本覆盖全了。最后分享一个小经验如果你只记住一个排查动作那就记_cluster/allocation/explain如果你只记住一个保护动作那就记滚动重启之前把allocation.enable设成none。这两个动作能帮你避开百分之八九十的分片分配事故。后面我再遇到这块的新坑还会继续整理补充分片分配机制值得每个ES使用者投入时间吃透。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑