当前位置: 首页 > news >正文

【Elasticsearch从入门到精通】第43篇:Elasticsearch搜索过程原理——分词、查询树与BM25评分

上一篇【第42篇】Elasticsearch倒排索引原理——Lucene的核心数据结构
下一篇【第44篇】Elasticsearch分布式索引原理——分片路由与写入流程


摘要

搜索是Elasticsearch的核心能力,而理解其背后的原理是写好查询、调优相关性的关键。本文从搜索引擎的底层出发,逐层剖析一条查询从输入到返回结果的完整旅程:首先介绍文本分析的三层处理架构(字符过滤器→分词器→词元过滤器),然后讲解查询语法分析如何将用户输入构建为查询树,接着深入倒排索引的搜索流程(Term查找→文档列表合并→得分计算),最后重点解析TF-IDF的直觉理解与BM25算法的核心改进。文中通过完整的REST API代码示例和TF-IDF与BM25参数对比表格,帮助读者建立起从理论到实践的完整知识体系。关键词:Elasticsearch搜索原理、分词器、BM25评分、倒排索引、相关性排序。


一、搜索引擎为什么需要"索引"

在理解Elasticsearch的搜索原理之前,我们先回答一个基础问题:为什么需要搜索引擎?

传统的数据库查询(如MySQL的LIKE查询)属于顺序查找。当我们要在100万条文档中找到包含"历史"关键字的记录时,数据库需要逐行遍历、逐字段扫描,时间复杂度为 O(n)。这种查找方式在数据量较小的时候表现尚可,但当文档数量达到百万、千万级别时,性能会急剧下降。

搜索引擎采用了完全不同的策略——索引查找。它的核心理念是:

顺序查找: 文档1 → 扫描全文 → 没有 → 文档2 → 扫描全文 → 没有 → ... → 文档N → 找到了 索引查找: 创建索引时:文档 → 分词 → 建立 "词→文档列表" 映射 搜索时:输入关键词 → 直接查找映射表 → 立即定位文档

搜索引擎在写入数据时将非结构化内容中的信息提取出来,重新组织成结构化数据,这部分数据就叫作索引。这种"用空间换时间"的策略使得搜索过程从 O(n) 降低到 O(1) 级别,是搜索引擎高性能的根本保证。


二、文本分析:分词器三层处理架构

当一条文档被写入Elasticsearch时,它首先要经历**文本分析(Analysis)**过程。这是搜索引擎的第一道工序,也是最容易被忽视的一环——分词结果的好坏直接决定了后续搜索的召回率和准确率。

Elasticsearch的分析器由三个核心组件按顺序组成,形成三层流水线处理架构

原始文本 "The iPhone 14 Pro Max costs $1099!" │ ▼ ┌─────────────────────────┐ │ 1. 字符过滤器 │ 过滤/替换无意义字符 │ (Character Filter) │ 如:HTML标签清理、表情符号替换 └────────────┬────────────┘ │ "The iPhone 14 Pro Max costs 1099!" ▼ ┌─────────────────────────┐ │ 2. 分词器 │ 将文本切分为词元(Term) │ (Tokenizer) │ 如:按空格/标点切分 └────────────┬────────────┘ │ ["The", "iPhone", "14", "Pro", "Max", "costs", "1099"] ▼ ┌─────────────────────────┐ │ 3. 词元过滤器 │ 对词元进行加工处理 │ (Token Filter) │ 如:转小写、去除停用词、同义词扩展 └────────────┬────────────┘ │ ["iphone", "14", "pro", "max", "cost", "1099"] ▼ 最终索引的Term列表

2.1 字符过滤器(Character Filter)

字符过滤器工作在原始文本层面,在分词之前对字符流进行预处理。它主要完成三类任务:

  • HTML Strip:去除HTML标签,将<b>iPhone</b>处理为iPhone
  • Mapping:字符映射替换,如将&替换为and,或将全角字符转为半角
  • Pattern Replace:基于正则表达式的替换,如过滤掉敏感信息

2.2 分词器(Tokenizer)

