写点什么

大数据 -179 Elasticsearch 倒排索引与读写流程全解析:从 Lucene 原理到 Query/Fetch 实战

作者:武子康
  • 2025-12-10
    山东
  • 本文字数:4158 字

    阅读完需:约 14 分钟

大数据-179 Elasticsearch 倒排索引与读写流程全解析:从 Lucene 原理到 Query/Fetch 实战

TL;DR

  • 场景:在日志检索、站内搜索、大数据分析中,Elasticsearch 查询性能和结果相关性经常“黑箱化”。

  • 结论:理解倒排索引 + 分片路由 + Query/Fetch 两阶段,是排查“搜不全、搜不准、搜很慢”的前提。

  • 产出:一套围绕倒排索引和读写流程的知识框架 + 版本适用矩阵 + 常见错误速查卡。


版本矩阵


倒排索引

Elasticsearch 是一个基于 Apache Lucene 构建的开源分布式搜索引擎,它能够以近乎实时的速度执行复杂的全文搜索查询。作为当前最流行的企业级搜索解决方案之一,Elasticsearch 被广泛应用于日志分析、电子商务搜索、应用内搜索等各种场景。


在 Elasticsearch 的核心架构中,倒排索引(Inverted Index)是最关键的数据结构之一。与传统数据库使用的正排索引不同,倒排索引采用了"词项→文档"的映射方式。具体来说,当文档被索引时,Elasticsearch 会:


  1. 对文本内容进行分词处理(Tokenization),将句子分解为独立的词项(Terms)

  2. 建立词项到文档 ID 的映射关系

  3. 记录词项在每个文档中的出现频率(TF)和位置信息


例如,假设有三个文档:


  • 文档 1:"Elasticsearch is fast"

  • 文档 2:"Elasticsearch is scalable"

  • 文档 3:"Search is fast"


构建的倒排索引会类似这样:


"elasticsearch" → [文档1, 文档2]"fast" → [文档1, 文档3]"scalable" → [文档2]"search" → [文档3]
复制代码


这种数据结构设计使得 Elasticsearch 在执行搜索查询时,只需查找查询词项对应的文档列表,然后进行合并操作即可得到结果,而无需扫描所有文档内容。这种机制正是 Elasticsearch 能够实现毫秒级搜索响应的关键所在。理解倒排索引的工作原理对于优化 Elasticsearch 查询性能、设计合适的分词策略以及解决搜索相关问题时都至关重要。

什么是倒排索引?

倒排索引是一种用于快速查找包含特定词汇的文档的数据结构。它类似于一本书的索引页,但结构上是“倒过来”的,因此得名。

正向索引 vs. 倒排索引

  • 正向索引(Forward Index)

  • 是基于文档的索引结构,以文档为中心组织数据

  • 存储方式:为每个文档建立一个条目,记录该文档包含的所有词汇及其出现位置

  • 典型实现:文档 ID → [词项 1:位置列表, 词项 2:位置列表,...]

  • 应用场景:

  • 文档内容检索(如显示搜索结果时获取完整文档内容)

  • 文档相似度计算

  • 搜索引擎的文档存储层

  • 示例:文档 D1 包含"搜索引擎"(位置 5)、"技术"(位置 8);文档 D2 包含"数据库"(位置 3)

  • 倒排索引(Inverted Index)

  • 是基于词汇的索引结构,以词项为中心组织数据

  • 存储方式:为每个词项建立一个条目,记录包含该词项的所有文档及出现位置

  • 典型实现:词项 → [文档 ID1:位置列表, 文档 ID2:位置列表,...]

  • 核心优势:

  • 快速定位包含特定词项的文档

  • 支持高效的布尔查询(AND/OR/NOT)

  • 便于实现相关性排序

  • 应用场景:

  • 搜索引擎的核心检索组件

  • 大规模文本检索系统

  • 广告定向投放

  • 示例:词项"技术" → [D1:8, D3:2, D5:15];词项"数据库" → [D2:3, D4:7]


例如,假设有三篇文档如下:


  • "Elasticsearch 是一个分布式搜索引擎"

  • "分布式系统可以提供高可用性"

  • "搜索引擎使用倒排索引进行高效搜索"


正向索引会记录每个文档中有哪些词:


