倒排索引是什么?

#中间件#面试八股

倒排索引是什么?

结论先行:倒排索引是“词项 → 文档列表”的映射,与“文档 → 词项”的正排相反。它由三部分组成:词典(term dictionary,有序存放全部词项)、倒排表(posting list,每个词项对应的文档 ID 列表)、词项索引(term index,词典的前缀索引,常用 FST 实现并常驻内存)。检索时先查词项索引定位词典,再取倒排表求交,最后按文档 ID 回表取原文。

一、结构总览

文档集合
  doc1: "分布式 锁 实现"
  doc2: "分布式 事务"
  doc3: "Redis 分布式 锁"

分词后建立倒排
  term        posting list
  ────────────────────────────
  分布式   →  [1, 2, 3]
  锁       →  [1, 3]
  实现     →  [1]
  事务     →  [2]
  Redis    →  [3]

term index (FST)  ──> term dictionary (有序) ──> posting list (压缩)
   常驻内存              磁盘 + 文件缓存           磁盘 + 跳表

二、三级结构各自解决什么

层次数据结构解决的问题
term indexFST(有限状态转换器)词典很大,无法全放内存,用前缀共享压缩
term dictionary有序数组/字典树二分或前缀定位到具体 term
posting list有序 docId 数组 + 跳表多个 term 求交、按 docId 顺序回表
压缩编码FOR(帧内差值)、Roaring Bitmap倒排表可能上亿条,必须压缩

举例:分布式 与 锁 求交,得到 [1, 3];如果查询是 分布式 AND 锁 AND NOT 事务,再与 [2] 做差集。

三、正排与倒排的分工

方向结构用途
正排(doc → values)DocValues 列存排序、聚合、脚本取值
倒排(term → docs)倒排索引关键词检索、过滤

ES 同时维护两套:检索走倒排,排序聚合走 DocValues,所以“既能搜又能排”。

四、动手验证

// 看分词结果,确认 term 是怎么产生的
GET /article/_analyze
{
  "analyzer": "ik_max_word",
  "text": "分布式锁的实现"
}
// 看查询命中了哪些 term、怎么算分
GET /article/_search
{
  "explain": true,
  "query": { "match": { "title": "分布式锁" } }
}

_analyze 输出 token 列表,explain 输出每个 term 的 IDF、TF 与最终得分,是排查“为什么这条没排前面”的标准手段。

五、倒排索引的代价

代价说明
写入放大每个字段的每个 term 都要写倒排,写入量远大于原文
存储膨胀倒排 + 正排 + 原始 _source,通常是原文的 2~3 倍
更新昂贵段不可变,更新等于重建文档,旧文档延迟删除
分词依赖中文分词质量直接决定召回率,词典要维护

常见追问

  • 追问:为什么倒排表要有序?有序才能用跳表做跳跃求交,也才能按 docId 顺序批量回表,减少随机 IO。
  • 追问:FST 为什么省内存?它把词典的所有前缀做成状态机共享,几十万词项的索引可以压到几 MB。
  • 追问:中文一定要装分词器吗?默认 standard 分词器按单字切,“分布式锁”会切成四个字,召回高但精度差;生产中文场景基本都用 ik 或 jieba 类分词器。

小结

  • 倒排 = 词典 + 倒排表 + 词项索引,方向是 term → docs。
  • FST 压缩词典索引,跳表加速求交,FOR/Roaring 压缩倒排表。
  • 检索走倒排,排序聚合走 DocValues,两者互补。
  • 倒排索引用存储和写入成本,换检索速度。
笔记加载中…