分词器负责将字符流切分为独立的词元(Term)。这是分析器的核心组件,一个分析器有且仅有一个分词器。Elasticsearch内置了多种分词器:

  • standard:默认分词器,按Unicode文本分段算法切分,去除标点并转小写
  • whitespace:仅按空白字符切分,不做任何其他处理
  • keyword:不分词,将整个字段值作为一个Token
  • pattern:按正则表达式切分
  • ik_smart/ik_max_word(IK分词器):专门针对中文的分词模式

2.3 词元过滤器(Token Filter)

词元过滤器对切分后的Token进行二次加工,可以有零个或多个。常见的Token Filter包括:

  • lowercase:将所有Token转为小写
  • stop:移除停用词(如"的"、“是”、“the”、“a”)
  • synonym:同义词扩展(如"手机"→"移动电话,cellphone")
  • stemmer:词干提取(如"running"→"run")
  • shingle:生成N-gram词组

2.4 使用_analyzeAPI 测试分词效果

任何阶段的排查和调优都离不开_analyzeAPI。以下代码演示如何一步步测试分析效果:

// 1. 测试 standard 分析器的完整处理结果POST_analyze{"analyzer":"standard","text":"The iPhone 14 Pro Max costs $1099!"}// 返回结果(省略部分字段){"tokens":[{"token":"the","start_offset":0,"end_offset":3,"position":0},{"token":"iphone","start_offset":4,"end_offset":10,"position":1},{"token":"14","start_offset":11,"end_offset":13,"position":2},{"token":"pro","start_offset":14,"end_offset":17,"position":3},{"token":"max","start_offset":18,"end_offset":21,"position":4},{"token":"costs","start_offset":22,"end_offset":27,"position":5},{"token":"1099","start_offset":29,"end_offset":33,"position":6}]}// 2. 仅测试分词器(跳过字符过滤器和词元过滤器)POST_analyze{"tokenizer":"standard","text":"The iPhone 14 Pro Max costs $1099!"}// 3. 自定义分析器组合测试POST_analyze{"char_filter":["html_strip"],"tokenizer":"standard","filter":["lowercase","stop","stemmer"],"text":"The <b>iPhone</b> 14 Pro Max costs $1099!"}// 4. 测试中文分词(需安装IK分词器)POST_analyze{"tokenizer":"ik_max_word","text":"中华人民共和国国歌"}// 返回:["中华人民共和国", "中华人民", "中华", "华人", "人民共和国", "人民", "共和国", "共和", "国", "国歌"]

最佳实践建议:每个索引的Mapping都应该显式指定分析器,而不是依赖默认值。分析器的选择直接影响搜索体验,应当先在测试环境通过_analyzeAPI 验证分词效果,再上线生产。


三、查询语法分析:构建查询树

当用户在搜索框中输入 “iPhone 14” 并按下回车键后,Elasticsearch并不会立即去倒排索引中查找Term,而是先进行查询语法分析

3.1 查询语法分析的两大任务

查询语法分析需要完成两个关键任务:

  1. 确定各个Term的搜索字段:用户输入的关键词应该在哪个(或哪些)字段中搜索?是title还是content?如果是multi_match查询,则需要确定权重分配。

  2. 确定Term之间的逻辑组合关系:多个关键词之间是 AND 关系(同时匹配)还是 OR 关系(任一匹配)?用户是否指定了排序、分页等自定义条件?

3.2 查询树的构建过程

Elasticsearch的查询语法分析会将用户输入构建为一棵查询树(Query Tree)。查询树的结构决定了后续倒排索引搜索时的文档列表合并策略。

以搜索 “iPhone 14” 为例,构建查询树的过程如下:

用户输入: "iPhone 14" │ ▼ ┌─────────────────┐ │ 词法分析 │→ 切分为两个Term: "iphone", "14" │ (分词器处理) │ └────────┬─────────┘ │ ▼ ┌─────────────────┐ │ 语法分析 │→ 确定搜索字段:title, description │ (构建查询树) │→ 确定逻辑关系:should (OR) └────────┬─────────┘ │ ▼ Boolean Query (查询树根节点) │ ┌────┴────┐ │ │ TermQuery TermQuery "iphone" "14" (title) (title) (desc) (desc)

AND 关系的查询树(match查询使用operator: “and”):

