核心答案

倒排索引建立“词项到文档”的映射。查询时先找到词项,再读取包含该词项的文档列表,因此适合全文搜索,而不需要逐条扫描原始文本。

分析过程

文本字段写入时经过 analyzer:字符过滤器预处理文本,tokenizer 切分词元,token filter 再进行小写化、停用词处理或词干化。查询使用的分析器应与索引设计相匹配。

text 与 keyword

text 字段会分词,适合全文搜索;keyword 保留完整值,适合精确过滤、排序和聚合。常见 mapping 会为一个字段同时配置 text 和 keyword 子字段。

{
  "title": {
    "type": "text",
    "fields": { "keyword": { "type": "keyword" } }
  }
}

分片与副本

索引由主分片组成,副本提供高可用和额外读取能力。主分片数量创建后不容易直接改变,过多小分片会增加集群元数据、文件句柄和合并压力。

near real-time

新写入的数据经过 refresh 后才对搜索可见,所以 Elasticsearch 是近实时搜索。频繁 refresh 会增加段创建和合并成本,应根据业务延迟要求设置。

参考资料

核心考点清单

  • 正排是文档到字段,倒排是词项到文档列表。
  • 分析器决定文本如何变成词项,索引期和查询期分析要协调。
  • 词项字典定位词项,倒排列表保存文档 ID、词频和位置信息。
  • text 用于全文检索,keyword 用于精确过滤、排序和聚合。
  • Lucene Segment 不可变,删除先打标记,空间在后续 Merge 中回收。

写入为什么是近实时

文档写入内存缓冲后,经过 Refresh 生成可搜索的新 Segment,因此写成功与搜索可见之间有短暂间隔。Flush 主要涉及持久化提交点,Merge 负责合并小 Segment,三者不能混为一谈。

高频追问与参考回答

追问 1:term 为什么可能查不到 text 原文?

text 已被分析成多个词项,而 term 不分析输入。全文搜索使用 match,精确匹配使用 keyword + term

追问 2:Refresh 越频繁越好吗?

不是。频繁 Refresh 降低可见延迟,但会产生更多小 Segment,增加合并与资源开销。

追问 3:删除后磁盘会立刻释放吗?

通常不会。不可变 Segment 先记录删除标记,实际空间在后续 Merge 重写文件时回收。

机制全景图

下面把「Elasticsearch 倒排索引原理是什么?」从输入到结果压缩成一条可复述的主链路。面试时先用图建立全局坐标,再进入局部实现,能避免只背零散结论。

flowchart LR
    A["文档字段进入 Analyzer"]
    A --> B["生成规范化 Term"]
    B --> C["写入倒排词典与 Postings"]
    C --> D["查询解析得到 Term"]
    D --> E["求交并评分返回文档"]

完整链路:从输入到结果

沿着「文档字段进入 Analyzer → 生成规范化 Term → 写入倒排词典与 Postings → 查询解析得到 Term → 求交并评分返回文档」观察输入、状态与输出,下面每个阶段都对应一个可以在源码、日志或系统表中验证的位置。

1. 文档字段进入 Analyzer

文本按字符过滤、Tokenizer 和 Token Filter 分析,索引与查询分析器不一致会导致看似存在却搜不到。

2. 生成规范化 Term

Term 是索引最小词项,大小写、词干、同义词和中文分词都在这里决定召回边界。

3. 写入倒排词典与 Postings

倒排表从 Term 指向包含它的文档 ID,并可保存词频、位置和 offset 支持评分与短语。

4. 查询解析得到 Term

查询字符串被同样或指定 Analyzer 处理,term 查询则通常不分析输入。

5. 求交并评分返回文档

执行器对 postings 求交/并集并结合 BM25 等评分,最终从正排 doc values 或 _source 取字段。

源码与实现定位

入口 阅读重点
org.apache.lucene.index Terms/Postings/Segment
_analyze/_termvectors Token 与词项证据

源码或系统表应按上表顺序追踪:先确认入口实际走到哪条路径,再用运行时数据验证,而不是仅凭类名或配置推测。

参数配置与可复现实验

POST products/_analyze
{"field":"title","text":"Java并发编程"}

