ES 倒排索引深度解析
面试官:ES 为什么搜索这么快?
你:核心是倒排索引。ES 在写入文档时会对文本字段进行分词,建立「词项 → 文档列表」的倒排索引,查询时直接定位词项对应的文档,不需要全表扫描。
面试官:倒排索引和数据库的 B+树索引有什么根本区别?
链式追问一:倒排索引结构
Section titled “链式追问一:倒排索引结构”Q1:倒排索引的数据结构是什么?必考
Section titled “Q1:倒排索引的数据结构是什么?”正排索引(Forward Index):文档 ID → 文档内容(数据库 B+树就是正排)
倒排索引(Inverted Index):词项 → 包含该词项的文档列表
假设有 3 个文档: Doc1: "Java 是世界上最好的语言" Doc2: "Python 是 AI 领域最热门的语言" Doc3: "Java 和 Python 都是优秀的语言"
分词后的倒排索引: 词项 | Posting List(文档 ID 列表) ---------|---------------------------------- Java | [Doc1(TF:1), Doc3(TF:1)] Python | [Doc2(TF:1), Doc3(TF:1)] 语言 | [Doc1(TF:1), Doc2(TF:1), Doc3(TF:1)] AI | [Doc2(TF:1)] 优秀 | [Doc3(TF:1)]Posting List 中存储的信息:
DocID:文档 IDTF(Term Frequency):词项在文档中出现的频率(用于相关性评分)位置信息(Position):词项在文档中的位置(用于短语查询)偏移量(Offset):字符位置(用于高亮显示)
Q2:倒排索引的词项字典是怎么存储的?FST 是什么?高频
Section titled “Q2:倒排索引的词项字典是怎么存储的?FST 是什么?”词项字典(Term Dictionary)存储所有不重复的词项,需要支持:
- 快速精确查找(
term查询) - 前缀匹配(
prefix查询) - 范围查询(
range查询)
ES 使用 FST(Finite State Transducer,有限状态转换器) 压缩存储词项字典:
假设词项:java, javascript, python普通存储:全部存入磁盘,占用大量内存FST 存储:共享公共前缀 "java",树形结构
(root) / \ java python | script
优势: - 极高的压缩率(类似 Trie 树但更紧凑) - 整个词项字典可以加载到内存(即使几十GB的索引) - O(len) 的查找时间复杂度与 MySQL B+树的根本区别:
| 维度 | MySQL B+树 | ES 倒排索引 |
|---|---|---|
| 适合查询 | 等值、范围查询 | 全文搜索、模糊查询 |
| 查询方向 | 属性 → 文档 | 词项 → 文档集合 |
| 空间优化 | 无压缩 | FST 压缩 + Posting 压缩 |
| 更新代价 | B+树节点分裂 | Segment 不可变,写新 Segment |
Q3:Posting List 是如何压缩存储的?
Section titled “Q3:Posting List 是如何压缩存储的?”ES 的 Posting List 存储大量文档 ID,ES 使用多种压缩技术:
1. Delta 编码(差分编码):
原始 DocID:[1, 3, 7, 15, 16, 19]差分值: [1, 2, 4, 8, 1, 3]存储差分值,减少数字大小,更利于压缩2. Frame Of Reference(FOR):将差分值按固定块(128个)分组,每块只用最少的 bit 数存储。
3. Roaring Bitmap:当文档量很大时,使用 Roaring Bitmap 存储文档 ID 集合,支持高效的集合运算(AND/OR/NOT),用于多条件查询的快速合并。
链式追问二:分词器
Section titled “链式追问二:分词器”Q4:ES 的分词器是怎么工作的?常用的分词器有哪些?必考
Section titled “Q4:ES 的分词器是怎么工作的?常用的分词器有哪些?”分词器(Analyzer) 由三部分组成:
Character Filter(字符过滤器) └── 处理原始文本,如去除 HTML 标签、替换字符 ↓Tokenizer(分词器核心) └── 将文本切分为词项(Token) ↓Token Filter(词项过滤器) └── 对词项进行处理:转小写、去停用词、词干提取等常用分词器:
| 分词器 | 适用语言 | 特点 |
|---|---|---|
standard | 英文(默认) | 按空格和标点分词,转小写 |
whitespace | 英文 | 仅按空格分词,不做其他处理 |
ik_max_word | 中文 | IK 中文分词,最细粒度切分 |
ik_smart | 中文 | IK 中文分词,最粗粒度切分(推荐用于搜索) |
jieba | 中文 | 结巴分词,支持自定义词典 |
中文搜索最佳实践:
{ "settings": { "analysis": { "analyzer": { "my_analyzer": { "type": "ik_max_word", "user_dict": "my_dict.txt" } } } }, "mappings": { "properties": { "title": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" } } }}分析器不一致的坑:索引时用 ik_max_word(细粒度),搜索时用 ik_smart(粗粒度),可以提高召回率。
Q5:text 类型和 keyword 类型的区别?
Section titled “Q5:text 类型和 keyword 类型的区别?”| 类型 | 是否分词 | 适用场景 | 支持聚合 |
|---|---|---|---|
text | ✅ 分词后建倒排索引 | 全文搜索、模糊匹配 | ❌(需要 fielddata,内存开销大) |
keyword | ❌ 整体作为一个词项 | 精确匹配、排序、聚合 | ✅ |
典型配置(同时支持全文搜索和聚合):
"title": { "type": "text", "analyzer": "ik_max_word", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } }}查询时用 title(全文搜索),聚合时用 title.keyword(精确值)。