第三章:索引分片设计——主分片与副本分片原理、分片大小与数量估算公式、Routing路由机制
分片设计,说白了就是ES集群的“地基工程”。
我见过太多团队,上来就建索引,分片数随便填个5或者10。结果数据量一上来,集群直接崩了。嗯,今天咱们就把这块彻底讲透。
3.1 主分片与副本分片:ES的“主从复制”机制
先搞清楚一个概念:分片(Shard)是ES数据存储的最小单元。每个分片本质上就是一个Lucene索引。
主分片(Primary Shard):数据写入的第一站。你往ES里写一条文档,它先落到主分片上。
副本分片(Replica Shard):主分片的“影子”。主分片写完了,数据同步到副本分片。副本分片可以分担读请求。
核心原则:
- 主分片数量在索引创建时固定,后续不可修改
- 副本分片数量可以动态调整
- 主分片和它的副本不会落在同一台节点上
为什么会这样设计?
你想想看,如果主分片和副本在同一台机器上,那台机器挂了,数据不就全丢了吗?ES的容错机制就是靠“分散”来实现的。
我记得有一次线上事故,某团队把副本数设为0,说是“为了省磁盘”。结果一台节点宕机,索引直接变红,数据恢复花了整整两天。嗯,这就是典型的“省小钱亏大钱”。
3.2 分片大小与数量:到底设多少才合适?
这是面试高频题,也是实战中最容易踩坑的地方。
3.2.1 分片大小的黄金法则
我个人习惯遵循一个经验值:每个分片20GB-50GB。
为什么是这个范围?
- 分片太小(<10GB):分片数量过多,集群管理开销大,查询时需要合并太多结果
- 分片太大(>50GB):Lucene的段合并压力大,故障恢复慢
我的实战经验:
日志类场景,我一般控制在30GB左右。搜索类场景,可以放宽到40-50GB。如果数据有TTL(定期删除),建议偏小,20GB左右,方便滚动索引。
3.2.2 分片数量估算公式
直接上公式,别整那些虚的:
主分片数 = 预期数据总量 / 目标分片大小
举例:
预期数据总量 = 500GB
目标分片大小 = 30GB
主分片数 = 500 / 30 ≈ 17
建议向上取整,留点余量 → 20个主分片
副本分片数怎么定?
- 高可用要求高:副本数=2(即总共3份数据)
- 一般场景:副本数=1(总共2份数据)
- 读多写少场景:副本数可以到3-5,提升查询吞吐
注意:
总分片数 = 主分片数 × (1 + 副本数)
这个总数不要超过集群节点数的20倍。比如你有10个节点,总分片数最好控制在200以内。否则集群的元数据管理会成为瓶颈。
我曾经接手过一个集群,单索引分了100个主分片,副本数2,总分片300个。但集群只有5个节点。结果每次集群重启,元数据恢复要半小时。嗯,这就是典型的“分片过多综合征”。
3.3 Routing路由机制:数据到底落在哪个分片上?
ES默认的路由规则很简单:
shard_num = hash(_routing) % num_primary_shards
默认情况下,_routing就是文档的_id。所以同一个_id的文档,永远落在同一个分片上。
但问题来了——如果你做批量查询,比如查询某个用户的所有订单,默认路由会导致请求广播到所有分片,然后再聚合。效率很低。
3.3.1 自定义路由:把相关数据“粘”在一起
我们可以指定一个字段作为路由键:
PUT my_index/_doc/1?routing=user_123
{
"user_id": "user_123",
"order_id": "order_001",
"amount": 99.9
}
PUT my_index/_doc/2?routing=user_123
{
"user_id": "user_123",
"order_id": "order_002",
"amount": 199.9
}
这样,user_123的所有订单都落在同一个分片上。查询时指定路由:
GET my_index/_search?routing=user_123
{
"query": {
"term": { "user_id": "user_123" }
}
}
查询只访问一个分片,速度能快10倍以上。
避坑指南:
我曾经犯过一个错——用了自定义路由,但忘了在查询时也带上routing参数。结果查询走了广播,数据没查到(因为路由不一致)。
解决方案:建索引时设置 routing_partition_size,或者干脆在mapping里强制要求所有请求必须带routing。
3.3.2 路由的副作用:数据倾斜
自定义路由有个坑——如果某个路由键的数据量特别大(比如大V用户有100万条订单),那个分片就会变成“热分片”。
怎么解决?
- 使用复合路由键:比如 user_id + date,把数据打散到多个分片
- 或者用routing_partition_size:让一个路由键的数据分布到多个分片上
PUT my_index
{
"settings": {
"index.number_of_routing_shards": 6,
"index.routing_partition_size": 2
}
}
这样,同一个user_id的数据会落在2个分片上,既保留了路由的局部性,又避免了单分片过热。
3.4 实战总结:分片设计检查清单
| 决策项 | 建议值 | 说明 |
|---|---|---|
| 单分片大小 | 20-50GB | 日志类偏小,搜索类偏大 |
| 主分片数 | 数据总量 / 目标分片大小 | 向上取整,留余量 |
| 副本数 | 1-2(生产环境至少1) | 读多写多可适当增加 |
| 总分片数 | ≤ 节点数 × 20 | 避免元数据膨胀 |
| 是否使用自定义路由 | 按业务场景决定 | 注意数据倾斜和查询一致性 |
最后说一句:分片设计没有“银弹”。我每次建新索引前,都会先估算半年到一年的数据量,再反推分片数。宁可多留点余量,也别让分片数太少导致后期无法扩展。
嗯,这一章就到这里。下一章咱们聊聊索引生命周期管理——数据怎么自动滚、自动删、自动冷热分离。