2. 索引分片与副本策略设计
分片与副本是 Elasticsearch 索引的物理骨架。分片决定数据如何被切分、并行处理能力有多强;副本决定容错能力和读吞吐上限。两者一旦在索引创建时确定,主分片数量便无法再改(只能重建索引),因此这一节的目标是:在写入前就把分片数、副本数、路由方式和分配约束想清楚,避免后期被迫 reindex。
一、分片数量估算:从数据量和堆内存反推
分片不是越多越好。每个分片都是一个独立的 Lucene 实例,占用文件句柄、堆内存和集群状态。经验上,单个分片大小控制在 10GB~50GB 之间较为健康,搜索类场景偏小、日志类场景偏大。估算公式可以写成:
主分片数 ≈ 索引总数据量 ÷ 单分片目标大小
再向上取整到节点数的整数倍,便于均匀分布
例如一个日志索引,按每天 200GB、保留 7 天计算,总量约 1.4TB。若单分片目标 30GB,则 1400 ÷ 30 ≈ 47,取 48 个主分片。若集群有 8 个数据节点,48 正好是 8 的 6 倍,每个节点承载 6 个主分片,分布均匀。反过来,如果只有 3 个节点却设了 100 个分片,每个节点要扛 33 个分片,堆内存和段合并压力会显著上升。
另一个约束来自堆内存。粗略经验是每 GB 堆内存对应 20~30 个分片。一个 30GB 堆的节点,分片总数(含副本)建议不超过 600~900。这个上限比数据量估算更硬,因为它直接关系到集群能否稳定运行。
二、副本配置权衡:可用性、成本与写入放大
副本数(number_of_replicas)的取值需要在三个维度上权衡:
- 可用性:副本为 0 时,任一节点故障都会导致分片不可用;副本为 1 可容忍单节点故障;副本为 2 可容忍双节点同时故障。
- 读吞吐:搜索请求会轮询主分片和副本分片,副本越多,可并行服务的分片副本越多,读 QPS 上限越高。
- 写入成本:每次写入都要同步到所有副本,副本数增加会放大写入网络和磁盘开销,同时占用更多存储。
生产环境的常见选择是副本数为 1:既保证单节点故障不丢数据,又不至于让写入放大过于严重。对读多写少、SLA 要求高的索引,可以设为 2;对可重建的日志类索引,副本 0 或 1 都可接受。副本数可以随时通过 API 调整,这一点比主分片灵活得多:
PUT /logs-2024.06/_settings
{
"index": { "number_of_replicas": 1 }
}
三、路由机制:文档如何落到某个分片
默认情况下,Elasticsearch 用文档 _id 做哈希路由:
shard = hash(_id) % number_of_primary_shards
这意味着相同 _id 的文档总是落到同一分片,保证写入和查询的一致性。但默认路由有两个问题:一是无法控制数据分布,二是无法把相关数据聚到一起。可以通过 routing 参数自定义路由值,例如让同一用户的订单都落到同一分片:
PUT /orders/_doc/1001?routing=user_42
{ "user": "user_42", "amount": 199 }
查询时也要带上相同的 routing,否则会广播到所有分片。自定义路由的代价是可能造成分片倾斜——如果某个 routing 值对应的文档特别多,那个分片就会成为热点。因此 routing 键要选择基数高、分布均匀的字段。
四、分片分配过滤:把分片放到该去的地方
分配过滤通过 index.routing.allocation 系列参数控制分片落在哪些节点上,常用于冷热分层、机架感知和硬件隔离。典型配置:
PUT /logs-2024.06/_settings
{
"index.routing.allocation.require.box_type": "hot",
"index.routing.allocation.exclude._name": "node-old-01"
}
require 是硬约束,不满足则分片无法分配;include 是软偏好;exclude 用于排除节点。冷热架构中,热节点用 SSD 承载写入,冷节点用大容量 HDD 承载历史数据,通过 require.box_type 实现自动迁移。需要注意的是,硬约束过严可能导致分片无法分配(unassigned),此时集群健康会变黄甚至变红,应配合 _cluster/allocation/explain 排查。
五、热点分片与脑裂的规避
热点分片通常来自三种原因:分片数过少导致单分片过大、routing 键分布不均、以及写入集中在少数分片。应对手段包括:增加主分片数、改用更均匀的 routing 键、使用 index.routing_partition_size 让路由在多个分片间再散列,以及通过 ILM 滚动索引把新数据写入新索引。
脑裂是集群中出现多个 master 各自认为自己是主节点,导致元数据不一致。根本原因是 master 选举时无法确认自己是否仍拥有多数派。规避方法是设置 discovery.zen.minimum_master_nodes(7.x 之前)或使用 7.x 之后的自动 quorum 机制,并保证 master 候选节点数为奇数。生产环境建议至少 3 个专用 master 节点,避免数据节点兼任 master 带来的选举抖动。
把上述四件事串起来看:先用数据量和堆内存定下主分片数,再用可用性与成本定下副本数,然后用 routing 控制数据落点,最后用分配过滤把分片约束到合适的节点上。热点和脑裂不是独立问题,而是这四个决策没做对时的症状。索引创建前多花十分钟估算,往往能省下后期数小时的 reindex 和故障排查。