Skip to content

ES 分片与路由深度解析

面试官:ES 的分片是什么?

:ES 将索引划分为多个分片,每个分片是一个独立的 Lucene 实例,分布在集群的不同节点上,实现数据的水平扩展和高可用。

面试官:文档写入时,ES 是怎么决定写到哪个分片的?


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 → 分片 1
Doc2: hash("doc2") % 3 = 2 → 分片 2

修改为 5 个分片后,查询时:

查 Doc1: hash("doc1") % 5 = 3 → 去分片 3 找
但 Doc1 实际在分片 1!→ 找不到!

解决方案_reindex API 重建索引(创建新索引,用正确的分片数,迁移数据)。


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) + 1
discovery.zen.minimum_master_nodes: 2
# 例如 3 个 master-eligible 节点,设置为 2
# 少于 2 个节点的分区无法选出 Master,避免脑裂

ES 7.x+ 的解决方案:Raft 共识算法(自动处理,无需手动配置)

新的选主流程基于 Raft:
- 超过半数节点同意才能成为 Master
- 天然避免脑裂
- 自动维护 cluster.initial_master_nodes

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)