- 前段时间写多项式算分插件,发现 ES 的算分概念还是挺多的,主要有 Query, Weight, Scorer。本文简单介绍一下,不过还有一些细节看得也不清晰,如果有错,欢迎斧正
查询流程概览
-
首先看下 ES 的查询流程
- HTTP 收到请求,按 Shard 分发到 Data
- Data 按 shard 查询结束后,发往 http merge, 然后再发往 Data fetch
- 算分便是发生在 lucene:search (org.apache.lucene.search.IndexSearcher#search) 中
算分过程
-
算分整体分为四步
- 从 Query 依次按层构建 Weight
- 从 Weight 构建 BulkScorer
- 从 BulkScorer 构建 Scorer
- Collector 收集时用 Scorer 按文档打分,得到分数
从 Query Builder 到 Query
- createWeight 则是构造用于查询的 Weight,在其中可以指定要不要算分,当不要算分时,有些 query 会进行改写,比如 bool query 会将 must 移入 filter。另外也只有不要算分时,weight 才会进缓存。
Weight 是什么
The purpose of Weight is to ensure searching does not modify a Query, so that a Query instance can be reused.
- 可见,Weight 最大的作用就是保存和 IndexSearcher 相关的状态,类似 Query 级的上下文,来保证 Query 的复用。 (不过没看出有对 Query 的复用?)
-
Weight 主要有4个核心方法,1个辅助方法
-
scorer 打分器
- 传入一个 LeafReaderContext ,返回一个"打分器", 为什么加引号,详见下文介绍
-
bulkScorer
- 批量打分器,search 调用的入口方法,方便在批量场景下做优化,比如二阶段查询加速,倒排链合并,大多数场景采用
DefaultBulkScorer
-
scorerSupplier
- 可以在不够造 scorer 之前先判断一下 cost。不过默认的实现是先创建了个 scorer 然后取了其迭代器的 cost
-
extractTerms
- 获得 query 中的 term,term query a:b, 则会获得b . dfs 和高亮时会用到,如未实现,则无法高亮。如 terms 当 term 数大于16时,则会走 TermInSetQuery 的 weight, 其未实现此方法,因此无法高亮
-
explain
-
matches
- 判断某个 doc 有没有命中,如果有二阶段,则先用二阶段粗略判断一次。返回一个
MatchesIterator
仅测试中用到
不单纯的 Scorer
总结
- 所以,总结一下,要实现一个算分需要怎么样呢?首先 QueryBuilder 转换时要用 toQuery, 然后 needsScores 为 true, 然后 bulkScorer 中给 collector 设置了 scorer, 最后在 Collector 中调用 score 方可完成算分。
参考资料