分片机制算是 Elasticsearch 里面最容易被忽略、但又最影响集群命运的那部分。很多人刚接触 ES 时会搜各种安装教程,装好一个节点就把数据往里灌,直到有一天查询突然变慢、或者某个节点一挂整个索引变红,才回头研究分片到底是什么。这篇文章我就以实际运维的视角,把 ES 的分片机制从概念、规划、路由到底层分配完整捋一遍,顺便把我踩过的坑也一并交代了。如果你是刚接触 ES、或者是已经在生产环境维护集群但还没系统理解分片的人,这篇内容应该正好对你有用。
1. 分片到底是什么——先把概念讲透
1.1 数据条带化与横向扩展
分片(shard)本质上是一个完整且独立的 Lucene 索引。ES 能把一个索引拆成多个分片,相当于把一份大文件切成多段,分别放到不同的节点上,这就是典型的数据条带化(striping)思路。类比一下:一本书太厚了,复印店会把原稿拆成好几叠,分给不同的人同时复印,最后再合到一起。ES 里的分片就是那“一叠稿子”,每个节点就是那个“复印的人”。
因为 Lucene 索引由多个不可变分段(segment)组成,查询和写入本质上都是和这些分段打交道。分片能不能工作、内存够不够、磁盘是否充足,都直接影响整个集群的表现。所以,分片数量的选择不只是“越多越分散”,而是要在“并行能力”和“资源开销”之间找平衡。
1.2 主分片与副本分片的关系
ES 里每个分片都有“主分片”(primary shard)和“副本分片”(replica shard)之分。主分片承担索引文档的写入和查询请求,副本分片是主分片的完整拷贝,负责容灾和分担查询压力。
举个例子,一个索引设置 5 个主分片、每个主分片 1 个副本,那么一共会有 10 个分片分布在整个集群中。主分片分布在节点 A 和节点 B,副本分片会自动分配去别的位置。ES 的设计原则是:副本与它的主分片尽量不落在同一个节点上。这是为了避免“一台机器挂了,主分片和副本同时丢失”的最坏情况。ES 的容错逻辑很简单——只要主分片和至少一个副本不在同一台机器同时宕机,数据就不会丢。
这里有个容易搞混的点:副本分片同样具备查询服务能力。查询请求可以落在任意一个分片(无论是主还是副本)上,ES 内部会根据负载情况选择分片响应请求。所以,副本的作用不只是备份,它在查询层面也是“额外的读容量”。
2. 创建索引时怎么规划分片数
2.1 默认值的前世今生
很多人在创建索引时直接忽略了分片数设置,这一点其实挺危险的。
ES 5.x 之前的版本,默认是 5 个主分片、每个分片 1 个副本。那时社区推荐的做法就是设置 5 个主分片,默认值也沿用了多年。但从 ES 7.0 开始,默认主分片数变成了 1。官方给出的理由是,在数据量没有确切预估之前,1 个主分片配合后期扩容(比如用 split API)反而比一开始盲目设很多分片更安全。
我曾接手过一个老集群,索引是 5.x 时代建的那一批,主分片数全是 5。问题在于:索引实际数据量只有几十 GB,每个分片只有几 GB,查询却动不动就要遍历 5 个分片再合并结果,coordinating 节点的开销白白多了好几倍。这就是“分片太多,数据太少”的典型场景。
2.2 基于数据量反推分片数
我建议你在定分片数的时候,先把“未来一年索引预计容纳多少数据”算清楚,再按单分片大小倒推。
单分片多少数据合适?社区比较普遍的经验值是20~40GB 左右。为什么是这个区间?因为 Lucene 的 segment 合并、内存映射文件(mmap)以及查询时的堆内存占用,都跟单个分片的数据量密切相关。分片太小,会导致分片数量膨胀,查询时协调成本高;分片太大,单个分片恢复耗时长,查询也会变慢,还会撑爆节点内存。
假设一个业务索引每天产生 5GB 数据,保留 30 天,那就是 150GB 数据量。按单分片 30GB 来算,至少需要 5 个主分片。如果你想留出扩容余量,可以按 6 或 7 个主分片建。再配合 ILM(索引生命周期管理),按天建索引,那么每天的那个索引其实是 6 个主分片 × 1 副本,一共 12 个分片在集群里跑。这个量对三个节点的集群来说,完全可控。
2.3 分片大小经验区间与“过早优化”
这里我额外说一句:分片数不是越大越好,也不是越小越好,而是要根据查询类型来选择。
- 如果你跑的主要是精确 ID 查询或关键词查询,单分片数据量偏大一点影响不大,主要还是看内存和磁盘 IO。
- 如果你是做聚合分析、大范围扫描,分片太多可能更糟,因为协调节点要等所有分片都返回结果才算完成。
- ES 官方和社区并没有硬性规定单分片上限,但超过 50GB 之后,merge 和 recovery 的耗时都会显著上升,建议尽量不要超过 50GB。
另外,不要一开始就为了“以后大数据量”把分片数设成 50 或 100。主分片数是创建索引后不可随意修改的(除非用 split/shrink API),但设多了的后果同样很严重:节点空闲但分片很多时,每个分片都要占用内存和线程资源,没数据却要“空转”,反而拖垮集群。
3. 路由、查询与写入的幕后机制
3.1 文档路由公式
分片规划好了,接下来一个最常被忽略的问题就是:文档到底写到哪个分片?这就涉及路由(routing)机制。
ES 的默认路由公式是:
shard = hash(routing) % number_of_primary_shards这里的routing默认是文档的_id,hash是 ES 内部使用的哈希函数,number_of_primary_shards就是该索引的主分片数。
举个例子,主分片数是 3,某条文档的_id经过哈希和取模运算后得到 1,那么它就会被写入第 1 个分片。查询这条文档时,ES 先计算hash(_id) % 3,定位到对应分片,再直接请求那个分片。这也是为什么 ES 说“单文档实时性很好”的原因之一——不需要广播到全索引,只要定位到一个分片。
3.2 查询的 fan-out 真相
如果查询不带routing值,那情况就完全不同了。ES 会把请求广播到索引的所有主分片或副本分片(取决于协调节点策略),每个分片各自查完自己的那部分,最后回到协调节点上合并结果。
这就是为什么“分片越多,查询不一定越快”的根本逻辑。假设一个索引有 30 个分片,一条查询请求会打到 30 个分片上去执行。如果这 30 个分片分散在 5 个节点上,那每个节点要承受 6 个分片的查询任务;如果 30 个分片全压在一个节点上,那这个节点要串行或并发处理 30 次查询任务,CPU 和 IO 都会成为瓶颈。
严格来说,ES 会在协调节点上做一定的并发控制,但分片数量过大时,协调节点收集结果的开销仍然很大。你可以在查询 API 里通过preference参数让同一用户的请求尽量落到同一分片副本,从而利用节点级别的查询缓存。
3.3 为什么说副本不只是容灾
既然副本分片也响应查询,那它的意义可以拆成两层:
- 容灾:某个节点宕机后,副本分片会被提升为主分片,服务不中断。
- 横向扩展读性能:读多写少的场景下,增加副本数可以让查询请求分散到更多节点上。
比如一个索引有 3 个主分片、2 个副本,承载搜索业务的查询流量。当 QPS 很高时,副本数量多就相当于把读压力平均分散了。我之前做过一个压测,3 节点集群、同一索引从 0 副本加到 1 副本,查询吞吐量大约提升 50%+,代价只是同步复制了一倍的数据。
需要注意的是,写操作仍然只发生在主分片上。主分片写完后再异步复制到副本分片。副本数过多时,写放大效应很明显。所以副本规划要看你的是读多写少还是写多读少,不能盲目加副本。
4. 分片分配与集群再平衡
4.1 节点上线、下线时发生了什么
当你往集群里加一个新节点时,ES 并不会自动把所有数据重新均匀打散。它只会把“当前没有满足分配策略的分片”迁到新节点上去。
如果集群里已经有大量索引、分片分布已经固定,新节点加入后,ES 默认配置下不会做任何大规模重分布,除非开启 rebalance(默认是开启的),让分片从负载高的节点往负载低的节点迁移。
而节点下线时,情况要复杂得多。如果只是优雅停掉一个节点,ES 会把该节点上的主分片对应的副本提升为主分片,并把缺失的副本重新复制到别的节点。这个过程中,集群健康状态会变成 yellow(有副本未分配)或 red(有主分片未分配),涉及大量跨节点数据拷贝,磁盘 IO 和网络带宽都会飙高。
4.2 分片分配的核心配置
生产环境我一般会关注下面几个配置项:
{ "cluster.routing.allocation.enable": "all", "cluster.routing.allocation.node_concurrent_incoming_recoveries": 2, "cluster.routing.allocation.node_concurrent_outgoing_recoveries": 2, "cluster.routing.allocation.node_initial_primaries_recoveries": 4, "cluster.routing.rebalance.enable": "all", "cluster.routing.allocation.total_shards_per_node": 500 }cluster.routing.allocation.enable:控制分片分配功能是否开启。维护时临时设为none可以阻止分片移动,比较常用。node_concurrent_incoming_recoveries和node_concurrent_outgoing_recoveries:控制单节点上并发恢复的分片数量。设得越大恢复越快,但 IO 压力也越大。数据量大的集群,我会保守一点设为 2。total_shards_per_node:单节点分片总数上限,防止某个节点被分配过多分片导致负载失衡。600 或 1000 是常见值,但要根据节点规格决定。cluster.routing.rebalance.enable:分片自动均衡开关。扩容节点后想让分片自动铺满时保持开启,如果有手动规划需求可以临时关闭。
这些配置的生效范围一般是整个集群,也可以在索引级别覆盖部分参数,但分配策略这类配置最好在集群级别统一管理。
4.3 rebalance 与冷热数据分层
再说说 rebalance 在实际运维中的影响。有一次我在晚上对集群做了一个节点配置变更,ES 开始自动 rebalance,大量分片在不同节点间迁移,结果业务高峰期查询延迟直接翻倍。后来我就养成了习惯:大版本升级、节点扩容这种操作,尽量放到业务低峰期,或者临时把 rebalance 关掉,等手动确认分片分布再开启。
如果要进一步控制数据分布,可以考虑 ES 的冷热架构。热节点使用高性能磁盘(如 SSD),冷节点使用大容量 HDD,通过index.routing.allocation.require或node.attr标签实现对不同索引分片的节点归属控制。这样可以把热数据集中在少数节点上,减少查询跨节点扇出,冷数据则放到廉价的存储上。
5. 分片出问题时的排查清单
5.1 健康状态红黄绿怎么看
ES 集群健康状态用颜色表示,但很多刚入门的朋友只是背了“绿色最好,红色最差”,却不理解颜色的具体含义。
- 绿色:所有主分片和副本分片都正常分配。
- 黄色:所有主分片已分配,但至少有一个副本分片未分配。集群可以正常读写,但存在数据冗余风险。
- 红色:至少有一个主分片未分配。该分片对应的数据不可读写,如果主分片无法恢复,就可能意味着数据丢失。
你可以通过_cluster/health快速查看:
GET _cluster/health返回结果里的status就是健康状态。如果status是黄色,我一般会继续看unassigned_shards的数量,这个字段代表未分配的分片数。
5.2 未分配分片排查路径
未分配分片是运维里最常遇到的问题。我看到黄色或红色后,通常会按以下步骤排查:
先看哪些分片是未分配状态:
GET _cat/shards?v=true这一步会列出所有索引的分片分布和状态,UNASSIGNED的即为未分配。接下来查看未分配的具体原因:
GET _cluster/allocation/explain?pretty这个接口非常关键,它会告诉你某个未分配分片为什么无法分配,常见的 reason 包括:
INDEX_CREATED:索引刚创建,副本还没来得及分配。CLUSTER_RECOVERED:集群从 full cluster restart 中恢复中。NODE_LEFT:节点离开导致分片需要重新分配。REPLICA_ADDED:主动增加副本数后等待分配。ALLOCATION_FAILED:分配失败,通常是因为磁盘不足或分片损坏。NO_ATTEMPT:可能是节点已达到total_shards_per_node上限。
常见原因是磁盘水位线过高。ES 默认当节点磁盘使用率超过 85% 时,不再分配新的副本分片。遇到这种情况,要么清理磁盘、扩容磁盘,要么临时调高水位线配置,但调整时要慎重,以免把节点写满。
5.3 常见坑与运维建议
已在生产环境,主分片数想改怎么办?
我遇到很多团队一上来就遇到这个坑:数据量涨了,觉得主分片数不够,于是想修改主分片数。但主分片数是索引创建时决定的,不能直接改。怎么办?两种主流方案:
- 使用
_reindex把旧索引的数据导入到一个主分片数更多的新索引,然后切别名。 - 使用
splitAPI 将现有索引拆成更多分片。前提是原索引主分片数是 1,或者满足特定拆分比例,且索引是只读状态。
两种方案都涉及全量数据拷贝,时间取决于数据量。如果你能提前预估好分片数,就不用走这一步。
分片很多,但节点上堆内存一直高。
分片本身有独立的 segment 内存映射,分片数量越多,常驻内存占用越大。如果节点堆内存已经到 85% 以上,建议先把分片数降下来,或者给集群加节点。还有一个小技巧:把历史索引定期用 ILM 关闭(close),关闭后的索引不占堆内存,查的时候再临时打开,适合低频访问的老数据。
单分片数据量太大,恢复慢到绝望。
索引有大量分片损坏或者节点宕机恢复时,ES 会复制副本分片、重新分配主分片。单分片数据量越大,重建索引的时间越长。所以前面说的“单分片不要超过 50GB”不只是性能问题,也是数据安全维度的问题——分片小一点,单点故障影响面也小一点。
6. 写在最后:分片机制里最容易被低估的细节
说实话,我自己实操下来体会最深的一点是:分片机制不是一个“建索引时选个数字就完事”的静态设置,它是整个集群运行期都要反复审视的动态因子。
比如你加了新节点,不自动 rebalance 就等于让旧节点持续高负载;你索引建了很多分片,虽然单个节点查询压力低了,但协调节点要处理的结果集合并开销又上去了。ES 官方一直强调的“一个分片就是一个 Lucene 索引”,这句话背后意味着内存、磁盘、线程、网络、恢复时间的多重占用。每增加一个分片,都是给集群增加一份可以度量的资源成本。
最后分享一个我常用的检查习惯:每周固定看一次_cat/shards和_cat/allocation,确认分片分布是否均匀、是否有节点磁盘吃紧、是否有分片一直处于未分配。很多看起来很难排查的“灵异问题”,实际上都是分片分配不均衡埋下的雷。把这个习惯保持下来,你能在故障发生之前就解决掉至少一半的隐患。