这是《Elasticsearch 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。
关键词搜索擅长精确匹配词项,向量搜索擅长理解语义相似。随着语义检索、推荐召回和 RAG 系统普及,dense_vector 与 kNN 已经成为 Elasticsearch 的重要能力。
本章讲解向量字段、kNN 查询、混合搜索、过滤向量查询、召回与重排,以及向量索引的容量与性能边界。
14.1 为什么需要向量搜索
用户输入:
适合出差的轻便电脑
关键词搜索依赖分词词项是否命中,而语义搜索可以把文本映射成向量:
适合出差的轻便电脑 -> [0.12, -0.34, 0.56, ...]
轻薄笔记本 -> [0.10, -0.30, 0.52, ...]
向量距离越近,语义越相似。它适合:
- 同义表达召回;
- 图片相似度;
- 音频相似度;
- 推荐召回;
- 知识库问答;
- 语义去重。
向量搜索不能替代所有关键词搜索,例如订单号、错误码、品牌型号仍应使用关键词精确匹配。
14.2 dense_vector 字段
Mapping:
PUT /semantic-docs
{
"mappings": {
"properties": {
"title": { "type": "text" },
"content": { "type": "text" },
"content_vector": {
"type": "dense_vector",
"dims": 768,
"index": true,
"similarity": "cosine"
}
}
}
}
参数说明:
| 参数 | 说明 |
|---|---|
dims |
向量维度 |
index |
是否构建向量索引 |
similarity |
相似度度量 |
常见维度:
| 模型类型 | 维度示例 |
|---|---|
| 小型语义模型 | 256 / 384 |
| 中型通用模型 | 768 / 1024 |
| 大型模型 | 1536 / 3072 |
维度越高表达能力通常越强,但内存和计算成本也越高。
14.3 相似度度量
| similarity | 含义 | 适用 |
|---|---|---|
cosine |
夹角相似度 | 文本语义、归一化向量 |
dot_product |
内积 | 已归一化向量,通常性能好 |
l2_norm |
欧氏距离 | 空间坐标、某些图像特征 |
文本嵌入常使用 cosine。如果模型输出已归一化,使用 dot_product 通常更快,但必须确认向量确实归一化。
14.4 写入向量文档
PUT /semantic-docs/_doc/1
{
"title": "轻薄笔记本",
"content": "适合出差使用,重量轻,续航长。",
"content_vector": [0.12, -0.34, 0.56]
}
实际 768 维向量会很长,不建议在业务数据库中直接保存完整 JSON 明文。同步服务应在内存中调用 Embedding 模型,然后写入 ES。
14.5 kNN 搜索
GET /semantic-docs/_search
{
"knn": {
"field": "content_vector",
"query_vector": [0.11, -0.32, 0.55],
"k": 10,
"num_candidates": 100
}
}
参数说明:
| 参数 | 说明 |
|---|---|
field |
向量字段 |
query_vector |
查询向量 |
k |
最终返回近邻数 |
num_candidates |
每个分片候选数量 |
filter |
过滤条件 |
num_candidates 越大,召回越准确,但查询越慢。常见从 k * 5 或 k * 10 开始压测。
14.6 带过滤条件的 kNN
GET /semantic-docs/_search
{
"knn": {
"field": "content_vector",
"query_vector": [0.11, -0.32, 0.55],
"k": 10,
"num_candidates": 200,
"filter": {
"bool": {
"filter": [
{ "term": { "status": "PUBLISHED" } },
{ "range": { "publish_at": { "lte": "now" } } }
]
}
}
}
}
过滤条件在候选检索时生效,不是先取 Top K 再过滤。这样可以避免某些过滤条件下结果不足。
14.7 混合搜索
混合搜索同时使用关键词和向量:
GET /products/_search
{
"query": {
"multi_match": {
"query": "轻薄笔记本",
"fields": ["title^3", "brand", "description"],
"type": "best_fields"
}
},
"knn": {
"field": "title_vector",
"query_vector": [0.11, -0.32, 0.55],
"k": 50,
"num_candidates": 300
},
"rank": {
"rrf": {
"rank_window_size": 100
}
},
"size": 20
}
RRF
Reciprocal Rank Fusion 按名次融合,不直接比较原始分数:
score = sum(1 / (rank_constant + rank_i))
优点:
- 不需要归一化 BM25 和向量分数;
- 对不同分值分布稳健;
- 实现简单;
- 常作为默认混合策略。
线性加权
final_score = text_weight * text_score + vector_weight * vector_score
优点是可解释、可控;缺点是 BM25 与向量分数分布不同,需要调参。
14.8 Rerank
粗召回后,可以使用更重模型重排:
关键词召回 100 条
+ 向量召回 100 条
-> 去重合并
-> Rerank 模型打分
-> 返回 Top 20
常见 Rerank 输入:
query: 适合出差的轻便电脑
document: 轻薄笔记本,重量 1.2kg,续航 18 小时
模型输出相关分数。应用层根据分数重新排序。
适合:
- 搜索质量要求高;
- 召回集较小;
- 允许额外延迟;
- 有标注或点击数据;
- 可以承担模型成本。
14.9 多路召回架构
flowchart LR
Query[用户查询] --> Analyzer[查询理解]
Analyzer --> Keyword[关键词召回]
Analyzer --> Vector[向量召回]
Analyzer --> Rule[规则/运营召回]
Keyword --> Merge[合并去重]
Vector --> Merge
Rule --> Merge
Merge --> Rerank[重排]
Rerank --> Result[搜索结果]
查询理解可以包括:
- 分词;
- 改写;
- 同义词扩展;
- 类目预测;
- 语言识别;
- 意图识别;
- 向量化。
14.10 向量生成链路
原始文本 -> 清洗 -> 截断/分块 -> Embedding 模型 -> 向量 -> Elasticsearch
注意事项:
- 写入和查询必须使用同一模型或兼容模型;
- 模型升级需要重建向量字段;
- 长文本应分块,每块独立向量;
- 保留 chunk 元数据,例如文档 ID、位置、标题;
- 控制 Embedding 调用成本;
- 向量生成失败要有重试和死信;
- 批量生成比单条调用更高效。
文档分块示例:
{
"doc_id": "kb-1001",
"chunk_id": "kb-1001-0003",
"chunk_index": 3,
"title": "退款政策",
"content": "未发货订单可在 30 分钟内取消。",
"content_vector": [0.1, 0.2, 0.3],
"source": "help-center"
}
14.11 RAG 检索
RAG 常用流程:
用户问题 -> 查询改写 -> 混合检索 -> 重排 -> 组装上下文 -> LLM 生成答案
检索建议:
- 同时使用关键词和语义召回;
- 限制上下文长度;
- 返回来源文档和位置;
- 权限过滤必须在检索层完成;
- 对无结果和低置信结果兜底;
- 记录用户反馈;
- 不要把私有数据直接暴露给无权限用户。
14.12 性能与容量
向量检索的主要成本:
- 内存:HNSW 图和向量数据;
- CPU:距离计算;
- 磁盘:向量字段存储;
- 网络:多分片候选归并;
- 构建成本:向量索引写入更慢。
优化方式:
| 优化 | 说明 |
|---|---|
| 降低维度 | 选择合适模型,不盲目追求高维 |
| 使用量化 | 依据版本支持选择压缩能力 |
| 减少 num_candidates | 降低单次候选数 |
| 缩小 filter 范围 | 时间、租户、类目过滤 |
| 分索引 | 按业务或时间拆分 |
| 冷热分层 | 冷向量少查或不留内存 |
| 预过滤 | 避免无权限候选 |
| 监控 P99 | 向量查询延迟单独观测 |
14.13 关键词与向量选型
| 查询 | 建议 |
|---|---|
| 订单号、错误码 | keyword |
| 品牌型号 | keyword + text |
| 中文自然语言 | text + IK |
| 同义表达 | dense_vector |
| 图片相似 | image embedding + kNN |
| 推荐召回 | user/item 向量 |
| 知识问答 | 混合搜索 + Rerank |
| 强权限文档 | 必须加 filter |
14.14 版本边界
不同版本对向量检索能力差异较大:
- 早期版本支持 script score,但性能有限;
- 8.x 引入近似 kNN 与 HNSW 索引;
- 后续版本增强过滤、量化、多向量等能力;
- 9.x 在性能和语义搜索生态上继续演进;
- 兼容发行版能力可能不同。
使用前必须确认当前版本的 Mapping 语法、最大维度、相似度类型和 kNN 参数。
14.15 本章小结
- dense_vector 用于存储向量,kNN 用于近邻检索;
- 文本语义常用 cosine,已归一化向量可用 dot_product;
- kNN 的
k和num_candidates是效果与性能的关键权衡; - 混合搜索通常比单一关键词或单一向量更稳;
- RRF 按名次融合,适合作为默认策略;
- 复杂搜索采用多路召回加重排;
- 向量检索内存、CPU 和构建成本显著,必须容量规划和压测。
14.16 思考题
- 什么场景必须使用关键词搜索而不是向量搜索?
num_candidates调大会带来什么影响?- 为什么 RRF 比直接相加 BM25 分和向量分更稳?
- 模型升级后为什么需要重建向量字段?
- RAG 系统中权限过滤为什么必须发生在 ES 查询中?