BooleanQuery (must) │ ┌────┴────┐ │ │ TermQuery TermQuery "iphone" "14"

3.3 完整DSL示例

// 默认OR关系查询GET/products/_search{"query":{"match":{"title":{"query":"iPhone 14","operator":"or"}}}}// AND关系查询(要求两个词同时出现)GET/products/_search{"query":{"match":{"title":{"query":"iPhone 14","operator":"and"}}}}// 多字段查询(cross_fields类型将各字段视为一个大字段)GET/products/_search{"query":{"multi_match":{"query":"iPhone 14","fields":["title^3","description","brand"],"type":"cross_fields","operator":"and"}}}

四、倒排索引搜索流程

查询树构建完毕后,就进入搜索引擎最核心的阶段——倒排索引搜索。这是一个三段式流水线:Term查找 → 文档列表合并 → 得分计算

4.1 Term查找

倒排索引的本质是一个"词→文档ID列表"的映射表。当查询树中的每个TermQuery被执行时,Lucene直接去倒排索引中查找该Term对应的倒排列表(Posting List):

倒排索引结构(简化): Term → Posting List ───────────────────────────────────── "iphone" → [doc1, doc3, doc7, doc15, doc22, ...] "14" → [doc3, doc7, doc9, doc15, doc20, ...] "pro" → [doc1, doc15, doc18, doc27, ...] "max" → [doc1, doc15, doc22, doc30, ...]

4.2 文档列表合并(AND/OR 操作)

获取到各Term的Posting List后,根据查询树的逻辑关系进行列表合并:

OR关系(should)—— 取并集

Term "iphone" 的列表: [doc1, doc3, doc7, doc15, doc22] Term "14" 的列表: [doc3, doc7, doc9, doc15, doc20] ↓ OR(合并+去重) 结果列表: [doc1, doc3, doc7, doc9, doc15, doc20, doc22]

AND关系(must)—— 取交集

Term "iphone" 的列表: [doc1, doc3, doc7, doc15, doc22] Term "14" 的列表: [doc3, doc7, doc9, doc15, doc20] ↓ AND(双指针求交集) 结果列表: [doc3, doc7, doc15]

Lucene内部使用**跳表(Skip List)**数据结构优化AND/OR操作,使得多列表合并的效率接近 O(min(len(A), len(B)))。

4.3 得分计算

文档列表合并完成后,对结果集中的每个文档计算相关性得分(Score)。得分计算的完整公式将在下一节详细介绍,这里先给出计算的三个核心维度:

  1. 词频(TF):查询词在文档中出现了多少次?出现次数越多,相关性越高。
  2. 逆文档频率(IDF):查询词在整个索引中出现在了多少文档中?出现越少(越稀有),相关性越高。
  3. 字段长度归一化:字段越长,每个词的重要性越低。

得分计算完成后,按Score降序排序,最终返回 Top N 结果给用户。


五、TF-IDF的直觉理解

在深入BM25算法之前,先理解TF-IDF的直觉是必要的。TF-IDF虽然已经不再是Elasticsearch的默认评分算法(从5.0开始默认使用BM25),但它的设计思想影响了所有现代评分模型。

5.1 TF(词频):词频越高越相关

直觉:如果搜索词"Java"在文档A中出现了100次,在文档B中只出现了1次,那么文档A显然与"Java"更相关。

TF(t, d) = 词t在文档d中出现的次数

5.2 IDF(逆文档频率):文档越稀少越相关

直觉:如果搜索词"Elasticsearch"只出现在很少的文档中,而搜索词"的"出现在几乎全部文档中,那么匹配到"Elasticsearch"的文档比匹配到"的"的文档更有价值。

IDF(t) = log( 总文档数N / 包含词t的文档数n(t) )

含义解读:当 n(t) 很小时,N/n(t) 很大,log值大,说明这个词很"珍贵";当 n(t) 接近N时,N/n(t)≈1,log值≈0,说明这个词几乎没有区分度。

5.3 TF-IDF的问题

