Elasticsearch使用
安装
docker安装 es
拉取镜像
1 | docker pull elasticsearch:7.3.0 |
准备好数据挂载文件夹,比如
E:\docker\elasticsearch,先启动一个容器
1 | docker run -d --name es7 -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms1024m -Xmx1024m" elasticsearch:7.3.0 |
复制需要挂载的文件夹
1 | docker cp es7:/usr/share/elasticsearch/config E:\docker\elasticsearch |
修改
/config文件夹下的elasticsearch.yml文件如下,cluster.name可自己指定
1 | cluster.name: "elasticsearch-cluster" |
删除刚刚创建的容器
1 | docker rm -f es7 |
新建容器,使用挂载的文件夹
1 | docker run -d --name es7 -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms1024m -Xmx1024m" -v E:\docker\elasticsearch\config:/usr/share/elasticsearch/config -v E:\docker\elasticsearch\data:/usr/share/elasticsearch/data -v E:\docker\elasticsearch\plugins:/usr/share/elasticsearch/plugins -v E:\docker\elasticsearch\logs:/usr/share/elasticsearch/logs elasticsearch:7.3.0 |
浏览器访问 http://localhost:9200/ 如下:

可视化工具 Kibana 安装
拉取镜像
1 | docker pull kibana:7.3.0 |
准备好挂载的文件夹,先运行一个容器
1 | docker run -d --name kibana -p 5601:5601 kibana:7.3.0 |
复制配置到挂载的文件夹
1 | docker cp kibana:/usr/share/kibana/config E:\docker\kibana |
编辑配置文件
kibana.yml如下
1 | server.name: kibana |
如何确定本机
IP地址:windows系统终端执行ipconfig命令,复制IPV4地址即可
删除刚刚运行的容器,并重新使用挂载的文件夹数据运行一个新的容器
1 | docker rm -f kibana |
1 | docker run -d --name kibana -p 5601:5601 -v E:\docker\kibana\config:/usr/share/kibana/config kibana:7.3.0 |
浏览器访问 http://localhost:5601 即可访问控制台

Kibana 内置的 Dev Tools 提供了一个与 Elasticsearch 交互的界面,主要特点:
- 类似命令行的输入界面:直接输入 Elasticsearch 的 RESTful API 调用,查询数据、创建索引、更新设置等都可以。
- 历史记录:保存之前的查询,方便回顾或重用。
- 语法高亮和自动补全:JSON 语法高亮,API 方法和参数有补全提示。
- 响应格式化:返回的 JSON 自动格式化,便于阅读。

Elasticsearch 基本请求格式
Elasticsearch 提供 REST API,常见请求格式如下:
1 | GET /索引名/_search |
Elasticsearch 基础语法学习
1. 创建索引
创建一个名为
users的索引。
1 | PUT /users |
2. 新增文档
指定 ID 新增
1 | PUT /users/_doc/1 |
自动生成 ID 新增
1 | POST /users/_doc |
3. 根据 ID 查询文档
1 | GET /users/_doc/1 |
4. 搜索文档
查询所有文档
1 | GET /users/_search |
条件搜索
查询
name包含张的文档:
1 | GET /users/_search |
精确查询
查询
1 | GET /users/_search |
范围查询
查询年龄大于等于 20 的文档:
1 | GET /users/_search |
分页查询
1 | GET /users/_search |
参数含义:
from:从第几条开始size:每页返回多少条
5. 更新文档
局部更新
只更新某个字段:
1 | POST /users/_update/1 |
增加字段
1 | POST /users/_update/1 |
6. 删除文档
删除 ID 为 1 的文档:
1 | DELETE /users/_doc/1 |
7. 删除索引
删除整个 users 索引:
1 | DELETE /users |
8. 查看索引是否存在
1 | HEAD /users |
如果存在,返回状态码:
1 | 200 |
如果不存在,返回:
1 | 404 |
9. 查看索引信息
1 | GET /users |
查看索引 mapping:
1 | GET /users/_mapping |
查看索引 settings:
1 | GET /users/_settings |
10. 最小可用 CRUD 总结
增
1 | PUT /users/_doc/1 |
查
1 | GET /users/_doc/1 |
改
1 | POST /users/_update/1 |
删
1 | DELETE /users/_doc/1 |
记住这个格式就行:
1 | PUT /索引/_doc/ID 新增或覆盖文档 |
倒排索引
认识
倒排索引是从关键词到文档的映射。它与传统的正排索引不同,正排索引存储的是文档及其内容,而倒排索引反其道而行之,存储的是每个关键词以及包含这些关键词的文档信息。

