基于本文回答
0
评论

Elasticsearch 读取/搜索文档(Read)的流程

知识点图片

Elasticsearch 的读取操作主要分为两种场景,流程有很大的区别:

  1. 根据 ID 查询文档 (Get by ID):这是最简单的“读取”操作,类似于数据库的主键查询。
  2. 搜索文档 (Search):这是复杂的查询操作(如关键词搜索、聚合),涉及“Query Then Fetch”两个阶段。

以下是这两种流程的详细解析。


一、 根据 ID 读取文档 (Get Workflow)

当你发送 GET /index_name/_doc/id 请求时,Elasticsearch 可以通过哈希计算直接定位到文档所在的分片。

核心公式:
shard=hash(routing)%number_of_primary_shardsshard = hash(routing) \% number\_of\_primary\_shards
(默认情况下,routing 就是文档的 ID)

详细步骤:

  1. 客户端请求:客户端向集群中的任意节点发送读取请求。接收请求的这个节点成为协调节点 (Coordinating Node)
  2. 路由计算:协调节点根据文档 ID(或指定的 routing 值)进行哈希计算,确定该文档属于哪个主分片 (Primary Shard)
  3. 负载均衡
    • 协调节点查看集群状态,找到该主分片及其所有副本分片 (Replica Shards) 所在的节点。
    • 为了负载均衡,协调节点通常会使用轮询 (Round-Robin) 算法在主分片和副本分片中选择一个节点(例如,这次选副本,下次选主分片)。
  4. 转发请求:协调节点将请求转发给持有目标分片的节点。
  5. 检索数据:目标节点在本地索引中查找文档,如果找到,将文档数据返回给协调节点。
  6. 返回结果:协调节点将最终结果返回给客户端。

特点:快速、直接,不需要查询所有分片。


二、 搜索文档 (Search Workflow)

当你发送 POST /index_name/_search 请求时,协调节点不知道数据在哪里,因此需要查询所有分片。这个过程被称为 "Query Then Fetch" (先查询后取回),分为两个阶段。

第一阶段:查询阶段 (Query Phase)

目的是找到“哪些文档匹配”以及“它们的排序如何”,只返回 ID 和分数,不返回具体内容

  1. 客户端请求:客户端向协调节点发送搜索请求。
  2. 广播请求:协调节点将搜索请求转发给该索引的所有分片(Primary 或 Replica 均可,每个分片组选一个即可)。
  3. 本地搜索
    • 每个分片在本地执行查询。
    • 每个分片会计算文档的评分 (Score),并创建一个优先队列 (Priority Queue)。
    • 如果客户端请求 from=0, size=10,每个分片都会返回它本地的前 10 条结果的元数据(主要是 文档 IDScore),不会返回 _source 原始内容。
  4. 合并结果
    • 所有分片将结果返回给协调节点。
    • 协调节点将所有分片的结果(假设有 5 个分片,每个返回 10 条,共 50 条)合并到一个全局的优先队列中。
    • 协调节点进行全局排序,选出最终的 Top 10。

第二阶段:取回阶段 (Fetch Phase)

目的是根据第一阶段拿到的 ID,去获取真正的文档内容 (_source)。

  1. 定位文档:协调节点现在知道了最终要返回的 10 个文档的 ID,以及它们分别位于哪些分片上。
  2. 发送 MGET:协调节点向持有这些文档的分片发送 Multi-GET 请求。
  3. 读取内容:各个分片根据 ID 读取文档的 _source 内容,并返回给协调节点。
  4. 最终响应:协调节点收集所有文档内容,拼接成最终的 JSON 响应,返回给客户端。

三、 流程对比与性能隐患

特性 Get (按 ID 读取) Search (搜索)
定位方式 哈希计算,直接定位 广播到所有分片
涉及节点 仅涉及持有目标数据的 1 个节点 涉及所有分片所在的节点
阶段 单一阶段 Query 阶段 + Fetch 阶段
性能 极快 (O(1)) 较慢,取决于数据量和分片数

关键性能问题:深度分页 (Deep Paging)

在搜索流程中,如果请求是 from=10000, size=10 (即获取第 10001 到 10010 条数据):

  1. 每个分片都必须查询出本地的前 10010 条数据(排序并保存在内存中)。
  2. 假设有 5 个分片,协调节点会收到 5×10010=500505 \times 10010 = 50050 条记录。
  3. 协调节点必须在内存中对这 50050 条记录进行全局排序,然后丢弃前 10000 条,只取最后 10 条。

后果:这对内存和 CPU 的消耗非常大。因此,Elasticsearch 默认限制 max_result_window 为 10,000,防止深度分页导致集群崩溃。如果需要深度分页,建议使用 search_afterScroll API。

总结图示

plaintext
[Client]
   |
   v
[Coordinating Node]  <-- 接收请求
   |
   +--- (1. Query Phase: 广播) ---> [Shard A] (返回 ID+Score)
   |                                [Shard B] (返回 ID+Score)
   |                                [Shard C] (返回 ID+Score)
   |
   | (2. Merge & Sort: 确定最终 Top N)
   |
   +--- (3. Fetch Phase: 定向抓取) -> [Shard A] (请求具体 _source)
   |                                [Shard C] (请求具体 _source)
   |
   v
[Client] <-- 返回最终 JSON
右滑查看面试常问