倒排索引原理与 Elasticsearch 应用
🎯 面试题:什么是倒排索引?为什么 Elasticsearch 查询这么快?
倒排索引是搜索引擎的核心数据结构,与传统正排索引相反,通过「词 → 文档列表」的方式实现快速检索。本题考察对全文检索底层原理的理解深度。
一、正排索引 vs 倒排索引
正排索引(Forward Index):
文档1 → [词1, 词2, 词3, 词4]
文档2 → [词2, 词3, 词5, 词6]
查包含"词3"的文档:需要遍历所有文档
倒排索引(Inverted Index):
词1 → [文档1]
词2 → [文档1, 文档2]
词3 → [文档1, 文档2]
查包含"词3"的文档:直接查词3的列表 → [文档1, 文档2]
二、倒排索引结构
倒排索引 = Term Dictionary + Posting List
┌────────────────────────────────────────────────────┐
│ Term Dictionary(词典) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 阿 │ │ 里 │ │ 巴 │ ← 词项 │
│ │ (指针) │ │ (指针) │ │ (指针) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ↓ ↓ ↓ │
│ ┌────────────────────────────────────────────┐ │
│ │ Posting List(倒排列表) │ │
│ │ │ │
│ │ 词 "阿里" → docID[1,3,7], freq[2,1,3], │ │
│ │ position[0,2][1,0][0,4,6] │ │
│ │ │ │
│ │ 每个元素的格式: │ │
│ │ [docID(文档ID), freq(出现次数), │ │
│ │ [position1, position2...](词出现位置)] │ │
│ └────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘
三、FST 压缩
Finite State Transducer
FST(有限状态自动机):压缩 Term Dictionary
普通词典:
阿里 → 0x1234
阿里巴巴 → 0x5678
阿里云 → 0x9ABC
FST 压缩:
┌──A──┐
──→○──L──○──I──○──B──○── (阿里) → 0x1234
│
(N──○──B──○──U──○── (阿里巴巴) → 0x5678
│
(U──○──N──○── (阿里云) → 0x9ABC
FST 优势:
- 共享前缀,节省空间(可减少 80% 词典体积)
- 有序存储,支持二分查找
- 支持前缀查询(自动补全)
四、分词器原理
分词流程:
原始文本:"Spring Boot 框架非常强大"
↓
┌──────────────────────────────────┐
│ 1. 字符过滤器(Character Filter) │
│ - HTML 标签去除 │
│ - 特殊字符过滤 │
└──────────────────────────────────┘
↓
┌──────────────────────────────────┐
│ 2. 分词器(Tokenizer) │
│ - 按空格/标点切分 │
│ - Spring / Boot / 框架 / 非常 / 强大 │
└──────────────────────────────────┘
↓
┌──────────────────────────────────┐
│ 3. Token Filter(词项过滤器) │
│ - 转小写(Spring → spring) │
│ - 词干提取(frameworks → framework)│
│ - 同义词扩展 │
└──────────────────────────────────┘
↓
spring, boot, 框架, 非常, 强大
五、ES 内置分词器
PUT /my_index
{
"settings": {
"analyzer": {
"my_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "stop", "snowball"]
}
}
}
}
GET /_analyze
{
"analyzer": "standard",
"text": "Spring Boot's power"
}
→ [spring, boot, power]
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "Java后端面试指南"
}
→ [java, 后端, 面试, 指南]
六、查询流程
ES 查询流程(三个阶段):
第一阶段:parse(解析)
用户查询 → DSL 解析 → 抽象语法树
第二阶段:rewrite(重写)+ score(打分)
- 根据路由找到对应 shard
- 每个 shard 查询本地倒排索引
- 计算 TF/IDF/BM25 相关性得分
第三阶段:fetch(获取)
- 协调节点收集所有 shard 结果
- 按得分排序
- 取 top N 文档
- 从各 shard 拉取完整文档
┌─────────────────────────────────────────────┐
│ 协调节点(Coordinating Node) │
│ │
│ Query Phase: │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Shrad│ │Shrad│ │Shrad│ │
│ │ 0 │ │ 1 │ │ 2 │ │
│ └──┬──┘ └──┬──┘ └──┬──┘ │
│ │ │ │ │
│ 收集 Top100 排序 收集 Top100 排序 │
│ │ │ │ │
│ └────────┼────────┘ │
│ ↓ │
│ 汇总 Top N │
│ ↓ │
│ Fetch Phase: │
│ ┌─────────────────────────────────────┐ │
│ │ 并行从各 shard 拉取文档内容 │ │
│ └─────────────────────────────────────┘ │
│ ↓ │
│ 返回给用户 │
└─────────────────────────────────────────────┘
七、面试高频题
Q1: 倒排索引是什么?它和正排索引的区别是什么?
正排索引是「文档 → 词」的映射,查询时需要遍历所有文档。倒排索引是「词 → 文档列表」的映射,查询时直接查词的列表,性能高几个数量级。倒排索引包含 Term Dictionary(词典,有序存储所有词)和 Posting List(倒排列表,记录每个词出现在哪些文档中)。Elasticsearch 使用 FST 对词典进行压缩,支持前缀查询。
Q2: Elasticsearch 查询为什么这么快?
三个原因:① 倒排索引:词到文档的映射是 O(1) 查询,而非遍历;② FST 压缩词典:词典有序且共享前缀,占用内存小,可完全加载到内存;③ 分布式架构:数据分片到多个节点并行查询,横向扩展。核心是倒排索引 + 内存缓存 + 分片并行。
Q3: 分词器有哪些类型?如何选择?
ES 内置分词器:standard(默认,英文分词)、simple(按非字母切分)、whitespace(按空格切分)、keyword(不分词,整体作为一个词)、pattern(正则分词)。中文推荐 IK(ik_max_word 细粒度、ik_smart 粗粒度)或 jieba。分词器选择影响召回率和精确率:粗粒度召回率高但精确率低,细粒度精确率高但召回率低。