ES 分片与路由深度解析
面试官:ES 的分片是什么?
你:ES 将索引划分为多个分片,每个分片是一个独立的 Lucene 实例,分布在集群的不同节点上,实现数据的水平扩展和高可用。
面试官:文档写入时,ES 是怎么决定写到哪个分片的?
链式追问一:分片基础
Section titled “链式追问一:分片基础”Q1:主分片和副本分片的区别?必考
Section titled “Q1:主分片和副本分片的区别?”| 维度 | 主分片(Primary Shard) | 副本分片(Replica Shard) |
|---|---|---|
| 数量 | 创建索引时指定,不可修改 | 可随时修改 |
| 功能 | 处理写入请求 | 处理读取请求,主分片故障时晋升为主 |
| 位置 | 不能与其副本在同一节点 | 分布在不同节点 |
| 默认值 | 1个主分片(ES 7.x+) | 1个副本 |
分片数量的重要性:
- 主分片数量创建后不可修改(因为路由算法依赖分片数)
- 副本数可以随时增减(水平扩展读性能)
- 主分片太少:数据无法充分利用多节点
- 主分片太多:每个分片元数据开销,管理成本高
经验法则:
- 单个分片建议 10~50GB
- 数据量 / 50GB = 参考分片数
- 节点数 × 3 以内(确保每个节点分片数合理)
Q2:ES 的路由算法是什么?高频
Section titled “Q2:ES 的路由算法是什么?”ES 使用以下公式决定文档存入哪个分片:
shard_num = hash(_routing) % number_of_primary_shards_routing:默认是文档的_id,可以自定义number_of_primary_shards:主分片总数(这就是为什么主分片数不能修改!)
自定义路由的应用:
// 写入时指定路由键PUT /orders/_doc/1?routing=user_123{ "user_id": "user_123", "amount": 100}
// 查询时指定路由键(只查特定分片,避免广播查询)GET /orders/_search?routing=user_123{ "query": { "term": { "user_id": "user_123" } }}自定义路由的好处:同一用户的订单写入同一分片,查询时只需访问一个分片(从 N 个节点减少到 1 个),性能提升明显。
Q3:为什么主分片数修改后路由就会混乱?
Section titled “Q3:为什么主分片数修改后路由就会混乱?”假设原来 3 个分片,写入文档时:
Doc1: hash("doc1") % 3 = 1 → 分片 1Doc2: hash("doc2") % 3 = 2 → 分片 2修改为 5 个分片后,查询时:
查 Doc1: hash("doc1") % 5 = 3 → 去分片 3 找 但 Doc1 实际在分片 1!→ 找不到!解决方案:_reindex API 重建索引(创建新索引,用正确的分片数,迁移数据)。
链式追问二:集群与选主
Section titled “链式追问二:集群与选主”Q4:ES 集群的节点角色有哪些?高频
Section titled “Q4:ES 集群的节点角色有哪些?”| 节点角色 | 职责 | 配置 |
|---|---|---|
| Master-eligible | 可以被选为 Master 节点 | node.master: true |
| Master | 管理集群状态(分片分配、节点加入/离开) | 由选举产生 |
| Data | 存储分片数据,执行查询 | node.data: true |
| Coordinating | 接收客户端请求,协调查询 | 所有节点默认都是协调节点 |
| Ingest | 文档预处理管道 | node.ingest: true |
生产环境最佳实践:
- Master 节点:专用,不存数据(通常 3 个,奇数,防脑裂)
- Data 节点:存数据,不参与选主
- Coordinating 节点:接入层,处理客户端请求
Q5:ES 如何防止脑裂(Split-Brain)?高频
Section titled “Q5:ES 如何防止脑裂(Split-Brain)?”脑裂场景:网络分区时,集群分成两半,两半各自选出 Master,出现双 Master,导致数据不一致。
ES 7.x 之前的解决方案:设置 minimum_master_nodes
# 设置为 (master-eligible 节点数 / 2) + 1discovery.zen.minimum_master_nodes: 2# 例如 3 个 master-eligible 节点,设置为 2# 少于 2 个节点的分区无法选出 Master,避免脑裂ES 7.x+ 的解决方案:Raft 共识算法(自动处理,无需手动配置)
新的选主流程基于 Raft: - 超过半数节点同意才能成为 Master - 天然避免脑裂 - 自动维护 cluster.initial_master_nodes链式追问三:跨分片查询
Section titled “链式追问三:跨分片查询”Q6:ES 查询时,Coordinating 节点是怎么协调的?高频
Section titled “Q6:ES 查询时,Coordinating 节点是怎么协调的?”ES 查询分为两个阶段:Query Phase(查询阶段) + Fetch Phase(获取阶段)
Query Phase(scatter-gather 模式):
客户端 → Coordinating 节点 │ ├── 广播查询到所有相关分片(主分片或副本) │ ├── 每个分片本地执行查询 │ └── 返回:匹配文档的 ID + 相关性分数(Top K) │ └── Coordinating 节点合并结果 └── 全局排序,取 Top K(如 from=0, size=10,则取 Top 10)Fetch Phase:
Coordinating 节点知道了 Top K 文档的 ID 和所在分片 └── 只向对应分片发出 GET 请求,获取完整文档内容 └── 合并后返回给客户端深分页问题:
查询 from=9990, size=10(第 1000 页): 每个分片返回本地 Top 10000 条 Coordinating 节点合并 N个分片 × 10000 = 大量数据 → 内存占用大,性能差
解决方案: 1. scroll API(游标,适合导出全量数据) 2. search_after(基于上一页最后一条记录,适合翻页) 3. 限制最大页数(max_result_window 默认 10000)