简单示例:
假设我们有以下文档,分别对应一条中文句子:
- 文档 1: Elasticsearch 是分布式引擎
- 文档 2: 分布式系统的核心是搜索
- 文档 3: 搜索引擎的未来是智能化
倒排索引将这些文档转化为如下形式:
| 关键词 | 文档 ID |
|---|---|
| elasticsearch | 1 |
| 分布式 | 1, 2 |
| 系统 | 2 |
| 核心 | 2 |
| 搜索 | 2, 3 |
| 引擎 | 1, 3 |
| 智能化 | 3 |
当用户搜索某个关键词,如“引擎” 时,Elasticsearch 可以通过倒排索引快速查找到关键词对应的文档 ID(文档 1 和文档 3),而无需扫描所有文档。
构建过程
1. 分词(Tokenization)
分词是将文本内容拆分为独立的单词或短语,例如:
1 | 原语句:Elasticsearch是分布式引擎 |
2. 归一化(Normalization)
归一化是对分词结果进行标准化处理,包括:
- 转小写:将所有大写字母转为小写。例如
Elasticsearch→elasticsearch。 - 去除停用词:删除一些对查询结果影响不大的词,如“的”“是”等。
- 词干提取:将词汇归为基本形式,如
running→run。
3. 创建倒排索引
根据分词结果,为每个词项建立一个到文档的映射关系。最终生成的倒排索引可以看作如下结构,然后存储起来:
1 | { |
此时,倒排索引已经完成,后续的搜索操作会以此为基础。
ik 中文分词器插件
安装
打开终端进入
es7容器的内部
1 | docker exec -it es7 /bin/sh |
执行安装命令
1 | bin/elasticsearch-plugin install https://gt.infini.cloud/elasticsearch/analysis-ik/7.3.0 |
安装完成后,执行如下命令,退出容器:
1 | exit |
然后,执行
docker cp命令,将已安装的ik插件相关文件,复制到宿主机中,防止丢失:
1 | docker cp es7:/usr/share/elasticsearch/plugins/analysis-ik E:\docker\elasticsearch\plugins |
重启容器
1 | docker restart es7 |
分词模式
智能分词:ik_smart
1 | POST /_analyze |

最细粒度分词:ik_max_word
1 | POST /_analyze |

进阶使用
索引
常用属性
顶层控制属性
dynamic
场景:你定义了 title 是 text,结果代码不小心多传了个 age 字段。
dynamic: true(默认):ES 默默帮你在映射里加上age,类型可能错判成long,导致后续范围查询异常。dynamic: strict(推荐):直接拒绝写入,返回明确报错,倒逼你修正代码。
示例(严格模式下的报错):
1 | # 1. 创建索引,开启 strict |
fields + ignore_above
场景:title 既要全文搜索(用 text),又要按字母排序(必须用 keyword)。同时,为了防止有人存超长文本撑爆内存,用 ignore_above 切断。
1 | # 1. 创建索引 |
copy_to
场景:用户想搜索 title 或 content,不想分两个字段查,希望一个 full 字段覆盖所有。用 copy_to 把多个字段的内容复制到一个隐藏字段里。
1 | # 1. 创建索引 |
format
场景:你的数据来源复杂,有时候传 "2026-09-05",有时候传 "2026/09/05",默认格式是 strict_date_optional_time||epoch_millis,只认 ISO 风格(如 2026-09-05、2026-09-05T10:30:00Z)和毫秒时间戳,不认斜杠。
1 | # 1. 创建索引,指定多种兼容格式(用 || 分隔) |
null_value
场景:有些文档的 tags 字段没填,是 null。默认情况下,null 值不会建立倒排索引,你搜 tags: "NULL" 搜不到。如果你想统一把空值替换成占位符,就用 null_value。
1 | # 1. 创建索引,将 null 替换成 "EMPTY" |
normalizer
场景:存邮箱或用户名,用户搜 "Admin" 想命中 "admin",但 keyword 是精确匹配,区分大小写。用 normalizer 在索引时统一转小写。
1 | # 1. 先在 settings 里定义自定义 normalizer |
字段级属性
index:搜索开关
场景:有些字段(如内部 ID、base64 图片、加密数据)只需要随文档返回展示,永远不需要被搜索。关闭索引可以节省大量磁盘空间和写入 CPU。
1 | # 1. 创建索引:secret_id 不建索引,name 正常建索引 |
analyzer vs search_analyzer
场景:索引时希望尽可能多地切词(提高召回率),搜索时希望更符合用户意图(提高精准度)。比如中文搜索,索引用 ik_max_word(细粒度),搜索用 ik_smart(粗粒度)。
1 | # 1. 创建索引(假设你已安装 IK 分词器;若未安装,用 standard 做对比演示原理) |
doc_values
原理:text 类型默认 doc_values: false(因为分词后无法做精确排序/聚合),keyword 和数值类型默认 true。如果你错误地关闭了它,排序和聚合会直接报错。
1 | # 1. 创建索引:score 关闭 doc_values |
coerce
场景:你定义字段为 integer,但数据源传来 "10.0"(字符串带小数点)。默认 coerce: true 会自动转成整数 10,不会报错。如果你希望严格校验,就关掉它。
1 | # 1. 创建索引,关闭 coerce(严格模式) |
properties
场景:你的文档里有一个嵌套的 user 对象,包含 name 和 age。必须在 properties 里再定义一次内部字段,否则 ES 会动态映射,类型可能不准。
1 | # 1. 创建索引,定义 object 内部结构 |
enabled
场景:你需要存储一个巨大的、结构不固定的 JSON(如第三方回调的原始报文),只是存着将来展示用,永远不需要在内部字段上做搜索或聚合。开启 enabled: false 可以节省大量索引开销。
1 | # 1. 创建索引,metadata 字段只存不解析 |
字段的数据类型
核心基础类型
1. text vs keyword
text:会分词,用于全文模糊搜索(如百度搜索文章内容)。默认不支持排序和聚合。keyword:不分词,整个值作为一个整体,用于精确匹配、排序、聚合统计(如状态码、分类、邮箱)。
实战对比示例:
1 | # 1. 创建索引,对比 text 和 keyword |
2. long、integer、short、byte、double、float
- 用途:存储数字,用于范围查询(
range)、排序(sort)、数学聚合(sum、avg)。 - 选型原则:尽量选最小的够用的类型,以节省磁盘。
- 人的年龄:
byte(-128~127)就够了。 - 商品价格:用
float或scaled_float(推荐,精确控制小数点)。 - 商品浏览量:用
integer(约 21 亿)通常够。 - 订单金额(精确到分):建议用
long存储“分”的整数值,避免浮点精度丢失。
- 人的年龄:
示例:
1 | PUT /test_numbers |
3. date
- 用途:存储时间,支持范围查询(最近7天)、排序(按发布时间)。
- 关键配置:不指定
format时默认是strict_date_optional_time||epoch_millis,只认 ISO 风格(如2026-09-05T10:30:00Z)和毫秒时间戳;其他格式(如2026-09-05 18:30:00)需要自己指定format。
实战示例(兼容多种格式):
1 | PUT /test_dates |
4. boolean
- 用途:存真/假。
- 注意:除了 JSON 的
true、false,ES 也接受字符串"true"、"false"(以及表示 false 的空字符串"");6.0 起不再接受"on"、"off"、1、0这类写法。
1 | PUT /test_boolean |
复杂结构类型
1. object
- 用途:处理普通的 JSON 嵌套对象。
- 写法:直接用
properties定义内部字段。
1 | PUT /test_object |
2. nested
- 场景:当你的对象是数组,且需要独立维护数组内每个对象的查询条件时,必须用
nested。 - 为什么必须用?如果用默认的
object存数组,ES 会将其扁平化,丢失对象间的边界。
经典错误场景:
1 | # 错误示范:用 object 存员工数组 |
正确写法:
1 | PUT /test_correct_nested |
1. 创建索引
1.1 基础创建(使用默认配置)
最简单的创建方式,Elasticsearch 会应用默认设置(如1个主分片、1个副本分片)并启用动态映射。
1 | PUT /my_first_index |
1.2 指定分片与副本设置
在生产环境中,建议你根据数据量规划好主分片数,并设置副本以提高可用性。
1 | PUT /my_blog |
1.3 定义字段映射 (Mappings)
映射定义了索引中字段的数据类型,搜索和聚合的行为都取决于它。下面的例子定义了一个包含 id(整数)、title(文本,支持全文搜索)和 publish_date(日期)的映射。
1 | PUT /my_blog |
注意:一旦创建,主分片数 (
number_of_shards) 和已有字段的类型通常无法修改。如需更改,通常需要重建索引(Reindex)。
2. 查看索引信息
- 查看单个索引的详细信息(包括设置和映射):
1 | GET /my_blog |
- 查看集群中所有索引的概览:
1 | GET /_cat/indices?v |
- 仅查看索引的设置或映射:
1 | GET /my_blog/_settings |
3. 删除索引
此操作将永久删除整个索引及其包含的所有数据,操作前请务必确认。
1 | DELETE /my_blog |
你也可以使用通配符一次删除多个索引,例如 DELETE /index_*。
4. 更新索引设置
与映射不同,部分索引设置(如副本数)可以动态更新。
1 | PUT /my_blog/_settings |
查询
查询基础
Elasticsearch 所有查询都通过 _search 接口,并使用基于 JSON 的 Query DSL(领域特定语言)来构建,基本结构如下:
1 | GET /your_index/_search |
注意:
_search是固定路径。
常用查询功能详解
Elasticsearch 的查询 DSL 主要分为叶子查询(在单个字段上查询)和复合查询(组合多个叶子查询)。
1. 全文检索
这类查询会对输入的搜索文本进行分词,然后用分词结果去匹配索引中的内容,并根据相关性(_score)排序。
match查询:最常用的全文搜索,用于在一个指定字段中进行分词匹配。
1 | GET /books/_search |
示例场景:在图书描述中搜索包含“分布式”或“搜索引擎”的书籍。
multi_match查询:match的增强版,允许在多个字段中同时进行全文搜索。
1 | GET /books/_search |
match_phrase查询:要求分词后的所有词项必须全部出现在文档中,并且顺序一致、位置连续(允许通过slop参数调整间隔)。它把整个短语当作一个整体来查找,match则会把词项拆开独立匹配。
1 | GET /books/_search |
补充:
在 Elasticsearch 中,
operator主要用于match系列查询(如match、multi_match),用来控制多个搜索词之间的逻辑关系,决定文档需要满足“任意一个”还是“全部”搜索词才算匹配。它接受两个值,默认为
or:
or(默认值):只要文档包含任意一个搜索词,就算匹配。例如,搜索“quick brown fox”,只要文档包含quick、brown或fox中的任意一个词就会被召回。and:文档必须同时包含所有搜索词,才算匹配。例如,搜索“quick brown fox”,文档必须同时包含quick、brown和fox这三个词才会被召回。示例对比:
假设索引中有两篇文档:
- 文档A:
"I love cats"- 文档B:
"I love dogs"执行查询
"match": {"content": {"query": "cats dogs"}}:
operator 参数 |
逻辑 | 匹配结果 |
|---|---|---|
"or"(默认) |
cats OR dogs |
文档A和文档B都会被匹配,因为每篇文档至少包含其中一个词。 |
"and" |
cats AND dogs |
两篇文档都不会被匹配,因为没有文档同时包含cats和dogs。 |
2. 精确查询
这类查询不会对输入文本进行分词,而是将整个输入作为一个词条,与字段中的精确值进行匹配。通常用于 keyword、数值、日期等类型的字段。
term查询:用于查找与指定值完全相等的文档。
1 | GET /books/_search |
注意:不要用 term 查询 text 类型的字段,否则可能查不到结果。
terms查询:term的复数版本,用于匹配多个精确值中的任意一个。
1 | GET /books/_search |
range查询:用于查找字段值在指定范围内的文档。
1 | GET /books/_search |
同样适用于日期范围,如 publish_date 在 "2023-01-01" 之后。
3. 复合查询
用于组合多个查询条件,最常用的是 bool 查询,它包含四个子句:
must:条件必须满足,有算分贡献。filter:条件必须满足,但不算分,用于过滤,性能更高。should:条件满足任意一个即可,有算分贡献。must_not:条件必须不满足,不算分。
示例:查找标题和描述都包含“Elasticsearch”,价格在 30 到 80 之间,且状态为“published”的书籍。
1 | GET /books/_search |
4. 聚合查询
聚合用于对数据分组统计,类似于 SQL 的 GROUP BY 和聚合函数。
1 | GET /books/_search |
这个查询会统计每个图书分类下的文档数量。
kNN 与 BM25 Rescore
版本说明:本节和后面“混合检索”一节的示例需要 Elasticsearch 8.x。
dense_vector的index/similarity/index_options和近似 kNN(当时是单独的_knn_search接口,技术预览)从 8.0 开始提供;在_search里直接写顶层knn、以及knn与query组合的混合检索,要 8.4 及以上。前面安装部分用的 7.3.0 环境跑不了这些示例。
认识
1. kNN
在 kNN 向量检索出现之前,搜索主要依赖关键词匹配:你搜什么词,系统就找包含什么词的文档。这套机制在以下场景中不够用:
- 语义鸿沟:用户搜“性价比高的手机”,系统只认识“性价比”“高”“手机”这几个词,无法理解“划算”“实惠”“值”等语义相近的表达。
- 非文本数据:图片、视频、音频等无法用关键词描述的内容,传统全文检索无能为力。
- 推荐系统:需要根据用户行为找到“相似”的商品或内容,关键词匹配无法表达“相似”这个概念。
kNN 检索的做法是:将文本、图片、用户行为等数据转化为向量(一串数字),用向量之间的“距离”来衡量相似度。你搜“好吃的餐厅”,系统会把这句话转成向量,然后找到向量最接近的那些文档,哪怕文档里没有“好吃”这个词,只要语义相近(如“美味”“口碑不错”),也能被检索出来。
Elasticsearch 从 8.0 版本开始原生支持近似 kNN 向量检索(8.0 时是 _knn_search 接口,8.4 起可以在 _search 里用顶层 knn)。
2. BM25 Rescore
BM25 是 Elasticsearch 默认的相关性评分算法。它根据词频(TF) 、逆文档频率(IDF) 和字段长度来计算文档与查询的相关性。
但 BM25 有一个局限:它只能基于关键词匹配来算分。在复杂场景下,比如:
- 用户希望“苹果手机”这个短语整体匹配,而不是“苹果”和“手机”分开匹配;
- 用户希望用更复杂的模型(如机器学习排序模型)来重新排序;
- 第一轮检索结果不够精准,需要第二轮精细化调整。
如果对所有文档都执行这些复杂计算,性能会急剧下降。
Rescore(二次打分)的思路是:先用廉价的 BM25 快速召回 Top N 个候选文档,然后只对这 N 个文档执行第二轮更精准(但更昂贵)的评分,重新排序。这就像海选先用简单标准筛一遍,决赛再用严格标准精评,质量和成本都能兼顾。
适用场景
| 技术 | 适用场景 | 不适用场景 |
|---|---|---|
| kNN | 语义搜索、图片/视频相似检索、推荐系统、异常检测、RAG 知识库问答 | 纯关键词精确匹配、数据量极小(< 几千条)、对解释性要求极高的场景 |
| BM25 Rescore | 需要二次精排的复杂搜索、短语优先匹配、结合业务字段(如热度)重新排序、多轮渐进式排序 | 数据量极小(不需要二次优化)、对实时性要求极高且无法接受额外延迟、排序逻辑在第一轮已完全满足 |
kNN 和 BM25 Rescore 经常组合使用:先用 BM25 做关键词召回、kNN 做语义召回(混合搜索),再用 Rescore 对结果做二次精排,也就是常见的“召回层 + 精排层”搜索架构。
使用
dense_vector
dense_vector 是 Elasticsearch 中专门用来存储向量数据的字段类型。你在索引里定义一个 dense_vector 字段,然后把 ML 模型生成的向量(一串浮点数)存进去,kNN 检索就靠它。
用法:
1 | PUT my_index |
dims 是向量维度(必须与模型输出一致),similarity 是相似度算法(cosine/dot_product/l2_norm)。
近似 kNN vs 精确 kNN
近似 kNN 用 HNSW 图索引“大概”找到最近的向量,速度快但不保证 100% 精确;精确 kNN 逐个比较所有向量,保证精确但数据量大时极慢。
近似 kNN:
1 | GET my_index/_search |
精确 kNN:
1 | GET my_index/_search |
精确 kNN 通过
script_score实现,必须扫描所有匹配文档。
BM25(默认相关性评分算法)
BM25 是 Elasticsearch 默认的“算分公式”。它根据三个因素给文档打分:这个词在文档里出现了几次(词频 TF)、这个词在整个索引里稀不稀有(逆文档频率 IDF)、文档的字段是长是短(字段长度归一化)。
用法:
1 | GET my_index/_search |
BM25 的参数可在索引设置中调整:
1 | PUT my_index |
k1 控制词频饱和度(默认 1.2),b 控制字段长度归一化强度(默认 0.75)。
Rescore(二次打分)
Rescore 是在第一轮搜索结果返回后,只对排名靠前的少量文档“再算一次分”,用更复杂的逻辑重新排序。
用法:
1 | GET my_index/_search |
window_size 控制参与重打分的文档数(默认 10),query_weight 和 rescore_query_weight 控制两轮分数的权重。
混合检索
完整的查询请求示例
1 | { |
注意:
query_vector里的...是占位符,实际生产环境中是一长串几百位的浮点数(由 NLP 模型将“如何配置Redis集群”这句话转化而来)。
为什么需要“混合检索”?
假设你在公司内部知识库搜索“如何配置Redis集群”:
- 纯关键词检索(BM25):它会老老实实找包含“配置”、“Redis”、“集群”这些词的文档。但如果有人写的是“Redis 集群搭建步骤”(没有“配置”二字),它可能就搜不到了。
- 纯向量检索(kNN):它能读懂语义,知道“搭建”和“配置”是一回事。但它可能把权限搞混,把别人私密的“Redis故障演练”文档也推给你。
混合检索 = 左手抓关键词精确匹配,右手抓语义泛化理解,脚下还踩着“权限过滤器”防止越权,最后再用“精排算法”把最好的结果顶到最前面。
模块一:"size": 10(返回结果数)
这是整个查询的“出口阀门”。无论中间过程捞出来 300 条还是 1000 条,最后返回给前端或用户的,只有 10 条。
模块二:knn(语义检索)
1 | "knn": { |
field:你在建索引时,一定有个字段叫vector,类型是dense_vector,里面存着每篇文档的向量值。query_vector:这是通过机器学习模型实时推理出来的向量。k:向量检索这一路,先猛捞 300 个语义最像的文档。num_candidates:为了性能,每个分片会先挑出 300 个“看起来像的”,再汇总挑出全局最优的 300 个。
模块三:query.bool(关键词硬匹配 + 权限安检)
这是整个查询里最复杂的部分,由 must(必须匹配,算分)和 filter(必须匹配,不算分)组成。
① must 里的 match:全文检索(传统搜索引擎老本行)
1 | { "match": { "textContent": "如何配置Redis集群" } } |
- 这个
match会把“如何配置Redis集群”分词(比如分成:如何、配置、Redis、集群)。 - 然后去
textContent字段里找包含这些词的文档,并计算一个 相关性评分(_score),词频越高、越稀有的词,得分越高。
② filter 里的 bool.should:权限门禁(OR 逻辑,必须满足其一)
1 | "filter": [ |
这里的逻辑翻译成人话就是:
只有满足以下条件之一的文档,才有资格进入候选池:
- 作者 ID 是 42(你自己写的);或者
- 是公开文档;或者
- 组织标签属于 “ENGINEERING / TECH_DEPT / COMPANY” 中的任意一个。
filter 里的条件不影响相关性评分,只做“硬过滤”。哪怕这篇文档关键词匹配度 100 分,只要不通过这里的安全检查,就会直接舍弃。
注意:query.bool.filter 只作用于 query 这一路,不会过滤顶层 knn 的命中结果。所以完整示例在 knn 里也加了一份同样的权限条件(knn.filter),否则私密文档仍可能从向量检索这一路进入结果。
模块四:rescore(二次精排)
前面的 knn + query 已经产生了一个初步排名,但 Elasticsearch 觉得“不够精细”,于是启动重打分机制。
1 | "rescore": { |
window_size: 300:大海捞针捞出来的几十万文档,我只关心当前得分最高的前 300 个,其他的不管了。rescore_query:用operator: "and"重新匹配一次。这意味着,只有在textContent里 同时包含“如何”、“配置”、“Redis”、“集群”这四个词 的文档,才会在这个环节获得较高的加分。- 权重计算:最终得分 =
0.2 * 老分数 + 1.0 * 新分数。rescore_query_weight设为 1.0(远大于 0.2),相当于用“必须全部包含关键词”这个硬标准,把最精准的文档强行提拔到最前面。
混合检索流程
- 并行双路召回:
- 一路走
knn(向量),捞出 300 条语义相似的。 - 一路走
query(关键词 + 过滤器),筛出所有符合关键词且通过权限验证的文档,并算出初步分数。
- 一路走
- 结果合并与粗排:
- 系统将两路结果按“或”的关系合并:同一篇文档如果两路都命中,它的分数是 kNN 分数与 query 分数之和;只命中一路的,就只有那一路的分数。然后按合并后的分数(
_score)排序,形成一个大的候选池。
- 系统将两路结果按“或”的关系合并:同一篇文档如果两路都命中,它的分数是 kNN 分数与 query 分数之和;只命中一路的,就只有那一路的分数。然后按合并后的分数(
- 截取前 300 名:
- 从候选池中只取出当前排名最靠前的 300 个文档(由
window_size决定)。
- 从候选池中只取出当前排名最靠前的 300 个文档(由
- 精排重算:
- 对这 300 个文档执行
rescore_query(要求四个词全匹配),重新计算最终得分并排序。
- 对这 300 个文档执行
- 输出最终结果:
- 从精排后的前 300 名里,截取前 10 条(
size: 10)返回给调用方。
- 从精排后的前 300 名里,截取前 10 条(
调优建议
k和window_size的关系:通常建议window_size≥knn.k,确保向量捞回来的 300 条都能进入重打分环节。注意k是全局的前 k 个,而window_size是每个分片各自取前 N 个再重打分,重打分作用在 kNN 与 query 合并后的结果上。顶层knn加rescore的写法同样需要 8.4 及以上版本。num_candidates要大于k:比如k=300,num_candidates可以设 500 或 1000,能提升召回率(但会增加性能开销)。query_weight与rescore_query_weight的配比:如果你觉得关键词精确匹配更重要,可以把rescore_query_weight调得比query_weight大很多(就像本例中的 1.0 vs 0.2)。filter里多用term:对于精确匹配(如 ID、状态、标签)要用term,不要用match,否则值会被分词器拆散,可能导致权限越界。