对同一语料比较 keyword、中文 Analyzer 和 ngram 的词项数、召回与索引体积。

验证步骤与预期结果

1. 固定输入和基线

先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「零结果率」为主基线,记录值应满足「记录稳态基线」;同时保存 分析后 token 列表、Term 命中 doc_freq,使后续变化能够回到同一时间轴比较。

2. 从实现入口确认路径

在「org.apache.lucene.index」确认请求确实进入「Terms/Postings/Segment」对应的实现,再沿「_analyze/_termvectors」观察「Token 与词项证据」。如果入口路径都未命中,就不应继续调整下游参数,而应先检查调用条件、版本或路由是否与假设一致。

3. 注入本文特有的失败模式

优先复现「索引与查询分词器不一致」,并把单一变量逐级放大,直到「零结果率」越过「超过基线2倍」。随后再分别验证「用 term 查询分析型 text 字段」和「同义词更新造成历史索引与新规则不一致」,三类故障分开执行,避免多个变量同时变化而无法归因。

4. 执行止损和根因修复

第一轮只应用「固定索引/查询 Analyzer」,确认它能控制影响范围;第二轮应用「text+keyword 多字段」,验证核心链路恢复;最后落实「词典变更走重建」,消除同类问题再次出现的条件。每一步都保留变更前后数据,不用“感觉变快了”替代测量。

5. 通过退出条件

实验只有同时满足三项才算通过:「零结果率」回到「记录稳态基线」、「P99」回到「小于业务预算」、「结果差异」回到「0」,并且业务结果差异为零。若性能恢复但结果不一致,仍应视为失败;若指标恢复后很快再次越线,则说明只完成了临时止损,没有消除根因。

量化基线

指标 样例基线/口径 风险线 结论
零结果率 记录稳态基线 超过基线2倍 按实现入口定位
P99 小于业务预算 突破预算 停止扩量
结果差异 0 任意非零 回滚并重建

这些数值是实验口径或示例告警线,不是可复制到所有系统的固定答案;上线阈值应由本系统稳态、峰值和故障演练共同确定。

事故复盘:商品标题明明包含关键词却搜索不到

字段使用 keyword 映射,索引中只有完整标题一个 Term,match 查询分析出的单词无法命中。改为 text 用于全文检索、keyword 子字段用于聚合排序后语义才分离。

失败模式 首要证据 第一处置动作
索引与查询分词器不一致 分析后 token 列表 固定索引/查询 Analyzer
用 term 查询分析型 text 字段 Term 命中 doc_freq text+keyword 多字段
同义词更新造成历史索引与新规则不一致 召回率与零结果率 词典变更走重建

发布与回滚检查点

  • 发布前:确认「org.apache.lucene.index」对应实现和上述配置在目标版本仍然有效,并保存「零结果率」基线。
  • 灰度中:同时观察 分析后 token 列表、Term 命中 doc_freq、召回率与零结果率;任一指标越过表中风险线,就停止继续扩量。
  • 回滚时:先执行「固定索引/查询 Analyzer」控制影响,再回退代码或参数;涉及持久状态时必须额外核对结果差异。
  • 发布后:至少覆盖一个完整峰值周期,确认「索引与查询分词器不一致」没有再次出现,才关闭变更观察窗口。

方案对比与选型

方案 更适合的场景 主要收益 代价与边界
text + Analyzer 全文检索、相关性和短语 支持分词与评分 聚合排序需额外 keyword 子字段
keyword 精确过滤、聚合和排序 Term 稳定、doc values 友好 不做全文分词
search_as_you_type/edge ngram 前缀联想 低延迟候选召回 索引体积和写入成本增加

选型至少带上 文档规模、分片数、字段基数、查询并发和写入速率,并用上面的量化基线验证;未知数据应明确为待测假设。

设计边界与工程取舍

倒排索引优化 Term 到文档的查找,不适合任意关系 Join;Mapping 和 Analyzer 一旦写入历史文档,修改通常需要重建索引。

工程落地遵循:先设计 Mapping 与分片,再优化查询;任何调优都要控制扫描与内存放大。回答时直接引用「org.apache.lucene.index」、配置实验和事故数据,比复述固定模板更有说服力。