深入 Elasticsearch(00):为什么 Elasticsearch 的核心是一张倒排索引
Elasticsearch 容易被归类成"分布式搜索引擎"或"日志分析平台"。这些标签描述了它的用途,但没有说清楚它的内部结构。把 Elasticsearch 拆到最底层,剩下的核心数据结构只有一个:倒排索引(inverted index)。mapping、analysis、shard、replica、aggregation 五个机制围绕这个数据结构逐层展开。
Elasticsearch 是一个以倒排索引为核心数据结构的分布式搜索与分析引擎。
本篇是系列导读。核心任务只有一个:说清楚倒排索引是什么,以及它和关系型数据库里的 B+Tree 索引有什么本质区别。
倒排索引的基本结构
关系型数据库的 B+Tree 索引从行出发。给定一个索引键值,B+Tree 能定位到存储这行数据的磁盘页。查找路径是:
1 | |
倒排索引从词项(term)出发。给定一个词项,倒排索引能定位到包含这个词项的所有文档。查找路径是:
1 | |
两条路径的起点不同。B+Tree 回答的问题是"这个键值对应哪一行",倒排索引回答的问题是"哪些文档包含这个词"。
倒排索引的核心由三层结构组成:
1 | |
Term Dictionary 是一个有序的词项表,通常用 FST(Finite State Transducer)压缩存储。Posting List 记录每个词项出现在哪些文档中,以及出现的频率、位置等信息。Documents 是原始文档的存储。
下面这张图把三层结构和查询方向放在一起,回答的问题是:一个搜索词经过哪些环节最终定位到文档。
graph LR
Q["搜索词<br/>e.g. search"] --> TD["Term Dictionary<br/>FST 有序词项表"]
TD --> PL["Posting List<br/>doc1:2, doc3:1"]
PL --> D1["doc1"]
PL --> D3["doc3"]
style TD fill:#e8f4fd,stroke:#1a73e8
style PL fill:#fef7e0,stroke:#f9ab00
style D1 fill:#e6f4ea,stroke:#34a853
style D3 fill:#e6f4ea,stroke:#34a853
三层之间是单向的包含关系:Term Dictionary 负责定位词项,Posting List 负责枚举文档 ID 和频率,Documents 负责存储原始内容。搜索时只需要前两层;需要返回原文时才访问第三层。
B+Tree 索引 vs 倒排索引
| 维度 | B+Tree 索引 | 倒排索引 |
|---|---|---|
| 查找起点 | 索引键值(如主键、列值) | 词项(文本分析后的 token) |
| 查找结果 | 一行或少数几行 | 包含该词项的所有文档集合 |
| 擅长的查询 | 等值查询、范围查询、排序 | 全文搜索、关键词匹配 |
| 更新方式 | 原地更新(in-place update) | 追加新段(append new segment) |
| 数据组织 | 行式(一行的所有列在一起) | 词项式(一个词指向多个文档) |
| 典型系统 | MySQL, PostgreSQL, Oracle | Elasticsearch, Solr, Lucene |
B+Tree 的优势在精确定位。当查询条件是"找到 id=42 的那行数据"时,B+Tree 从根节点开始二分,经过 3-4 层就能到达目标。
倒排索引的优势在全文检索。当查询条件是"找到包含 search 和 engine 这两个词的所有文档"时,倒排索引分别找到两个词项的 Posting List,做交集运算,就得到同时包含两个词的文档。
为什么 Elasticsearch 选择倒排索引
Elasticsearch 底层使用 Apache Lucene 作为存储和搜索引擎。每个 ES shard 本质上就是一个 Lucene index。Lucene 的核心数据结构就是倒排索引。
这个选择不是偶然的。Elasticsearch 要解决的核心问题是全文搜索——给定一段自然语言查询,快速找到最相关的文档。这个问题的性质决定了数据结构的选择:
- 查询的输入不是精确键值,而是自然语言文本。文本需要先经过分析(analysis)变成词项,然后在词项级别做匹配。
- 查询的结果不是精确一行,而是一个按相关性排序的文档列表。相关性计算需要词频(TF)和逆文档频率(IDF)等统计信息,这些信息天然存储在倒排索引的 Posting List 中。
- 查询需要跨多个字段搜索。倒排索引为每个字段独立建索引,多字段搜索就是多个 Posting List 的集合运算。
从这个视角看,Elasticsearch 的其他机制都是围绕倒排索引展开的:
1 | |
文档从写入到可搜索要经过一条完整的处理管道。原始文本先由 Analysis 拆成词项,词项写入倒排索引形成 Segment,多个 Segment 构成 Shard,多个 Shard 分布在集群节点上。
flowchart LR
RAW["原始文档"] --> ANA["Analysis<br/>分词 + 标准化"]
ANA --> SEG["Segment<br/>倒排索引写入"]
SEG --> SHARD["Shard<br/>= Lucene Index"]
SHARD --> CLUSTER["Cluster<br/>多节点分布"]
style RAW fill:#fce4ec,stroke:#d32f2f
style ANA fill:#e8f4fd,stroke:#1a73e8
style SEG fill:#fef7e0,stroke:#f9ab00
style SHARD fill:#e6f4ea,stroke:#34a853
style CLUSTER fill:#f3e5f5,stroke:#7b1fa2
这条管道中,倒排索引处于中间位置——它是 Analysis 的输出目标,也是 Shard / Replica / Aggregation 的底层依赖。后续文章会逐层展开管道中的每个环节。
实验:观察倒排索引的工作方式
以下实验使用 Elasticsearch REST API。可以用 curl 或 Kibana Dev Tools 执行。
创建一个测试 index 并写入三条文档:
1 | |
用 _analyze API 观察分词结果:
1 | |
返回结果(使用默认 standard analyzer):
1 | |
原始文本 “Elasticsearch” 被转成了小写的 “elasticsearch”。这就是 analysis 的作用——把原始输入标准化为可索引的词项。
搜索包含 “search” 的文档:
1 | |
三条文档都会被返回,因为 “search” 这个词项出现在所有三条文档的倒排索引中。返回结果按 BM25 相关性评分排序。
搜索同时包含 “search” 和 “distributed” 的文档:
1 | |
只有文档 1 被返回。match 查询默认对查询文本做分析,产生 “distributed” 和 “search” 两个词项,然后要求两个词项的 Posting List 做交集。
模式提炼:从行定位到词项定位
1 | |
两种索引结构代表两种不同的数据访问范式。B+Tree 围绕"行"组织——给定一个键,定位到一行。倒排索引围绕"词项"组织——给定一个词,定位到一组文档。
| 范式 | 核心操作 | 典型查询 | 结果特征 |
|---|---|---|---|
| 行定位(B+Tree) | 二分查找 → 精确定位 | WHERE id = 42 | 通常返回一行或少数行 |
| 词项定位(Inverted Index) | 词典查找 → 集合运算 | 搜索 “distributed search” | 返回文档集合,按相关性排序 |
这两种范式不是互斥的。Elasticsearch 内部也使用类似 B+Tree 的结构(BKD-tree)来处理数值范围查询和地理坐标查询。倒排索引负责文本搜索,BKD-tree 负责数值和空间查询,Doc Values 负责排序和聚合——但倒排索引是 Elasticsearch 存在的根本原因。
工程迁移表
| 概念 | RDBMS (MySQL/PostgreSQL) | Elasticsearch | Apache Solr |
|---|---|---|---|
| 核心索引结构 | B+Tree | 倒排索引(FST + Posting List) | 倒排索引(同 Lucene) |
| 数据存储单元 | 行(row) | 文档(document) | 文档(document) |
| 模式定义 | DDL (CREATE TABLE) | Mapping | schema.xml / managed-schema |
| 文本处理 | 有限(LIKE, FULLTEXT) | Analysis pipeline(丰富) | Analysis chain(同等丰富) |
| 分布式策略 | 分库分表(手动或中间件) | 原生 shard + replica | SolrCloud + ZooKeeper |
| 底层引擎 | InnoDB / PostgreSQL storage | Apache Lucene | Apache Lucene |
常见误解
误解一:倒排索引就是"反向的索引"。 “inverted” 不是"反向"的意思。它描述的是数据组织方向的反转——从"文档包含哪些词"反转为"词出现在哪些文档中"。正排(forward index)是 document → terms,倒排是 term → documents。
误解二:Elasticsearch 是一个数据库。 Elasticsearch 可以存储和检索数据,但它的核心优化方向是搜索而不是事务。它没有 ACID 事务,没有外键约束,更新操作实际上是删除旧文档再写入新文档。把 Elasticsearch 当主数据库使用是一个常见的架构错误。
误解三:倒排索引只能做全文搜索。 倒排索引最初是为全文搜索设计的,但 Lucene 在倒排索引之外还提供了 Doc Values(列式存储,用于排序和聚合)、BKD-tree(用于数值和地理范围查询)、Stored Fields(用于返回原始文档)。Elasticsearch 的能力来自这些数据结构的组合,不仅仅是倒排索引。
练习
-
在 Docker 中启动一个单节点 Elasticsearch 集群,创建上面的测试 index,用
_analyzeAPI 观察不同 analyzer(standard, simple, whitespace)对同一段文本的分词差异。 -
写入 10 条包含不同关键词的文档,用
match查询搜索某个词,观察返回结果的_score字段。尝试理解为什么有些文档的分数更高(提示:词频和文档长度)。 -
对比
match查询和term查询的结果差异。对 title 字段分别用match: "Search"和term: "Search"查询,观察哪个有结果哪个没有。思考原因(提示:match会先做分析,term不会)。
系列路线图
本系列共 18 篇,从倒排索引出发,逐层展开 Elasticsearch 的存储、搜索、聚合和分布式机制:
1 | |
系列导航
| 上一篇 | 下一篇 |
|---|---|
| — | 架构总览:Node、Cluster 与集群状态 |
参考资料
- Elasticsearch 官方文档:Getting Started
- Apache Lucene 官方文档
- Christopher D. Manning, Prabhakar Raghavan, Hinrich Schütze. Introduction to Information Retrieval. Cambridge University Press.(第 1-2 章:倒排索引的经典定义)
- Elasticsearch: The Definitive Guide — Inverted Index
