深度解析 Elasticsearch 搜索过程:Query Then Fetch 两阶段详解
深度解析 Elasticsearch 搜索过程Query Then Fetch 两阶段详解前言一、搜索流程全景图1.1 两阶段概览1.2 为什么需要两个阶段二、示例集群环境三、第一阶段Query 阶段3.1 步骤一协调节点广播请求3.2 步骤二每个分片本地查询3.3 步骤三协调节点合并排序四、第二阶段Fetch 阶段4.1 步骤一协调节点发起 MultiGet 请求4.2 步骤二分片返回完整文档4.3 步骤三协调节点组装并返回五、深度剖析关键设计细节5.1 为什么 Query 阶段返回 fromsize 条而非只返回 size 条5.2 深度分页问题5.3 分片选择策略主 vs 副本5.4 搜索类型已废弃六、完整时序图七、性能优化建议八、常见面试题Q1Query 阶段和 Fetch 阶段各自做了什么Q2为什么需要两个阶段不能合二为一Q3什么是深度分页问题如何解决Q4协调节点如何决定查询主分片还是副本分片九、总结十、面试加分回答)The Begin点点关注收藏不迷路前言搜索是 Elasticsearch 最核心的功能之一但很多开发者对 ES 内部如何执行搜索请求一知半解。为什么搜索分为两个阶段协调节点做了什么分片如何返回结果本文将围绕官方定义的“Query Then Fetch”两阶段模型逐步拆解分布式搜索的完整流程。一、搜索流程全景图1.1 两阶段概览┌─────────────────────────────────────────────────────────────────────┐ │ 客户端发送搜索请求 │ │ GET /my_index/_search?qkeyword │ └─────────────────────────────────┬───────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 【第一阶段Query 阶段】 │ │ 目的定位到哪些文档匹配但不返回文档内容 │ │ │ │ 1. 协调节点将请求广播到所有分片主或副本 │ │ 2. 每个分片本地查询返回文档ID 排序值到优先队列 │ │ 3. 协调节点合并各分片结果生成全局排序列表 │ └─────────────────────────────────┬───────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 【第二阶段Fetch 阶段】 │ │ 目的根据 Query 阶段得到的文档ID获取完整的文档内容 │ │ │ │ 1. 协调节点向相关分片发送 MultiGet 请求 │ │ 2. 分片返回完整文档内容 │ │ 3. 协调节点组装结果返回给客户端 │ └─────────────────────────────────────────────────────────────────────┘1.2 为什么需要两个阶段如果合并为一个阶段两阶段分离的好处每个分片返回完整文档 → 大量网络传输Query 阶段只传ID和排序值数据量极小协调节点需要丢弃超出size的文档Fetch 阶段只取最终需要的文档无法做全局排序先全局排序再按需获取一句话总结先定位后取数避免网络和内存浪费。二、示例集群环境为便于理解设定以下集群状态配置项值索引名my_index主分片数5副本数1总分片数105主 5副本文档总数10,000 条搜索请求GET/my_index/_search{from:0,size:10,query:{match:{title:elasticsearch}},sort:[{_score:desc}]}三、第一阶段Query 阶段3.1 步骤一协调节点广播请求┌─────────────────┐ │ 客户端 │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ 协调节点 │ │ (接收搜索请求) │ └────────┬────────┘ │ ┌──────────────┬───────────────┼───────────────┬──────────────┐ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 分片0 │ │ 分片1 │ │ 分片2 │ │ 分片3 │ │ 分片4 │ │ (主/副本) │ │ (主/副本) │ │ (主/副本) │ │ (主/副本) │ │ (主/副本) │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘关键要点协调节点向所有分片发送请求每个分片只命中主或副本中的一个采用轮询策略选择主还是副本负载均衡请求参数完全相同query、from、size、sort3.2 步骤二每个分片本地查询每个分片收到请求后独立执行以下操作┌─────────────────────────────────────────────────────────────────┐ │ 单个分片内部处理流程 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 1. 解析查询语句 → 生成 Lucene Query │ │ ▼ │ │ 2. 遍历倒排索引 → 找到匹配的文档ID列表 │ │ ▼ │ │ 3. 计算每个文档的 _score相关性评分 │ │ ▼ │ │ 4. 按排序字段排序默认按 _score 降序 │ │ ▼ │ │ 5. 截取 [from, fromsize] 范围的文档本地优先队列 │ │ ▼ │ │ 6. 返回 (文档ID 排序值) 给协调节点不返回文档内容 │ │ │ └─────────────────────────────────────────────────────────────────┘示例假设每个分片有 2000 条匹配文档from0, size10每个分片只返回前 10 条按排序值。// 每个分片返回给协调节点的数据格式{shard:0,hits:[{_id:doc_123,_score:9.5,sort_values:[9.5]},{_id:doc_456,_score:9.2,sort_values:[9.2]},...{_id:doc_789,_score:8.1,sort_values:[8.1]}// 共10条]}3.3 步骤三协调节点合并排序协调节点收到所有分片的返回结果后┌─────────────────────────────────────────────────────────────────┐ │ 协调节点合并流程 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 输入5个分片 × 10条结果 50条候选记录 │ │ ▼ │ │ 1. 将所有50条记录放入全局优先队列 │ │ ▼ │ │ 2. 按排序规则重新排序_score 降序 │ │ ▼ │ │ 3. 截取 [0, 10] 条即最终需要的10条文档 │ │ ▼ │ │ 4. 记录这10条文档分别来自哪个分片 │ │ 文档A → 分片0 │ │ 文档B → 分片2 │ │ 文档C → 分片1 │ │ ... │ │ │ └─────────────────────────────────────────────────────────────────┘此时 Query 阶段结束协调节点知道哪 10 条文档需要返回每条文档所在的分片位置四、第二阶段Fetch 阶段4.1 步骤一协调节点发起 MultiGet 请求协调节点根据 Query 阶段的结果向相关分片批量获取文档内容协调节点 │ ┌──────────────────┼──────────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ 分片0 │ │ 分片1 │ │ 分片2 │ │ 需要文档 │ │ 需要文档 │ │ 需要文档 │ │ [A, D] │ │ [C, E] │ │ [B, F] │ └─────────┘ └─────────┘ └─────────┘// 协调节点发送的 MultiGet 请求示例GET/_mget{docs:[{_index:my_index,_id:doc_A,_shard:0},{_index:my_index,_id:doc_D,_shard:0},{_index:my_index,_id:doc_C,_shard:1},...]}4.2 步骤二分片返回完整文档每个分片从Lucene 段或Translog中读取完整的文档源_source// 分片返回的文档内容{_id:doc_A,_index:my_index,_score:9.5,_source:{title:Elasticsearch 入门教程,content:本文介绍 Elasticsearch 的基本概念...,timestamp:2026-01-26T10:00:00Z}}4.3 步骤三协调节点组装并返回协调节点收集所有文档按 Query 阶段排好的顺序组装返回给客户端{took:15,timed_out:false,_shards:{total:10,successful:10,skipped:0,failed:0},hits:{total:{value:10000,relation:eq},max_score:9.5,hits:[{_index:my_index,_id:doc_A,_score:9.5,_source:{title:Elasticsearch 入门教程,...}},{_index:my_index,_id:doc_B,_score:9.3,_source:{title:Elasticsearch 高级查询,...}}// ... 共10条]}}五、深度剖析关键设计细节5.1 为什么 Query 阶段返回fromsize条而非只返回size条如果只返回 size 条实际做法返回 fromsize 条每个分片返回前 10 条每个分片返回前 50 条from0, size10 → 01010等等纠正实际上每个分片返回from size条而不是size条原因假设from90, size10全局第 91-100 条文档可能分散在各分片的前 100 条中。如果每个分片只返回 10 条会丢失数据。示例计算from 90, size 10 每个分片需要返回from size 100 条本地排序后的前100条 协调节点收到5个分片 × 100条 500条候选记录 ↓ 全局排序后取 [90, 100) 共10条5.2 深度分页问题上述机制导致深度分页性能极差页码from每个分片返回数协调节点处理数第1页0105×1050第10页901005×100500第100页99010005×10005000第1000页9990100005×1000050000解决方案Search After基于上一页最后一条文档的排序值继续查询Scroll API用于大量数据导出不再推荐Point in Time (PIT)ES 7.10 引入的游标机制5.3 分片选择策略主 vs 副本协调节点决定每个分片查询主还是副本遵循负载均衡原则默认策略自适应副本选择Adaptive Replica Selection - 优先选择响应最快的节点 - 考虑节点历史延迟和队列长度好处分摊主节点压力提升查询吞吐量5.4 搜索类型已废弃早期 ES 支持多种搜索类型现已统一为query_then_fetch搜索类型说明状态query_and_fetch查询取回合并废弃dfs_query_then_fetch全局词频计算废弃默认已优化query_then_fetch标准两阶段当前唯一六、完整时序图客户端 协调节点 分片0(主) 分片1(副本) 分片2(主) ... │ │ │ │ │ │──搜索请求────▶│ │ │ │ │ │ │ │ │ │ │──Query请求───▶│ │ │ │ │──Query请求──────────────────▶│ │ │ │──Query请求──────────────────────────────────▶│ │ │ │ │ │ │ │◀──返回ID列表──│ │ │ │ │◀──返回ID列表──────────────────│ │ │ │◀──返回ID列表──────────────────────────────────│ │ │ │ │ │ │ │ (合并排序) │ │ │ │ │ │ │ │ │ │──Fetch请求────▶│ │ │ │ │──Fetch请求──────────────────▶│ │ │ │ │ │ │ │ │◀──完整文档────│ │ │ │ │◀──完整文档──────────────────│ │ │ │ │ │ │ │◀──最终结果────│ │ │ │七、性能优化建议优化点建议效果控制返回字段使用_source只返回必要字段减少网络传输避免 depth 分页使用search_after替代from/size性能提升 10-100x合理设置分片数单分片 30-50GB避免过度分片使用索引排序index.sort减少排序开销查询提速 30%开启自适应副本默认已开启负载更均衡八、常见面试题Q1Query 阶段和 Fetch 阶段各自做了什么回答Query 阶段协调节点将搜索请求广播到所有分片每个分片本地查询后返回文档ID 排序值不返回文档内容。协调节点合并所有结果生成全局排序列表确定最终需要获取的文档ID及所在分片。Fetch 阶段协调节点向相关分片发送MultiGet 批量请求获取完整文档内容_source组装后返回给客户端。Q2为什么需要两个阶段不能合二为一回答如果合为一个阶段每个分片会返回大量完整文档造成巨大的网络开销。例如from990, size10每个分片需要返回 1000 条完整文档5 个分片就是 5000 条但最终只取 10 条。两阶段分离后Query 阶段只传轻量级的 ID 和排序值约 100 字节/条Fetch 阶段只取最终的 10 条效率大幅提升。Q3什么是深度分页问题如何解决回答深度分页指跳转到很深页码如第 1000 页的情况。由于 Query 阶段每个分片必须返回fromsize条记录翻页越深协调节点处理的数据量越大第 1000 页需要每个分片返回 10000 条5 分片处理 50000 条。解决方案Search After使用上一页最后一条文档的排序值像游标一样向前翻页Point in Time (PIT)固定时间点的快照视图结合 search_after 使用Scroll API适合大量数据导出已不推荐用于实时搜索Q4协调节点如何决定查询主分片还是副本分片回答ES 7.x 后默认使用自适应副本选择Adaptive Replica Selection协调节点会维护每个节点的响应延迟、历史失败率、搜索线程池队列长度等指标动态选择当前最优的节点主或副本发送请求。这样既实现了负载均衡又能自动避开慢节点。九、总结维度Query 阶段Fetch 阶段目的定位匹配文档获取完整内容传输数据文档ID 排序值完整_source数据量小每个分片fromsize条大仅size条涉及的节点所有分片只包含最终文档的分片关键操作倒排索引检索本地排序Lucene 读取_source时间复杂度O(分片数 × 查询开销)O(文档条数)核心要点搜索采用Query Then Fetch两阶段模型Query 阶段坐标 不取→ 轻量级定位Fetch 阶段取数 组装→ 按需获取深度分页用Search After替代from/size协调节点通过自适应副本选择实现负载均衡十、面试加分回答面试官请详细描述 Elasticsearch 的搜索过程。候选人“ES 的搜索采用Query Then Fetch两阶段模型。Query 阶段协调节点将请求广播到所有分片主或副本之一。每个分片独立执行查询从倒排索引中找到匹配文档计算_score并按排序字段截取fromsize条结果只返回文档ID和排序值给协调节点。协调节点合并所有分片结果进行全局排序最终确定需要返回的size条文档及其所在分片。这一阶段的核心价值是轻量级传输避免早期传输大量无用数据。Fetch 阶段协调节点向包含目标文档的分片发送 MultiGet 批量请求获取完整的_source内容按 Query 阶段排好的顺序组装返回给客户端。这里有一个关键设计每个分片必须返回fromsize条记录而非size条因为全局第 N 条可能来自任意分片的前 N 条。这也导致了深度分页问题解决方案是使用Search After Point in Time代替传统的 from/size 翻页。另外ES 通过自适应副本选择来决定查询主还是副本优先选择响应最快的节点兼顾负载均衡和性能。”The End点点关注收藏不迷路
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2557218.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!