3. 索引与分片设计
分片设计是 Elasticsearch 容量治理中最容易被低估、却最难事后补救的一环。索引一旦创建,主分片数量就固定了,后续只能通过 _reindex、_shrink 或 _split 重建,代价极高。因此分片设计必须在建索引之前完成估算,而不是等集群变慢再回头调整。
一、分片数量与大小估算
分片数量的核心矛盾是:分片太少,单个分片过大,恢复慢、查询并行度低;分片太多,集群元数据膨胀、段合并压力大、协调节点开销上升。业界较为稳妥的经验区间是:单个分片大小控制在 10GB~50GB,单节点承载的分片数控制在 20 个/GB 堆内存以内,例如 30GB 堆内存的节点,建议不超过 600 个分片。
估算流程可以按以下顺序推进。假设某日志索引每日写入约 200GB 原始数据,副本数为 1,保留 30 天:
原始日增量:200 GB
副本因子:1(即每份主分片额外存 1 份副本)
总存储需求:200 GB × 30 天 × (1 + 1) = 12000 GB
按单分片 30 GB 估算主分片数:
12000 GB ÷ 2(副本占一半)÷ 30 GB ≈ 200 个主分片
若集群有 10 个数据节点,则每节点约 20 个主分片 + 20 个副本 = 40 个分片
这里要注意:分片大小应按主分片实际存储量计算,副本不参与“单分片大小”的判断,但参与总容量和分片总数计算。上面的 200 个主分片是示例估算值,实际还要结合写入速率、查询模式、节点数和堆内存做修正。如果单日索引按天滚动,则每天索引可设为 7 个主分片(200GB ÷ 30GB ≈ 6.7,向上取整为 7),这样每个索引分片数固定,便于按天管理生命周期。
二、主分片与副本分片的权衡
主分片决定索引的并行写入能力和数据分布粒度,副本分片决定高可用与读吞吐。两者不是简单的“越多越好”,而是要在写入、查询、存储、恢复四个维度上权衡。
| 维度 | 增加主分片 | 增加副本分片 |
|---|---|---|
| 写入吞吐 | 提升并行度,但协调开销增加 | 降低(每次写入需同步更多副本) |
| 查询吞吐 | 提升并行度,但小分片合并开销大 | 提升(副本可分担读请求) |
| 存储成本 | 基本不变 | 线性增加 |
| 故障恢复 | 分片越小恢复越快 | 副本越多,恢复源越多,但占用更多资源 |
实践中的常见配置是:日志类索引副本数设为 1,兼顾可用性与写入成本;搜索类索引副本数设为 1~2,用副本分担读流量;对写入吞吐要求极高的场景可临时将副本设为 0,写入完成后再调回 1,但期间无高可用保障。副本数可以动态调整,主分片数不能,因此主分片数要一次估准,副本数可以按阶段调整。
三、路由与分片分配策略
Elasticsearch 默认使用 _id 的哈希值对主分片数取模来决定文档落在哪个分片,公式为:shard = hash(_id) % number_of_primary_shards。这意味着主分片数一旦确定就不能改,否则所有文档的路由位置都会变化。
当需要控制数据分布时,可以使用自定义路由。例如多租户场景中,把同一租户的数据路由到同一分片,既能减少查询时的分片扇出,又能让租户级操作更集中:
PUT /orders/_doc/1001?routing=tenant_A
{
"tenant": "tenant_A",
"amount": 199.00
}
GET /orders/_search?routing=tenant_A
{
"query": { "term": { "tenant": "tenant_A" } }
}
自定义路由的代价是可能造成分片倾斜:如果某个 routing 值的数据量远大于其他值,对应分片就会成为热点。因此自定义路由必须配合数据量监控,必要时通过 _split 或重建索引来重新分布。
分片分配层面,可以通过 index.routing.allocation 控制分片落在哪些节点上。例如把热数据索引绑定到高配节点,把冷数据索引绑定到低配节点:
PUT /logs-2024-06/_settings
{
"index.routing.allocation.require.box_type": "hot"
}
这类策略通常与冷热分层、节点属性标签配合使用,是容量治理中控制资源占用的重要手段。
四、避免热点分片的实战方法
热点分片表现为:某些分片的 CPU、磁盘 I/O 或查询延迟显著高于其他分片,导致集群整体吞吐被拖慢。常见原因有三类:路由倾斜、时间序列写入集中、分片数过少导致单分片过大。
针对时间序列数据,最有效的做法是使用滚动索引 + 别名,让写入始终落在最新索引上,同时通过 ILM 自动滚动、迁移和删除。这样每个索引的分片数固定,写入压力被分散到多个索引,而不是集中在一个超大索引的少数分片上。
PUT _ilm/policy/logs_policy
{
"policy": {
"phases": {
"hot": { "actions": { "rollover": { "max_size": "50gb", "max_age": "1d" } } },
"warm": { "actions": { "forcemerge": { "max_num_segments": 1 } } },
"delete": { "actions": { "delete": { "delete_searchable_snapshot": true } } }
}
}
}
针对路由倾斜,可以定期检查各分片的文档数和存储大小,发现异常分片后,通过调整 routing 策略或重建索引来重新分布。针对分片数过少,则应在建索引时按前述估算方法预留足够的主分片数,避免后期无法拆分。
_cat/shards?v 中的 store 和 docs 两列,再结合 _nodes/stats 中的 indices.search.query_time_in_millis 和 indices.indexing.index_time_in_millis 定位是读热点还是写热点。只有先确认热点类型,才能选择正确的治理手段。
最后强调一个判断顺序:先看 _cat/shards?v 确认是否存在明显偏大的分片,再看 _nodes/stats 确认热点是读还是写,最后才决定是调整路由、滚动索引还是重建分片。分片设计没有万能公式,但“先估算、再权衡、后监控”的顺序可以避免绝大多数不可逆的容量事故。