分片数量和副本数量这两个参数几乎是每个接触 Elasticsearch 的人在建第一个正式索引时都会被要求填的两个数字也是最容易被当成填空题随手带过去的两个数字。我见过太多索引在创建时number_of_shards写成 5、number_of_replicas写成 1然后数据量涨上来、节点挂掉一台、查询延迟开始抖动才回过头来问“分片数上限到底是多少”“副本到底该配几个”。这时候往往已经晚了——主分片数在索引创建之后就是不可改的副本数虽然能改但改完要触发一次全集群的数据搬迁赶上业务高峰就是一次事故。这篇文章不讲入门的增删改查专门把这两个参数掰开分片数量在不同层面上的“上限”分别是什么、这些上限是怎么被算出来的、如何从数据量和节点数反推一个合理的分片数、副本数量在可用性和写入延迟之间怎么权衡以及已经配错了之后有哪些补救路径。适合正在做索引规划的人、被集群变黄折腾过的运维以及想知道“为什么我的集群明明没多少数据但状态更新很慢”的开发。文中的数值和命令我都在实际集群里跑过会标注哪些是官方默认值、哪些是经验值你可以直接拿去对照自己的环境。1. 分片不是越多越好的旋钮先看清它在消耗什么1.1 一个分片背后是一个完整的索引引擎实例很多人对分片的理解停留在“数据太大切开存”这一层觉得分片就像给文件分了几个文件夹多分几个只是多几个目录名。实际不是。每一个分片在底层都是一个独立的索引引擎实例它有自己的段文件集合、自己的事务日志、自己的段合并调度、自己的查询缓存和写入缓冲。也就是说一个索引被切成 10 个分片就等于在节点上跑了 10 套相对独立的存储与检索结构只不过它们共用同一份进程堆内存和同一个线程池。这个认知非常关键因为它决定了分片数量带来的开销是线性叠加的而不是像很多人想的那样“加个名字而已”。你把一个 5 分片的索引拆成 50 分片硬件不变的前提下每个分片分到的写入缓冲变小、段合并更零碎、查询时需要跨更多分片做扇出再归并单次查询的协调开销显著上升。在数据量很小的时候这种开销看不出来因为每个分片里就几万个文档几分钟就能合并完一旦数据量上来问题会集中爆发。1.2 分片数量真正在吃掉的三种资源第一是堆内存。每个分片都有一批常驻的对象包括段的元数据、字段信息结构、分片级别的请求缓存等。这些开销和分片里存了多少数据关系不大是一个接近固定的底噪。业内比较通行的经验值是每 GB 堆内存承载不超过 20 个分片这个数字在很多公开的容量规划材料里都能看到。按这个算一台给 16GB 堆的节点理论上不该承载超过 300 个分片而这还只是“不炸”的线不是“跑得好”的线。第二是文件句柄。每个分片在磁盘上是一组段文件段合并过程中新旧文件会同时存在一段时间句柄占用会短暂翻倍。分片数越多打开的文件越多系统级的文件描述符上限如果没调很容易在某个合并高峰撞到 “Too many open files”然后节点失联。这个坑我在早期项目里踩过一次现象是节点每隔几小时就从集群里掉出去一次查了半天才发现是分片数乘上每分片段数超过了默认上限。第三是集群状态。每个索引、每个分片的路由信息、映射信息都要被主节点维护并广播给所有节点。分片数越多集群状态对象越大主节点处理一次状态变更比如某个节点下线触发的重平衡就越慢状态在节点间传播的延迟也越长。当分片数到几万的量级你会明显感觉到创建索引变慢、集群健康状态刷新滞后本质就是主节点在序列化和广播一份巨大的状态对象。1.3 副本不是备份盘它是另一份可查询的活索引副本最常见的误解是“相当于备份”。备份是离线的、滞后的、不参与读写的副本是实时的、在线的、参与写入确认和查询分摊的。写入一条文档时请求先到主分片主分片处理完立刻并行转发给所有副本等副本都写成功具体等几个由wait_for_active_shards控制才向客户端返回成功。这意味着副本数直接乘在了写入链路上副本越多一次写入需要等待的确认越多尾部延迟越容易被最慢的那个副本拖住。但副本带来的收益也是实打实的查询可以在主分片和副本之间轮询分摊读吞吐近似随副本数线性增长某个节点故障时它上面的副本失效但主分片或其他副本还在集群能自动把副本提升或者重新分配服务不中断。所以副本数的本质是在“写放大”和“读并发加可用性”之间做交换不是简单的一句“配 1 就行”。2. 分片数量的上限到底在哪三个层面各有各的天花板2.1 单索引层面主分片数创建即定死默认上限 1024先说最硬的那条。一个索引的主分片数量由index.number_of_shards决定这个参数在索引创建完成后不可修改任何“我想把 5 分片改成 10 分片”的想法在不重建索引的前提下都无法实现。从 7.x 版本开始这个参数的默认值是 1而更早的版本默认是 5很多老项目里遗留的 5 分片模板就是从那个年代带过来的如果直接沿用在新版本上会显得分片偏多。单索引的分片数还有一个默认的最大值限制通常在 1024 这个量级。这条限制存在的理由不是磁盘不够而是防止有人用一个索引就把整个集群状态撑爆。想突破它需要改系统级配置但我的建议是根本不要往这个方向想——单索引分片数超过几百这个索引的查询扇出和状态开销已经很难接受了正确做法是拆索引或者用滚动索引而不是把一个索引的分片数往四位数堆。2.2 集群层面每个节点能挂多少分片有个软上限除了单索引限制集群级别还有一个“防呆”参数控制每个数据节点平均能承载多少个分片默认值是 1000。它的判定逻辑大致是集群里所有索引的主分片加副本总数除以数据节点数如果超过这个阈值新建索引或者分配分片就会被拒绝报错信息里会明确告诉你超出了每个节点的分片上限。这个参数的初衷是好的防止一个配置失误的模板批量创建出几十万个分片把集群拖死。但它是个平均值不看实际分布所以在节点数很少的集群上特别容易误伤。比如一个三节点的小集群按默认 1000 算总容量是 3000 个分片听起来很多但如果你的业务是每天生成一个 5 分片、1 副本的索引一天就是 10 个分片一年就是 3650 个不到一年就会撞线而且这时候每个索引里可能只有几百 MB 数据。调整它的方式很直接PUT _cluster/settings { persistent: { cluster.max_shards_per_node: 2000 } }提示调高这个值只是解除“拒绝创建”的保护并不会让集群跑得更快。调之前先确认堆内存和文件句柄真的撑得住否则只是把明确的报错换成了隐性的性能崩塌。2.3 物理层面堆、句柄、状态大小构成真正的天花板真正决定你能挂多少分片的是上一节讲的那三项资源的组合而不是任何一个可以配置的数字。可以用一张表把三个层面的限制放在一起对照方便判断自己的集群到底卡在哪一层限制层面典型默认值触发后的表现是否建议调高单索引主分片数1024 左右创建索引直接失败不建议应改索引设计每节点平均分片数1000新建索引或分片分配被拒绝可小幅度调需评估资源堆内存承载约 20 分片/GB 堆查询变慢、GC 频繁、节点假死不可调只能加分片或加节点文件句柄系统 ulimit 决定节点掉线日志报 too many open files可调但治标不治本集群状态规模无固定值主节点 CPU 高、状态更新滞后不可调只能减少分片看清这张表之后很多“上限是多少”的问题就能自问自答了如果你的集群有 6 个数据节点、每节点 32GB 堆按 20 分片/GB 算是 3840 个分片的堆容量上限而按每节点 1000 的软上限算是 6000那么真正的瓶颈在堆软上限反而不是最先撞到的。反过来如果节点堆很小但数量很多先撞到的往往是软上限。3. 从数据规模和节点数反推分片数该怎么算3.1 单个分片多大的区间是合理的在讨论分片数之前得先有一个“单分片目标大小”的锚点。公开的容量规划建议里比较通行的区间是单分片 10GB 到 50GB。这个区间的下限来自固定开销的摊薄分片太小前面说的那批常驻对象的成本占比就高硬盘上全是元数据真正存数据的比例低上限来自恢复和合并的成本分片太大一次段合并的 IO 峰值更高节点故障后重建这个分片的耗时也更长。不同场景的取值还要再收窄一些。日志和时序类数据写入量大、查询多是按时间范围扫描、很少更新可以往 30GB 到 50GB 靠分片少一些能减少扇出面向用户的搜索类索引查询模式复杂、经常需要跨字段打分排序建议往 10GB 到 30GB 靠单分片小一点合并更轻、缓存命中更好更新极其频繁的索引比如库存、状态类单分片太大反而会让每次合并的代价变高也要往小的一侧压。3.2 一套可以直接照做的估算流程我把平时用的流程整理成五步你按顺序走一遍基本不会出大错。第一步估数据总量。不是当前量是索引一个完整生命周期结束时的量。比如按天滚动的索引保留 30 天日增原始数据 8GB主分片不压缩的情况下大约 8GB 乘 30 天再考虑副本和段合并的临时膨胀磁盘要多留 20% 到 30%。第二步定单分片目标大小。按上面说的场景选一个数日志类取 40GB 为例。第三步算主分片数。总量除以目标大小向上取整。假设是 240GB 除以 40GB得到 6 个主分片。这里有个细节如果算出来是 1.2不要取 2宁可取 1 然后靠滚动索引控制单索引大小。小索引上的多分片是纯粹的浪费。第四步算分片总数。主分片数乘上1 加副本数。6 个主分片配 1 副本就是 12 个分片。第五步校验。分片总数除以数据节点数看是否远低于每节点 1000 的软上限分片总数除以堆总容量GB看是否远低于 20再除以节点数看单节点分片数是否落在合理范围。三个校验都通过这个方案才站得住。举个完整例子6 个数据节点、每节点 16GB 堆、日增 8GB、保留 30 天、单分片目标 40GB。主分片 6副本 1总分片 12每节点平均 2 个分片每 GB 堆 0.125 个分片。这个配置非常宽松实际上意味着你完全可以用更少的节点或者把单分片目标调小以提高并行度。反过来说如果你算出来每节点平均分片数已经接近 800那不是在扩容边缘是该重新设计索引结构了。3.3 分片数为什么几乎是不可逆的三条补救路径的代价主分片数创建即固定这是设计约束不是 bug。真要改只有三条路每条都有明确代价。第一条是重建索引。新建一个目标分片数的索引然后_reindex把数据搬过去再切别名。这条最通用能改分片数、能改映射、能改分析器代价是搬运期间要双倍磁盘和大量 IO数据量大时会跑很久而且如果源索引还在写入需要处理增量同步。POST _reindex?wait_for_completionfalse { source: { index: orders_v1 }, dest: { index: orders_v2 } }第二条是拆分。_split能把一个索引的分片数按整数倍扩大比如 2 拆 4、4 拆 8。它比 reindex 快得多因为底层是硬链接段文件而不是重写数据。但有前提源索引必须设为只读目标分片数必须是源分片数的整数倍且不能超过创建时设置的 routing 分片数。也就是说如果你建索引时没考虑过以后要拆这条路可能走不通。第三条是收缩。_shrink把分片数缩到原来的因数分之一比如 8 缩 4。它要求所有主分片先被迁移到同一个节点上这个过程本身就要搬数据而且目标索引在收缩期间同样只读。方式能否改分片数相对耗时前置条件适用场景_reindex任意改最慢需要额外磁盘通用还要改映射时首选_split只能整数倍扩大快索引只读、routing 分片数够分片偏少想扩容并发_shrink只能按因数缩小中等分片集中到同一节点、只读分片过多小索引合并我的实际经验是这三种方式里最常用的其实是 reindex因为业务变化往往不只是分片数的问题。但不管用哪种都要提前准备好别名切换方案让上层应用感知不到索引名的变化。4. 副本数量怎么定可用性、写延迟和读吞吐的三角权衡4.1 副本数等于 N 时写入链路发生了什么写一条文档请求先路由到主分片所在的节点。主分片完成本地写入后把请求并行发给所有活跃副本等它们都返回成功才向客户端返回。默认情况下wait_for_active_shards的值是 1也就是只要求主分片本身活跃但这只是“等待确认”的下限实际写入仍然会同步到所有在同步集合里的副本。所以副本数从 1 加到 2写入需要等的确认从 1 个变成 2 个每一次写入的延迟取决于最慢的那个副本节点。如果三个节点里有一个正在做段合并或者 GC它的响应会变慢整个写入的 P99 就跟着变差。这是很多人在扩容副本之后发现“写变慢了”的直接原因不是索引变大了是尾部延迟被拉长了。副本的另一个隐性成本是磁盘和恢复时间。副本数翻倍存储成本翻倍这还好算真正麻烦的是节点重启后的分片恢复副本越多需要从主分片同步的数据量越大恢复窗口越长。一个大索引配 2 副本一个节点重启可能要跑十几分钟才恢复绿色这期间的查询能力是打折的。4.2 副本能不能放得下取决于节点数和分布策略副本不会被分配到和主分片相同的节点上这是基本规则。所以副本数的上限实际受节点数约束3 个数据节点的集群配 1 副本刚好每个节点上一主一副的合理分布配 2 副本意味着每个节点要扛三份数据一个主两个副的组合在三个节点间轮转磁盘压力直接翻三倍而且任何一个节点故障都会影响更大比例的分片。只有一个数据节点的集群配 1 副本索引会一直处于黄色状态因为副本无处分配。这不是错误是正常现象但很多人第一次看到黄色健康状态会慌。测试环境里正确的做法是把副本设为 0生产环境则至少保证三个数据节点。如果节点分布在不同的机架或可用区还要注意分片分配策略避免主分片和它的副本落在同一个故障域里。相关参数控制的是同一分片的主副本是否允许共置于同一主机默认是禁止的这个默认值不要动。4.3 生产上几种常见的副本配置组合场景节点数推荐副本数理由本地开发、单机测试10副本无处分配避免索引常黄小规模生产、读写均衡31允许挂一台读吞吐翻倍写放大可控读多写少的搜索服务5 以上2查询分摊明显写入量小可承受写放大写多读少、日志采集3 到 51写入吞吐优先副本多了写延迟明显归档、冷数据任意0 或 1查询少优先省磁盘还有一种常见做法是热温分层热节点上的索引配 1 到 2 副本保证服务能力滚动到温节点后把副本降到 0 或 1用磁盘换成本。这需要配合索引生命周期策略自动调整副本数而不是手工改手工改在大批量索引上几乎不可能维护。5. 已经配错了怎么办从症状到补救的完整链路5.1 分片过多的现场长什么样怎么一步步查出来分片过多的症状很分散容易被误判成别的问题。典型表现是创建索引偶尔超时、主节点 CPU 长期偏高但数据节点不高、集群健康状态刷新有延迟、节点重启后恢复时间以十分钟计、查询延迟在数据量没怎么涨的情况下缓慢上升。这几个现象同时出现两三个就该怀疑分片数了。排查的第一步是看总量和分布GET _cat/shards?vhindex,shard,prirep,state,docs,store,node GET _cat/allocation?v GET _cluster/health?pretty第一条能看到每个索引的每个分片落在哪个节点、多大、多少文档。重点看两类一是文档数很少但占用分片名额的索引比如几十万文档却占了 5 个分片二是单分片大小明显偏小的索引。第二条看每个节点的分片数和磁盘占用能立刻发现某个节点扛了远多于平均值的分片。第二步是看集群状态的压力GET _nodes/stats/indices/segments,indices/fielddata,jvm?pretty GET _cluster/stats?pretty重点看主节点的堆使用和 GC 频率、各节点的段数量、字段数据占用。段数量异常高通常意味着分片太多或者合并跟不上。第三步是算账。把索引数乘上每索引分片数除以数据节点数除以堆总容量GB得到每 GB 堆承载的分片数。超过 20 就要警惕超过 40 基本可以确认问题就在这里。5.2 分片过少的补救split 和 reindex 的选择顺序分片过少的表现和过多相反单个分片体积膨胀到上百 GB、写入集中在少数分片上导致 CPU 不均、查询并行度不够、段合并耗时越来越长。这时候第一反应是加节点但如果分片数不变加节点并不能提升这个索引的写入并行度因为一个分片只能落在一个节点上。补救的顺序我建议这样如果这个索引当初创建时设过 routing 分片数并且还有余量优先用_split速度快、风险低。操作前先把索引设为只读并等待所有分片健康PUT /orders_v1/_settings { settings: { index.blocks.write: true } } POST /orders_v1/_split/orders_v2 { settings: { index.number_of_shards: 8, index.number_of_replicas: 1 } }如果 routing 分片数不够或者映射也要一起改那就走 reindex配合别名切换。切换顺序是建新索引并写数据校验、跑 reindex、比对文档数、把别名从旧索引原子切到新索引、观察一段时间后再删旧索引。千万不要先删旧索引再建新的那是最常见的自找麻烦。5.3 索引模板里最容易埋的两个默认值第一个坑是模板里写死分片数。很多团队在建模板时按当时的业务量定了 5 分片之后所有按天滚动的索引都继承这个值结果每天的索引只有 2GB 数据也照样占 5 个分片一年下来几千个几乎空的分片。正确做法是把模板里的分片数设成 1让滚动策略根据单分片大小来触发而不是根据时间。第二个坑是滚动条件写的是时间而不是大小。按天滚动看起来很整齐但业务量波动大的时候某天数据暴涨就会产生一个巨大的分片某天数据很少就产生一堆碎片。用单分片大小或者单索引大小作为滚动条件分片大小才是可控的。PUT _ilm/policy/orders_policy { policy: { phases: { hot: { actions: { rollover: { max_primary_shard_size: 40gb, max_age: 7d } } }, delete: { min_age: 30d, actions: { delete: {} } } } } }max_primary_shard_size这个条件比按天数滚动稳得多分片大小一旦到了阈值就滚不受业务波动影响。同时保留一个max_age作为兜底防止低流量时期索引无限期不滚动。6. 我自己的几组配置习惯和每周要看的几个数说几个我在实际项目里形成的固定做法。建索引之前一定先算一遍分片数哪怕是最简单的临时索引也会先问一句“这个索引一个月后大概多大”因为事后补救的成本远高于事前多想两分钟。默认值我一般按单分片 30GB 作为起点日志类放宽到 40GB搜索类收紧到 15GB 左右这个起点在绝大多数中小规模项目上都够用。副本数我默认给 1除非节点数少于 3。节点数只有两个的时候配 1 副本意义不大因为一个节点故障剩下那个节点要同时扛主副两份数据服务能力断崖式下降还不如把资源用在扩容节点上。读多写少的搜索服务可以给到 2但要盯着写入延迟的 P99一旦发现尾部延迟变大先怀疑副本同步而不是索引设计。每周的巡检我固定看四个数集群总分片数除以数据节点数、每 GB 堆承载的分片数、单分片体积的最大值和最小值、集群状态的更新耗时。前两个判断容量是否健康第三个判断分片设计有没有失衡第四个判断主节点是不是快要扛不住了。这四个数里任何一个明显跑偏都比等到报警再去查要省事得多。还有一个容易被忽略的细节调整副本数是个在线操作改完会立刻触发分片重新分配和大量数据拷贝直接影响磁盘 IO 和网络。这种操作不要放在业务高峰做也不要一次给几百个索引同时改分批来改完等集群恢复绿色再做下一批。我自己就吃过一次亏为了赶一个“降本”的节点一次性把几百个索引的副本从 2 降到 1结果集群花了几个小时才稳定下来期间查询延迟翻了三倍比省下的那点磁盘钱贵多了。
阅读完成 · 觉得有帮助?