核心答案
倒排索引建立“词项到文档”的映射。查询时先找到词项,再读取包含该词项的文档列表,因此适合全文搜索,而不需要逐条扫描原始文本。
分析过程
文本字段写入时经过 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」、配置实验和事故数据,比复述固定模板更有说服力。