KnowFlow ② 八股文:Elasticsearch 混合检索(KNN + BM25 + RRF)
KnowFlow ② 八股文:Elasticsearch 混合检索
面向字节跳动 Java 后端暑期实习面试,从项目实现细节出发,每道题都结合代码讲清楚。
一、Elasticsearch 基础(5 道)
Q1:Elasticsearch 是什么?和 MySQL 有什么区别?
答:
Elasticsearch(简称 ES)是一个分布式全文搜索引擎,底层用 Lucene,擅长模糊搜索、全文检索、向量检索。
| 对比项 | MySQL(关系型数据库) | Elasticsearch(搜索引擎) |
|---|---|---|
| 擅长 | 精确查询、事务、JOIN | 模糊搜索、全文检索、向量检索 |
| 索引结构 | B+ 树(范围查询快) | 倒排索引(关键词查询快) |
| 模糊搜索 | LIKE '%关键词%' 很慢(全表扫描) |
天生支持,毫秒级 |
| 向量检索 | 不支持 | 原生支持 KNN(K 近邻) |
| 事务 | 支持 ACID | 不支持(最终一致性) |
项目中为什么用 ES?
- 用户搜”怎么配置 Redis 线程池”,关键词可能在文档的任何位置
- MySQL 的
LIKE很慢,且找不到语义相似的文档 - ES 的倒排索引 + BM25 算法,毫秒级返回结果
- ES 的 KNN 向量检索,能找到”意思相近但用词不同”的文档
Q2:倒排索引(Inverted Index)是什么?为什么搜索快?
答:
正排索引(MySQL 的 B+ 树):
1 | |
搜”Redis 线程池”,要逐个文档扫描,很慢。
倒排索引(ES 的核心):
1 | |
搜”Redis 线程池”,先找到”Redis”对应的文档 [1,2,3],再找到”线程池”对应的文档 [1],取交集 → 文档 1。
为什么快?
- 关键词 → 文档列表,是哈希查找,O(1)
- 多个关键词取交集/并集,用位图(BitSet) 操作,极快
面试加分:倒排索引包含两个结构:
- Term Dictionary(关键词字典):存储所有关键词,用 FST(有限状态转换器)压缩存储
- Posting List(倒排列表):每个关键词对应的文档 ID 列表,用 Roaring Bitmap 压缩存储
Q3:BM25 算法是什么?和 TF-IDF 有什么区别?
答:
BM25 是 ES 默认的相关性评分算法,用来计算一个关键词在一个文档中有多重要。
TF-IDF 的问题:
1 | |
BM25 的改进:
1 | |
项目中的使用(HybridSearchService.java):
1 | |
面试回答话术:
“BM25 是 ES 默认的相关性算法,解决了 TF-IDF 的两个问题:一是 TF 不会无限增长(饱和函数),二是考虑了文档长度归一化。我们项目中用 BM25 做关键词匹配,配合 KNN 向量检索,再用 RRF 融合两份结果。”
Q4:ES 的 Mapping 是什么?项目中怎么设计的?
答:
Mapping 是 ES 的表结构定义,类似 MySQL 的 CREATE TABLE。
项目中的 ES Mapping(EsDocument.java 实体类 + EsIndexInitializer.java):
1 | |
关键设计点:
textContent用ik_max_word分词:中文要分词,”技术派项目” → [“技术”, “派”, “项目”],不然搜”技术”找不到vector用DenseVector类型:专门存向量,支持 KNN 检索fileMd5、userId用Keyword类型:精确匹配,用于权限过滤
面试加分:为什么 textContent 的索引分词器和查询分词器不同?
ik_max_word:索引时最细粒度分词,尽量多分出词,提高召回率ik_smart:查询时最粗粒度分词,分出最合理的词,提高准确率
Q5:ES 的 KNN 向量检索是什么?和 BM25 有什么区别?
答:
BM25(关键词匹配):
- 基于词频统计,找包含查询词最多的文档
- 问题:语义理解为零。搜”怎么配置 Redis”,找不到”Redis 线程池设置方法”(用词不同但意思一样)
KNN(K 近邻向量检索):
- 把文档和查询都转成向量(2048 维浮点数)
- 计算向量距离(余弦相似度或欧氏距离),距离越近越相关
- 优势:语义理解。上面两个查询的向量很接近,能找到
项目中的 KNN 使用(HybridSearchService.java 第 91~100 行):
1 | |
面试加分:KNN 有两种实现:
- 暴力搜索(Brute Force):算查询向量和所有文档向量的距离,O(N),准确但慢
- HNSW(Hierarchical Navigable Small World):用图结构加速,O(log N),ES 8.x 默认用这个
二、混合检索架构(6 道)
Q6:什么是混合检索?为什么只用向量检索不够?
答:
只用向量检索的问题:
1 | |
混合检索 = 向量检索 + 关键词检索,两份结果融合:
1 | |
项目中的混合检索(HybridSearchService.java):
1 | |
Q7:RRF(倒数秩融合)算法是什么?公式是什么?
答:
RRF(Reciprocal Rank Fusion)是一种多路召回结果融合的算法。
核心思想:一个文档在多个召回列表中排名越靠前,最终得分越高。
公式:
1 | |
举例:
1 | |
为什么比单纯”得分相加”好?
- 防止某个检索方式刷分(比如向量得分普遍很高)
- 排名比得分更稳定(不同检索方式的得分尺度不同)
Q8:项目中 KNN 召回窗口为什么设为 topK * 30?
答:
原因:KNN 是粗排,BM25 + RRF 是精排。如果 KNN 只召回 topK 个,可能漏掉 BM25 认为很重要但向量距离稍远的文档。
1 | |
如果不放大召回窗口会怎样?
- KNN 只召回 10 个 → BM25 只能在 10 个里挑 → 可能漏掉好文档
- 放大到 300 个 → BM25 有更多选择 → 最终结果更准
代价:召回 300 个比 10 个慢。项目中 2048 维向量,HNSW 索引,300 个仍然在 50ms 内完成,可以接受。
Q9:混合检索的权限过滤是怎么实现的?
答:
企业知识库有多租户权限隔离:用户只能搜到自己有权限的文档。
权限规则:
- 用户自己上传的文档 → 能搜到
- 标记为”公开”的文档 → 能搜到
- 同组织的文档 → 能搜到(通过
orgTag判断) - 其他用户的私有文档 → 搜不到
项目中的实现(HybridSearchService.java 第 102~123 行):
1 | |
面试加分:为什么权限过滤要放在 filter 里,不放在 must 里?
filter不计算相关性得分,只做是/否过滤,速度快filter的结果会被 ES 缓存(Filter Cache),相同权限的查询直接命中缓存
Q10:如果向量化失败了,项目有什么兜底方案?
答:
向量化(调用 Embedding API)可能失败的原因:
- LLM 服务超时
- 网络抖动
- API 配额用完
项目中的兜底方案(HybridSearchService.java 第 84~87 行):
1 | |
textOnlySearchWithPermission 方法(只用 BM25,不用向量):
1 | |
面试回答话术:
“向量化失败不影响核心功能。我们会 fallback 到纯文本检索(BM25),虽然语义理解能力弱一些,但关键词匹配仍然能返回相关文档。这在生产环境是很重要的降级策略。”
Q11:ES 的 min_score 是什么?为什么要用?
答:
min_score 是 ES 的最低相关性得分阈值,得分低于这个阈值的文档直接被过滤掉,不会返回。
为什么需要?
1 | |
项目中的使用(第 243 行):
1 | |
BM25 的得分范围:
- 完全不相关:~0
- 有些相关:0.1 ~ 0.5
- 高度相关:0.5 ~ 数
面试加分:min_score 和 filter 的区别?
filter:是/否过滤,不影响得分min_score:得分阈值过滤,基于 BM25 或 RRF 的最终得分
三、Embedding 与向量化(4 道)
Q12:Embedding 是什么?怎么把文字变成向量?
答:
Embedding(嵌入)是把文字映射到高维向量空间的操作。
1 | |
项目中调用的是谁的 Embedding API?
- 代码中是
EmbeddingClient.java,封装了对 LLM 的调用 - 可能是 DeepSeek、通义千问、或开源 Embedding 模型
- 输出是 2048 维浮点数向量
为什么是 2048 维?
- 维度越高,语义表达越精细,但存储和计算开销越大
- 2048 维是效果和成本的平衡点
- ES 的
DenseVector类型支持最多 2048 维(ES 8.x 支持到 4096 维)
Q13:项目中 Embedding 的维度为什么选 2048?可以调吗?
答:
可以调,但要考虑:
| 维度 | 语义表达 | 存储开销(每个向量) | 检索速度 |
|---|---|---|---|
| 384 维 | 一般 | 1.5 KB | 很快 |
| 768 维 | 好 | 3 KB | 快 |
| 1536 维 | 很好 | 6 KB | 中等 |
| 2048 维 | 很好 | 8 KB | 中等偏慢 |
| 4096 维 | 极好 | 16 KB | 慢 |
项目中的设定(EsDocument.java):
1 | |
如果要调维度,要做的事:
- 重新调用 Embedding API(不同维度是不同模型)
- 重新生成所有文档的向量(全量更新)
- 修改 ES Mapping(
dims = 新维度) - 重建索引(Reindex)
面试回答话术:
“我们选 2048 维是因为效果和成本的平衡点。如果追求极致性能,可以降到 768 维,存储和检索速度都能提升 2 倍,但语义表达能力会有所下降。这需要在实际业务场景中做 AB 测试来决定。”
Q14:Embedding API 调用失败怎么办?有重试吗?
答:
项目中的容错(EmbeddingClient.java 应该有,但原代码没完全展示):
典型的实现是:
1 | |
面试加分:为什么不用 Spring Retry 或 Resilience4j?
- 可以用,但 Embedding API 的失败通常是限流(Rate Limit)或超时
- 需要**指数退避(Exponential Backoff)**重试,这些框架都支持
Q15:向量检索和关键词检索,哪个更适合中文搜索?
答:
中文搜索的痛点:
- 中文不分词,”技术派项目教程”是一整串
- 要搜到,要么精确匹配每个字(BM25 + IK 分词)
- 要么语义匹配(向量检索)
项目中用 IK 分词器:
1 | |
IK 分词效果:
1 | |
向量检索对中文的优势:
- “Redis 线程池” 能匹配 “Redis 连接池配置”
- 但可能对专有名词理解不准确(”技术派” 可能被拆成 “技术” + “派”)
面试回答话术:
“中文搜索我们两者结合用。BM25 负责关键词精确匹配,IK 分词器保证专业术语(如”技术派”)被正确切分;向量检索负责语义相似度匹配。两者通过 RRF 融合,既保证准确率,又提高召回率。”
四、综合设计题(5 道)
Q16:如果 ES 索引里有 1 亿条数据,检索会变慢吗?怎么优化?
答:
会变慢,但 ES 是分布式的,可以优化。
优化手段:
1. 增加 Primary Shard 数量
1 | |
- 1 亿条数据,12 个分片,每个分片约 800 万条
- 查询时,12 个分片并行搜索,结果汇总
2. 用 HNSW 索引加速向量检索
1 | |
3. 热数据预热(Warm Up)
- ES 启动后,索引数据在磁盘,第一次查询要加载到内存(慢)
- 配置
index.store.preload: ["nvd", "dvd"]让热数据常驻内存
Q17:如果知识库文档量涨到 1000 万,你的系统还能撑住吗?
答:
V1(当前项目):单 ES 实例 + 单 MinIO 实例
- 支持:100 万条向量,1000 个文档
V2(水平扩展):
- ES 集群:3 个节点,12 个主分片,每个分片 1 个副本
- MinIO 集群:纠删码 N=4, M=2
- 应用无状态化:3 个实例,Nginx 负载均衡
V3(性能优化):
- 向量量化(Quantization):把 2048 维 float 量化成 int8,存储和检索速度提升 4 倍
- 分层检索:热数据(最近 30 天上传的)放内存;冷数据放磁盘
- 预取(Prefetching):用户输入查询时,前端先发一个前缀请求,后端提前检索,等用户点”搜索”时直接返回
Q18:ES 和 Milvus / Chroma 等专业向量数据库比,有什么优劣?
答:
| Elasticsearch | Milvus / Chroma(专业向量 DB) | |
|---|---|---|
| 优势 | 同时支持 BM25 和 KNN,混合检索原生支持 | 向量检索性能极致(专门优化过) |
| 劣势 | 向量检索性能不如专业向量 DB | 关键词检索能力弱,混合检索要自己实现 |
| 适用场景 | 知识库、搜索引擎(需要混合检索) | 纯向量检索(推荐系统、图像检索) |
| 运维 | 成熟,生态好 | 相对新颖,踩坑多 |
面试回答话术:
“我们选 ES 是因为需要做混合检索(BM25 + KNN),ES 原生支持这两者,RRF 融合也很方便。如果是纯向量检索场景(比如以图搜图),我会选 Milvus,它的向量检索性能比 ES 好很多。但知识库场景,ES 更合适。”
Q19:如果用户输入一段很长的查询(比如 500 字),你怎么处理?
答:
问题:查询太长,Embedding API 有 token 限制(通常 8192 token),可能超限。
项目中的处理(HybridSearchService.java):
1 | |
更好的方案:
- 查询压缩:用 LLM 把长查询压缩成核心关键词,再做检索
- 分段检索:把长查询分成多段,分别检索,结果 RRF 融合
- HyDE(Hypothetical Document Embedding):让 LLM 先生成一段”假设答案”,再用这段答案的向量去检索(效果很好!)
Q20:如果让你从零设计知识库系统的检索模块,你会怎么做?
答:
MVP(最小可行产品):
- 文档上传 → 解析 → 切块(500 字/块,重叠 50 字)
- Embedding → 存入 ES(vector 字段)
- 用户查询 → Embedding → KNN 检索 → 返回 Top 5
V2(加入混合检索):
- 加入 BM25 关键词检索
- RRF 融合两路结果
- 权限过滤(只能搜到自己有权限的)
V3(生产级):
- 查询改写(LLM 扩写查询,提高召回率)
- 重排序模型(Cross-Encoder,对 Top 50 结果精排)
- 检索结果缓存(Redis Cache,相同查询 5 分钟内直接返回)
- 降级策略(Embedding 失败时 fallback 到纯文本检索)
五、简历话术准备
面试官问:”你在简历里写了混合检索架构,能详细讲一下吗?”
回答模板(背下来!):
“这个问题我从四个方面来讲。
第一,为什么需要混合检索。纯向量检索语义理解好,但可能返回答非所问的文档;纯关键词检索准确率高,但语义理解为零。我们把两者结合,既保证准确率,又提高召回率。
第二,具体怎么实现的。用 ES 的 KNN 做向量召回(粗排),用 BM25 做关键词匹配,再用 RRF(倒数秩融合)算法对两路结果重排序(精排)。KNN 召回窗口设为最终返回数量的 30 倍,避免漏掉好文档。
第三,权限过滤怎么做的。用户只能搜到自己上传的、公开的、或同组织的文档。把这个过滤条件放在 ES 的
filter子句里,不计算得分,还能利用 Filter Cache 加速。第四,降级策略。如果 Embedding API 调用失败,系统会自动 fallback 到纯文本检索(BM25),不影响核心功能。这在生产环境是很重要的容错机制。”
© 2026 KnowFlow 面试手册 · 转载请注明出处