尽管TF-IDF在搜索领域影响深远,但它有两个明显缺陷:

  1. TF的线性增长:词出现100次和出现101次对相关性的贡献应该是接近的,但TF-IDF中二者的得分差距很大——这在直觉上不合理。
  2. 不考虑字段长度:一篇500字的文章中出现5次"Java"和一篇50000字的文章中出现5次"Java",其重要性显然是不同的。

BM25正是针对这两个问题提出了改进方案。


六、BM25算法深度解析

BM25全称Best Matching 25,是BM25F的信息检索排序函数。从Elasticsearch 5.0开始,它取代了TF-IDF成为默认的相关性评分算法。

6.1 BM25的核心公式

BM25的得分公式如下:

score(D, Q) = Σ IDF(qi) × ( f(qi, D) × (k1 + 1) ) / ( f(qi, D) + k1 × (1 - b + b × |D| / avgdl) ) 其中: D = 文档(Document) Q = 查询语句(Query) qi = 查询Q分词后的第i个词元 f(qi, D) = 词元qi在文档D中出现的频次 |D| = 文档D的字段长度(词元数) avgdl = 索引中所有文档的平均字段长度 k1 = 可调参数,控制词频的饱和度(默认 1.2) b = 可调参数,控制字段长度归一化的程度(默认 0.75)

6.2 BM25对TF-IDF的两大改进

改进一:TF饱和度控制(k1参数)

BM25对词频引入了非线性饱和度函数。随着词频增加,得分增长逐渐变缓,最终趋近于一个上限:

TF-IDF: score ∝ TF (线性增长,永无止境) BM25: score ∝ TF / (TF + k1) (非线性增长,趋于饱和)

直观理解:

  • 词频从0→1:得分大幅提升(从无到有的质变)
  • 词频从1→2:得分小幅提升
  • 词频从10→11:得分几乎没有变化(已经饱和)

k1的默认值为1.2。值越小,饱和度越明显;值越大,越接近线性增长。

改进二:字段长度归一化(b参数)

BM25通过b参数控制字段长度对得分的影响程度:

  • b = 1:完全归一化,长文档的每个词权重被完全稀释
  • b = 0:完全不归一化,字段长度不影响得分
  • b = 0.75(默认):折中方案

6.3 k1和b参数调优指南

参数默认值含义调增方向适用场景
k11.2控制词频对得分的影响增大→词频影响更线性短文档搜索(标题/标签)
减小→词频饱和更快长文档搜索(文章内容)
b0.75控制字段长度归一化程度增大→惩罚长文档更重短文段与长文本混合搜索
减小→长度影响变小字段长度相对均匀的场景

6.4 TF-IDF vs BM25 对比

对比维度TF-IDFBM25
词频增长模型线性增长,无上限非线性增长,趋于饱和
字段长度影响不考虑通过b参数归一化
可调参数k1, b 两个调优参数
评分合理性长文档易被"埋没"自动适配不同长度
边界情况极端词频时不合理饱和机制保证稳定
Elasticsearch支持已废弃,不再默认5.0+默认评分算法
自定义参数DSL"type": "tfidf"通过similarity配置

七、实战:搜索"iPhone 14"的完整执行过程

让我们通过一个具体案例,完整追踪一条搜索请求从发出到返回结果的全过程。

场景设定

假设我们有一个商品索引shop,包含以下文档:

// 索引MappingPUT/shop{"mappings":{"properties":{"title":{"type":"text","analyzer":"standard","similarity":"BM25"},"description":{"type":"text","analyzer":"standard"},"brand":{"type":"keyword"},"price":{"type":"double"}}}}// 插入测试数据POST/shop/_bulk{"index":{"_id":1}}{"title":"iPhone 14 Pro Max 256GB","description":"Apple最新旗舰手机 iPhone 14 Pro Max","brand":"Apple","price":8999}{"index":{"_id":2}}{"title":"iPhone 14 Plus 128GB","description":"大屏iPhone 14 Plus 超长续航","brand":"Apple","price":6999}{"index":{"_id":3}}{"title":"iPhone 14 标准版","description":"年度主打iPhone 14 超强性能","brand":"Apple","price":5999}{"index":{"_id":4}}{"title":"华为Mate 60 Pro","description":"华为旗舰拍照手机 卫星通信","brand":"Huawei","price":6999}{"index":{"_id":5}}{"title":"小米14 Ultra","description":"小米14旗舰 徕卡影像","brand":"Xiaomi","price":5999}

