当前位置: 首页 > news >正文

【Elasticsearch从入门到精通】第45篇:Elasticsearch分布式检索原理——Query Then Fetch两阶段搜索

上一篇【第44篇】Elasticsearch分布式索引原理——分片路由与写入流程
下一篇【第46篇】Elasticsearch分布式文档更新原理


摘要

分布式检索是Elasticsearch应对海量数据场景的核心能力,但其原理远比单机搜索复杂。本文以Query Then Fetch两阶段模型为主线,首先详解Query阶段:协调节点如何将搜索请求广播到所有分片,各分片独立执行本地搜索后返回Top N文档的ID与分数,协调节点再合并全局排序;然后深入Fetch阶段:协调节点如何根据全局Top N结果向目标分片发起MultiGet请求获取完整文档内容。通过完整的API示例和ASCII流程图,读者将透彻理解两阶段搜索的数据流向和网络开销。此外,本文还深入探讨分布式环境下的相关性评分问题——各分片独立统计IDF导致的评分不一致,以及DFS搜索类型的解决策略与代价。关键词:Elasticsearch分布式搜索、Query Then Fetch、DFS搜索、相关性评分、两阶段搜索。


一、分布式检索的挑战

在单机环境下,搜索过程非常直观:Lucene在本地索引中查找匹配文档并按照相关性评分排序返回。但在分布式环境下,索引被拆分成了多个分片,分散在不同的节点上。这就引出了一个核心问题:

如何在一个由多个分片组成的分布式索引中执行搜索并返回全局正确的排序结果?

简单方案是不可行的——我们不能简单地将所有分片的数据全部拉取到协调节点再排序,因为全网数据量可能达到TB级别,全量数据传输会直接耗尽网络带宽和内存。

Elasticsearch采用了一种精巧的两阶段策略:Query Then Fetch


二、Query Then Fetch 两阶段搜索模型

2.1 架构总览

┌──────────────────────────┐ │ 客户端 (Client) │ └───────────┬──────────────┘ │ ① 发送搜索请求 ▼ ┌──────────────────────────┐ │ 协调节点 (Node 1) │ │ - 接收请求 │ │ - 路由分发 │ │ - 结果合并 │ │ - 全局排序 │ └────┬───────┬───────┬─────┘ ②广播│ │ │②广播 ┌──────▼──┐ ┌──▼──────┐ ┌──▼──────┐ │ Node 2 │ │ Node 3 │ │ Node 4 │ │ Shard 0 │ │ Shard 1 │ │ Shard 2 │ │ (主分片) │ │ (主分片) │ │ (主分片) │ └─────────┘ └─────────┘ └─────────┘ │ │ │ ③返回 ③返回 ③返回 top N top N top N (ID+分数) (ID+分数) (ID+分数) │ │ │ └─────┬─────┴─────┬─────┘ │ │ ▼ │ ┌────────────────────┴─────┐ │ 协调节点 (Node 1) │ │ ④ 全局归并排序 │ │ ⑤ 确定最终Top N │ └──────────┬───────────────┘ │ ⑥ 向目标分片请求完整文档 ▼ ┌──────────┴───────────────┐ │ 协调节点 → 目标分片 │ │ ⑦ Fetch阶段 │ │ - 获取完整_source │ │ - 组装最终结果 │ └──────────┬───────────────┘ │ ⑧ 返回结果 ▼ ┌──────────────────────────┐ │ 客户端 (Client) │ └──────────────────────────┘

2.2 两阶段的核心设计哲学

Query Then Fetch模型的设计目标是在保证结果正确性的前提下最小化网络传输开销

  • Query阶段:只传输文档ID和评分(几十字节),不传输完整的_source(可能几KB到几MB)
  • Fetch阶段:仅对最终确定需要返回的少量文档获取完整内容

这样,分片→协调节点→目标分片的两轮通信虽然增加了请求次数,但极大地减少了数据传输总量。


三、Query阶段详解

3.1 步骤分解

Query阶段完成从"用户查询"到"全局排序文档ID列表"的转换。具体包括以下子步骤:

Query阶段内部流程: ① 协调节点接收查询请求 - 解析DSL查询语句 - 确定搜索涉及的所有分片(主分片或副本分片的活跃列表) ② 协调节点向每个目标分片广播查询请求 - 每个分片收到的是一个完整的、独立的查询 - 请求中包含 size=from+size(确保获取足够多结果) ③ 每个分片独立执行本地搜索 ┌──────────────────────────────────────────┐ │ 分片本地搜索步骤: │ │ a. 查询语法分析(Query Parsing) │ │ b. 倒排索引查找(Term Lookup) │ │ c. 文档列表合并(AND/OR/交集/并集) │ │ d. 相关性评分计算(独立计算,可能不准确) │ │ e. 返回top(from+size)个文档的ID+评分 │ └──────────────────────────────────────────┘ ④ 协调节点收集所有分片的返回结果 - 假设有5个分片,每个返回from+size=20个文档 - 协调节点收到 5×20=100 个文档的ID+评分 ⑤ 协调节点进行全局归并排序 - 将100个文档按评分降序排列 - 截取最终的from+size条(如第0-9条)

3.2 Query阶段的代码体现

// 发起一个跨分片搜索请求GET/shop/_search{"from":0,"size":10,"query":{"match":{"title":"iPhone 14"}},"sort":[{"_score":"desc"}]}// 在这个查询中:// - 如果shop索引有5个主分片// - Query阶段每个分片返回 top 10 个文档的(_id + _score)// - 协调节点收集 5×10=50 个文档// - 全局排序后取前10个// - 进入Fetch阶段获取这10个文档的完整内容

3.3 Query阶段的关键细节

每个分片返回多少文档?

默认情况下,每个分片返回from + size条结果。例如,查询from=90, size=10时(即第10页,每页10条),每个分片返回100条。

这就引出了深度分页的性能问题

  • 当我们请求第1000页时,每个分片需要返回(1000-1)×10 + 10 = 10000
  • 协调节点需要处理5×10000 = 50000条数据的内存排序

两个重要的限制参数:

PUT/shop/_settings{"index.max_result_window":10000// 默认为10000,超过需用search_after}

四、Fetch阶段详解

4.1 步骤分解

Query阶段结束后,协调节点已确定了最终需要返回的文档ID列表。Fetch阶段的任务就是去获取这些文档的完整内容。

Fetch阶段内部流程: ① 协调节点确定目标文档分片分布 - 从Query阶段结果中得到最终的N个文档ID - 通过路由公式反算每个文档所在的分片 ② 协调节点向目标分片发起MultiGet请求 ┌────────────────────────────────────────┐ │ 按分片聚合请求(高效批量获取): │ │ │ │ → Node2 (Shard 0): [doc_3, doc_7, doc_15] │ → Node3 (Shard 1): [doc_1, doc_9] │ │ → Node4 (Shard 2): [doc_2, doc_5, doc_6] │ └────────────────────────────────────────┘ ③ 各目标分片根据文档ID返回完整文档 - 返回_source字段、stored fields等 ④ 协调节点组装最终响应 - 按排序顺序排列文档 - 添加hit元数据(_index, _id, _score等) - 返回给客户端

4.2 两阶段的网络开销分析

假设:

  • 5个分片
  • 查询from=0, size=10
  • 每个文档_source约1KB
  • 每个文档ID+分数约50字节
Query阶段网络传输: 分片→协调节点: 5 × 10 × 50字节 = 2.5KB Fetch阶段网络传输: 协调节点→分片: 请求10个文档ID ≈ 300字节 分片→协调节点: 10 × 1KB = 10KB 总网络传输: 约12.8KB 如果不分两阶段(每个分片返回完整文档): 分片→协调节点: 5 × 10 × 1KB = 50KB(近4倍差距)

随着分片数增加或文档体积增大,两阶段优化带来的节省效果更加明显。


五、分布式相关性评分问题

5.1 问题根源:各分片独立统计IDF

Query阶段每个分片独立计算BM25评分。问题在于BM25中的IDF(逆文档频率)需要知道全局的文档频率统计:

IDF(qi) = log(1 + (N - n(qi) + 0.5) / (n(qi) + 0.5)) 其中 N = 总文档数, n(qi) = 包含词qi的文档数

在分布式环境下,每个分片只有自己那部分数据的统计信息:

全局索引: 总共100万文档,"Elasticsearch"出现1000次 → IDF = log(1 + (1000000 - 1000 + 0.5) / (1000 + 0.5)) ≈ log(1000) ≈ 6.9 Shard 0 (30万文档): "Elasticsearch"出现800次 → IDF = log(1 + (300000 - 800 + 0.5) / (800 + 0.5)) ≈ log(375) ≈ 5.9 ← 偏低 Shard 1 (30万文档): "Elasticsearch"出现100次 → IDF = log(1 + (300000 - 100 + 0.5) / (100 + 0.5)) ≈ log(3000) ≈ 8.0 ← 偏高 Shard 2 (40万文档): "Elasticsearch"出现100次 → IDF = log(1 + (400000 - 100 + 0.5) / (100 + 0.5)) ≈ log(4000) ≈ 8.3 ← 更高

结果是:同一个文档在不同分片上的评分因为IDF的差异而不一致。当文档不均匀分布时(实际生产环境几乎必然如此),最终全局排序可能出现偏差——来自小IDF分片的文档得分可能系统性地偏低。

5.2 问题场景示例

假设搜索词"Elasticsearch": Shard A: 1000篇文档,其中800篇包含"Elasticsearch" → IDF很低(词很常见) Shard B: 1000篇文档,其中10篇包含"Elasticsearch" → IDF很高(词很稀有) Shard A中一篇标题为"Elasticsearch入门"的文档: - 本地得分: 3.5(因为本地IDF低) Shard B中一篇标题为"Elasticsearch入门"的文档: - 本地得分: 8.2(因为本地IDF高) 全局排序时,Shard B的文档可能排在Shard A前面, 尽管两篇文档的相关性实际上是相同的。

5.3 影响程度评估

数据分布情况IDF偏差程度对排序的影响
文档均匀分布★☆☆☆☆几乎无影响,各分片IDF接近
按时间分片(日志)★★☆☆☆轻微影响,IDF随时间波动
按业务线分片(routing)★★★★☆较大影响,相同词在不同分片频率差异大
小样本查询★★★☆☆样本足够大时影响可忽略

六、DFS Query Then Fetch:解决评分不一致

6.1 DFS搜索类型

DFS(Distributed Frequency Search)是Elasticsearch提供的另一种搜索方式,通过在Query阶段之前先执行一次"预查询",获取全局的词频统计信息,然后传递给各分片用于准确的IDF计算。

DFS Query Then Fetch 执行流程: ┌─────────────────────────────────────────────┐ │ 预Query阶段(DFS Phase) │ │ ① 协调节点向所有分片请求各Term的文档频率 │ │ ② 各分片返回本地频率统计 │ │ ③ 协调节点汇总为全局频率统计 │ └────────────────┬────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ Query阶段(使用全局IDF) │ │ ④ 协调节点携带全局频率统计广播Query到各分片 │ │ ⑤ 各分片使用全局频率计算准确的IDF和评分 │ │ ⑥ 返回文档ID + 准确评分 │ └────────────────┬────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ Fetch阶段(与普通Query Then Fetch相同) │ │ ⑦ 协调节点全局排序后获取完整文档 │ └─────────────────────────────────────────────┘

6.2 三种搜索类型对比

搜索类型网络往返次数评分准确性性能开销适用场景
query_then_fetch2次(Query + Fetch)★★☆☆☆★★★★★(最优)默认类型,日常搜索
dfs_query_then_fetch3次(DFS + Query + Fetch)★★★★★★★★☆☆相关性要求极高的场景
query_and_fetch(仅单个分片时)1次★★★★★★★★★★仅一个分片时自动启用

6.3 使用DFS搜索

// 使用DFS搜索类型GET/shop/_search?search_type=dfs_query_then_fetch{"query":{"match":{"title":"Elasticsearch"}}}// 也可以通过_profile分析各阶段的耗时GET/shop/_search?search_type=dfs_query_then_fetch{"profile":true,"query":{"match":{"title":"Elasticsearch"}}}

6.4 何时使用DFS?

建议使用DFS的场景:

  • 文档在不同分片上分布极不均匀(如按业务线routing)
  • 搜索结果较少(<100条),文档频率统计差异被放大
  • 相关性排序是业务核心指标(如电商搜索、文档检索)

无需使用DFS的场景:

  • 文档在各分片均匀分布
  • 日志搜索、数据分析等不需要精确相关性排序
  • 大数据量搜索(DFS的额外网络开销不值得)
  • 使用自定义排序或function_score的场景

七、深度分页与替代方案

7.1 深度分页的问题

Query Then Fetch模型在处理深度分页时会遇到严重的性能瓶颈:

第1000页 (from=9990, size=10): 每个分片需要返回:10000 个文档ID+评分 5个分片:协调节点合并 50000 个文档 内存压力极大,CPU归并排序开销高 根本原因:Query阶段必须保证"前面的文档"不会在后续跳出来

7.2 search_after:深度分页的推荐方案

// 使用 search_after 替代深度分页GET/shop/_search{"size":10,"query":{"match":{"title":"iPhone 14"}},"sort":[{"price":"asc"},{"_id":"asc"}],"search_after":[5999,"doc_123"]// 上一页最后一条的sort值}
方案适用深度性能实时性要求
from+size<10000浅分页优秀,深分页糟糕支持跳页
search_after无限制始终高效不支持跳页
scroll无限制高效快照模式,不实时

八、总结与最佳实践

核心要点回顾

  1. Query Then Fetch两阶段模型通过"先传ID+分数再取完整文档"的设计,实现了网络传输和内存使用的双重优化,是Elasticsearch分布式搜索的基石。

  2. Query阶段各分片独立搜索并返回Top N文档ID,协调节点全局归并排序;Fetch阶段仅对最终保留的少量文档进行MultiGet获取。

  3. 分布式IDF不一致是Query Then Fetch模型的固有问题,每个分片独立统计词频导致评分偏差,数据分布越不均匀问题越明显。

  4. DFS搜索通过增加一次预查询获取全局词频,以额外的一次网络往返为代价换取准确的评分,适用于相关性要求高的场景。

最佳实践清单

实践建议详细说明
默认使用query_then_fetch绝大多数场景下性能最优,保持默认设置
相关性敏感场景启用DFS电商搜索、知识库检索等场合适用dfs_query_then_fetch
避免深度分页from+size之和不宜超过10000,深翻场景使用search_after
监控profile输出使用"profile": true分析各阶段耗时分布
副本分片分担查询搜索负载高时增加副本数,利用副本分片并行搜索
preference参数控制使用preference参数绑定用户到固定分片,用于A/B测试或缓存

上一篇【第44篇】Elasticsearch分布式索引原理——分片路由与写入流程
下一篇【第46篇】Elasticsearch分布式文档更新原理


http://www.jsqmd.com/news/891349/

相关文章:

  • 2026年Q2高清投屏与屏幕镜像软件精选榜单,热门免费跨屏工具实用盘点
  • 2026抑尘剂核心生产厂家实力排行与性能对比 推荐任丘市双成化工产品厂 - 奔跑123
  • 视频目标检测中信息泄露的根源与基于聚类的数据划分解决方案
  • 西安系统门窗品牌推荐榜:5家靠谱本地厂商深度测评(2026版) - 深度智识库
  • UABEAvalonia:如何为现代Unity游戏资源管理提供跨平台解决方案?
  • 终极macOS Windows启动盘制作工具:3个核心问题一键解决
  • 2026 年河南巨量本地推推广公司推荐,结合 GEO 优化抓取 AI 搜索流量 - 企品推
  • 联邦学习在网络威胁情报共享中的应用:FedScope系统设计与实践
  • go-workers源码解析:深入理解Golang任务队列的实现原理
  • 嵌入式视觉传感软体手指:基于内部点阵变形实现多模态感知
  • 2026一键去水印工具怎么选?免费一键去水印工具大盘点 - 科技热点发布
  • iniparser配置管理最佳实践:从简单应用到复杂企业级系统的演进
  • Windows安全中心深度解析:如何通过WSC API绕过Windows Defender防护
  • 2026年混料系统老牌公司有哪些?混料设备企业实力推荐 - 品牌2025
  • BilibiliDown:一站式B站视频下载解决方案,让你的收藏永不丢失
  • 如何用ContextMenuManager拯救你的Windows右键菜单:3分钟告别混乱,效率翻倍
  • 珍宝黄金回收(十年老店)|2026年5月唐山黄金回收多少钱一克,实体老店,诚信经营 - 润富黄金珠宝行
  • 中石化加油卡回收四步走实测,猎卡回收正规流程与到账参考 - 京回收小程序
  • 【Elasticsearch从入门到精通】第44篇:Elasticsearch分布式索引原理——分片路由与写入流程
  • 5步掌握Auto.js:解放双手的Android自动化神器
  • 终极隐私保护指南:使用Privacy工具检测个人数据泄露的完整教程
  • 成都中视新影:覆盖全品类宣传片定制的头部传媒机构 - 奔跑123
  • 2026安徽省界首市寄快递省钱攻略!4个正规低价平台,告别线下寄件溢价陷阱 - 时讯资讯
  • 高温高强度耐磨合金厂商推荐:UNS N07718高温合金厂商联系方式 - 品牌2025
  • BLSTM与词嵌入技术:构建高精度普什图语词性标注器的实践
  • 嵌入认知期望的区间值粗糙集:从距离偏好到属性约简的决策分析新范式
  • Hindsight性能调优终极指南:优化内存使用和查询速度的10个技巧
  • ComfyUI-TeaCache与Compile Model协同使用:打造极速推理工作流
  • 基于GBDT神经架构比较器的移动端人脸识别模型快速搜索框架
  • AMD Ryzen 系统底层调试:SMUDebugTool 深度实战与性能优化指南