Doc1: ["Elasticsearch", "是", "一个", "分布式", "搜索", "引擎"]Doc2: ["分布式", "系统", "可以", "提供", "高", "可用性"]Doc3: ["搜索", "引擎", "使用", "倒排索引", "进行", "高效", "搜索"]
复制代码


而倒排索引则会记录每个词在哪些文档中出现:


"Elasticsearch": [1]"分布式": [1, 2]"搜索": [1, 3]"引擎": [1, 3]"倒排索引": [3]
复制代码


这样,当你查询一个词汇时,比如 "搜索",Elasticsearch 可以通过倒排索引立即返回它出现在 Doc1 和 Doc3 中,而不需要遍历所有的文档。

倒排索引的构建

在 Elasticsearch 中,文档首先会被分析和处理,然后生成倒排索引。其过程大致如下:


  • 文档分词:每个文档都会经过 分析器(Analyzer) 的处理,分析器负责将文档的文本分解成词项(terms)。例如,句子 "Elasticsearch 是一个分布式搜索引擎" 可能被分解为 ["elasticsearch", "分布式", "搜索", "引擎"]。

  • 标准化和过滤:分词后,分析器通常会进行进一步的处理,例如将所有词项转为小写、删除停用词(如 "是", "一个")等。这一步使得倒排索引更具可查询性。

  • 构建倒排列表:倒排索引将每个词项与它所出现的文档 ID 关联。词项是倒排索引的键,文档 ID 列表则是值。对于每个词项,Elasticsearch 还可能记录一些额外信息,如词频(TF)和词项在文档中的位置(用于短语匹配和邻近查询)。

倒排索引的结构

倒排索引的核心部分可以分为以下几个组成部分:


  • 词汇表(Term Dictionary):词汇表保存了所有被索引的词项,通常是以字典形式存储。通过这个表,Elasticsearch 可以快速定位某个词项是否存在。

  • 倒排列表(Posting List):对于每个词项,倒排列表记录了所有包含该词项的文档 ID。倒排列表还可以包含附加信息,如:

  • 词项频率(Term Frequency, TF):记录该词项在文档中出现的次数。

  • 文档频率(Document Frequency, DF):记录该词项在整个索引中出现的文档总数。位置(Position):词项在文档中的位置,用于支持短语和邻近查询。例如,对于词项 "搜索",倒排列表可能是这样的:


"搜索": [(Doc1, Position: 5), (Doc3, Position: 3, 7)]
复制代码


这意味着 "搜索" 在文档 1 中出现过,并且在文档 3 中出现过两次,分别位于位置 3 和 7。

倒排索引的查询原理

倒排索引使得 全文搜索查询 变得非常高效。例如,当你向 Elasticsearch 发起搜索请求时,比如查找包含词项 "搜索引擎" 的文档,Elasticsearch 可以执行以下步骤:


查找词项:Elasticsearch 首先在词汇表中查找 "搜索" 和 "引擎" 这两个词项,找到它们的倒排列表。


合并倒排列表:Elasticsearch 会合并这两个词项的倒排列表,找到同时包含 "搜索" 和 "引擎" 的文档。由于每个词项都与它的文档 ID 相关联,合并列表的操作非常快速。


计算相关性:在找到匹配的文档后,Elasticsearch 会根据一些算法(如 TF-IDF 或 BM25)对文档进行评分,衡量它们与查询的相关性。这个步骤基于词项频率、文档频率等信息,最终返回最相关的文档。

倒排索引的优势

  • 快速检索:倒排索引的结构使得对于某个词项的检索速度极快,尤其在海量文档中,查询性能仍能保持在毫秒级别。

  • 低内存占用:虽然倒排索引记录了大量的词项和文档 ID,但通过压缩算法和优化的数据结构,倒排索引可以保持较低的内存使用率。

  • 支持复杂查询:倒排索引不仅支持简单的关键词查询,还支持短语、前缀、邻近查询等复杂的搜索条件。

测试分析

Elasticsearch 使用一种称为倒排索引的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复的列表构成,对于其中每个词,有一个包含的它的文档列表。假设我们有两个文档,每个文档的内容如下:


1. The quick brown fox jumped over the lazy dog2. Quick brown foxes leap over lazy dogs in summer
复制代码


要创建倒排索引,首先要将每个文档内容拆分成单独的词,创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档,结果如下图所示:



现在我们想要搜索 quick down,我们只需要查找包含每个词条的文档:



两个文档都匹配,但是第一个文档比第二个匹配度更高,如果我们使用仅计算匹配条数量的简单相似性算法,那么对于我们查询的相关性来讲,第一个文档比第二个文档更佳。

读写流程

创建文档

向 Elasticsearch 中添加一个文档对象,由于 ES 是分布式集群并且底层设计了一个索引由众多 Shard 分片,所以添加文档时需要确定该文档属于哪个分片,确定规则为:


shard = has(routing) % number_of_primary_shards
复制代码


  • routing 是一个可变值,默认是文档的 ID,也可以设置成一个自定义的值

  • routing 通过 hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards(主分片的数量)后得到余数,这个分布在 0 到 number_of_primary_shards - 1 之间的余数,就是我们寻求的文档所在分片的位置。

写文档流程

以官网的例子进行分析,从图中可看出一个集群由三个节点组成,有两个分片,两个副本。



写操作必须在主分片上面完成之后,才能被复制到其他节点作为分片副本,新建、索引和删除请求都是写操作。


  • 客户端向 Master 发送写入请求,该节点作为协调节点

  • 根据文档的_id 确定分片,图中请求文档属于分片 0,协调节点请求转到节点的主分片

  • 在节点 3 上执行请求,成功之后,节点 3 根据副本数将请求并行转到副本分片对应节点,一旦副本分片执行完成,都向节点 3 报告成功,节点 3 将协调节点报告成功,协调节点向客户端报告成功

读文档流程

一个搜索请求必须询问请求的索引中所有分片的某个副本来进行匹配,假设一个索引由 5 个主分片,每个主分片有一个副本分片,共 10 个分片,一次搜索请求会由 5 个分片来共同完成,他们可能是主分片,也可能是副分片。也就是说,一次搜索请求只会命中所有分片副本中的一个。一次检索流程主要分为两个阶段:


  • Query 阶段

  • Fetch 阶段

Query 阶段


  • 客户端发送 Search 请求到 Node3

  • Node3 将查询请求转发到索引的每个主分片或副分片中

  • 每个分片在本地执行查询,并使用本地的 Term/DocumentFrequency 信息进行打分,添加结果到大小 from+size 的本地优先队列中

  • 每个分片返回各自优先队列中所有文档的 ID 和排序值给协调节点,协调节点合并这些值到自己的优先队列中,产生一个全局排序后的列表

Fetch 阶段


  • 协调节点相关 Node 发送 GET 请求

  • 分片所在节点向协调节点返回数据

  • 协调节点等待所有文档数据被取得,然后返回给客户端


分片所在节点在返回文档时,处理有可能出现的 _source 字段和高亮参数

错误速查

其他系列

🚀 AI 篇持续更新中(长期更新)

AI 炼丹日志-29 - 字节跳动 DeerFlow 深度研究框斜体样式架 私有部署 测试上手 架构研究,持续打造实用 AI 工具指南!AI 研究-132 Java 生态前沿 2025:Spring、Quarkus、GraalVM、CRaC 与云原生落地🔗 AI模块直达链接

💻 Java 篇持续更新中(长期更新)

Java-180 Java 接入 FastDFS:自编译客户端与 Maven/Spring Boot 实战 MyBatis 已完结,Spring 已完结,Nginx 已完结,Tomcat 已完结,分布式服务已完结,Dubbo 已完结,MySQL 已完结,MongoDB 已完结,Neo4j 已完结,FastDFS 已完结,OSS 正在更新... 深入浅出助你打牢基础!🔗 Java模块直达链接

📊 大数据板块已完成多项干货更新(300 篇):

包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈!大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT 案例 详解🔗 大数据模块直达链接

发布于: 刚刚阅读数: 3
用户头像

武子康

关注

永远好奇 无限进步 2019-04-14 加入

Hi, I'm Zikang,好奇心驱动的探索者 | INTJ / INFJ 我热爱探索一切值得深究的事物。对技术、成长、效率、认知、人生有着持续的好奇心和行动力。 坚信「飞轮效应」,相信每一次微小的积累,终将带来深远的改变。

评论

发布
暂无评论
大数据-179 Elasticsearch 倒排索引与读写流程全解析:从 Lucene 原理到 Query/Fetch 实战_Java_武子康_InfoQ写作社区