执行搜索

GET/shop/_search{"query":{"multi_match":{"query":"iPhone 14","fields":["title^2","description"],"operator":"and"}},"explain":true}

完整执行过程追踪

═══════════════════════════════════════════════════════ 搜索 "iPhone 14" 完整执行过程 ═══════════════════════════════════════════════════════ 第1步:接收请求(协调节点) ┌─────────────────────────────────────┐ │ 协调节点收到请求: "iPhone 14" │ │ - operator: AND │ │ - 字段权重: title^2, description^1 │ └─────────────────────────────────────┘ 第2步:查询分析(Query Parsing) ┌─────────────────────────────────────┐ │ 分词器处理查询字符串 "iPhone 14" │ │ → 得到两个Term: "iphone", "14" │ │ │ │ 语法分析构建查询树: │ │ BooleanQuery (must/AND) │ │ ├─ TermQuery("iphone") │ │ │ 在 title^2, description^1 │ │ └─ TermQuery("14") │ │ 在 title^2, description^1 │ └─────────────────────────────────────┘ 第3步:反向索引查找(Term Lookup) ┌─────────────────────────────────────┐ │ 倒排索引查找: │ │ Term "iphone" → Posting List: │ │ title字段: [doc1, doc2, doc3] │ │ desc字段: [doc1, doc2, doc3] │ │ │ │ Term "14" → Posting List: │ │ title字段: [doc1, doc2, doc3, doc5] │ │ desc字段: [doc2] │ └─────────────────────────────────────┘ 第4步:文档列表合并(AND操作) ┌─────────────────────────────────────┐ │ 多字段合并后的文档列表: │ │ "iphone" 匹配: [doc1, doc2, doc3] │ │ "14" 匹配: [doc1, doc2, doc3, │ │ doc5] │ │ AND 交集结果: [doc1, doc2, doc3] │ │ │ │ doc4: 未匹配 "iphone" → 淘汰 │ │ doc5: "14" 只在title出现,但desc没有 │ │ "iphone" → 淘汰(AND关系) │ └─────────────────────────────────────┘ 第5步:BM25评分计算 ┌─────────────────────────────────────┐ │ 对 doc1, doc2, doc3 逐一计算Score: │ │ │ │ doc1 ("iPhone 14 Pro Max 256GB"): │ │ IDF(iphone) × TF饱和 × 长度归一化 │ │ + IDF(14) × TF饱和 × 长度归一化 │ │ = 较高分(两个词都密集出现在短标题中) │ │ │ │ doc2 ("iPhone 14 Plus 128GB"): │ │ 同理计算 = 高分 │ │ │ │ doc3 ("iPhone 14 标准版"): │ │ 同理计算 = 高分 │ └─────────────────────────────────────┘ 第6步:排序与返回 ┌─────────────────────────────────────┐ │ 按Score降序排列: │ │ 1. doc1: score=2.85 (标题两次命中+权重) │ │ 2. doc2: score=2.63 │ │ 3. doc3: score=2.41 │ │ │ │ 返回Top N结果给客户端 │ └─────────────────────────────────────┘

使用_explainAPI 查看评分细节

// 查看特定文档的评分明细GET/shop/_explain/1{"query":{"multi_match":{"query":"iPhone 14","fields":["title^2","description"],"operator":"and"}}}// 该接口返回详细的计算过程,包括每个Term的IDF值、TF值、// 字段长度、k1/b参数以及最终加权得分。

八、总结与最佳实践

核心要点回顾

  1. 搜索引擎通过倒排索引实现O(1)级别的关键词查找,将非结构化文本转化为结构化的 “Term→文档列表” 映射。
  2. 文本分析是搜索质量的基础,字符过滤器→分词器→词元过滤器的三层架构决定了哪些Term能被搜索到,务必在写入前通过_analyzeAPI 验证分词效果。
  3. 查询树结构决定了文档列表的合并策略,AND关系取交集(精确匹配),OR关系取并集(广泛召回),根据业务场景合理选择。
  4. BM25通过TF饱和度(k1)和字段长度归一化(b)两大改进,解决了TF-IDF的两个核心缺陷,是更合理、更通用的评分算法。

最佳实践清单

实践建议详细说明
显式指定analyzer每个text字段在Mapping中明确指定analyzer,不依赖默认值
写入前验证分词使用POST _analyze测试分词效果,确保与预期一致
搜索时使用相同分析器搜索时用search_analyzer指定,确保搜索结果准确性
合理选择AND/OR精确匹配用AND,广泛召回用OR,高精度场景用minimum_should_match
调优BM25参数根据文档长度分布特征调整k1和b,长短文本混合场景适当降低k1
使用explain调试_explainAPI 可查看每篇文档的详细评分过程,是调优的利器
权重设置multi_match使用^符号为不同字段设置权重,如"title^3"

上一篇【第42篇】Elasticsearch倒排索引原理——Lucene的核心数据结构
下一篇【第44篇】Elasticsearch分布式索引原理——分片路由与写入流程


http://www.jsqmd.com/news/891356/

相关文章:

  • 成都中视新影:专注宣传片定制的综合性头部传媒机构 - 奔跑123
  • tools.simonwillison.net的实用时间工具:时区转换、日期计算与时间戳
  • 避坑指南!2026 深圳 LV、香奈儿、爱马仕回收哪家好! - 奢侈品回收测评
  • YOLOv12无人机小目标检测优化:切片对比与两阶段训练实战
  • 摆脱论文困扰!盘点2026年风靡全网的的降AIGC网站
  • 2026年厄瓜多尔建材五金展 Constructor - 中国组团单位- 新天国际会展 - 新天国际会展
  • 【Elasticsearch从入门到精通】第45篇:Elasticsearch分布式检索原理——Query Then Fetch两阶段搜索
  • 2026年Q2高清投屏与屏幕镜像软件精选榜单,热门免费跨屏工具实用盘点
  • 2026抑尘剂核心生产厂家实力排行与性能对比 推荐任丘市双成化工产品厂 - 奔跑123
  • 视频目标检测中信息泄露的根源与基于聚类的数据划分解决方案
  • 西安系统门窗品牌推荐榜:5家靠谱本地厂商深度测评(2026版) - 深度智识库
  • UABEAvalonia:如何为现代Unity游戏资源管理提供跨平台解决方案?
  • 终极macOS Windows启动盘制作工具:3个核心问题一键解决
  • 2026 年河南巨量本地推推广公司推荐,结合 GEO 优化抓取 AI 搜索流量 - 企品推
  • 联邦学习在网络威胁情报共享中的应用:FedScope系统设计与实践
  • go-workers源码解析:深入理解Golang任务队列的实现原理
  • 嵌入式视觉传感软体手指:基于内部点阵变形实现多模态感知
  • 2026一键去水印工具怎么选?免费一键去水印工具大盘点 - 科技热点发布
  • iniparser配置管理最佳实践:从简单应用到复杂企业级系统的演进
  • Windows安全中心深度解析:如何通过WSC API绕过Windows Defender防护
  • 2026年混料系统老牌公司有哪些?混料设备企业实力推荐 - 品牌2025
  • BilibiliDown:一站式B站视频下载解决方案,让你的收藏永不丢失
  • 如何用ContextMenuManager拯救你的Windows右键菜单:3分钟告别混乱,效率翻倍
  • 珍宝黄金回收(十年老店)|2026年5月唐山黄金回收多少钱一克,实体老店,诚信经营 - 润富黄金珠宝行
  • 中石化加油卡回收四步走实测,猎卡回收正规流程与到账参考 - 京回收小程序
  • 【Elasticsearch从入门到精通】第44篇:Elasticsearch分布式索引原理——分片路由与写入流程
  • 5步掌握Auto.js:解放双手的Android自动化神器
  • 终极隐私保护指南:使用Privacy工具检测个人数据泄露的完整教程
  • 成都中视新影:覆盖全品类宣传片定制的头部传媒机构 - 奔跑123
  • 2026安徽省界首市寄快递省钱攻略!4个正规低价平台,告别线下寄件溢价陷阱 - 时讯资讯