概念介绍
function_score
查询可以在原有的查询结果算分的基础上,对每个文档计算一个新的算分,而计算的规则取决于应用的具体的funcion以及相关的一些选项。
看一个例子,
代码语言:javascript复制GET kibana_sample_data_ecommerce/_search?
{
"query": {
"function_score": {
"query": {
"match": {
"manufacturer": "Elitelligence"
}
},
"boost": "5",
"weight": 2,
"boost_mode": "multiply"
}
}
}
上面这个查询,它的执行过程是这样的,首先执行查询
代码语言:javascript复制GET kibana_sample_data_ecommerce/_search?
{
"query": {
"match": {
"manufacturer": "Elitelligence"
}
}
}
比如某个文档查询处理算分是1.6829565,function指定的函数是weight,ES默认会用这个值乘以当前的算分,然后因为boost的值是5,并且boost_mode
指定的计算方法是multiply
,所以最终的算分是:
1.6829565 * 2 * 5 = 16.829565
boost_mode : 决定 old_score 和 加强score 如何合併。score_mode : 决定functions裡面的加强score们怎麽合併,会先合併加强score们成一个总加强score,再使用总加强score去和old_score做合併,换言之就是会先执行score_mode,再执行boost_mode。
boost_mode除了multiply
还有一些其它的选项,比如max,sum等,具体可以看官方文档。而function除了weight
之外,还有script_score
, random_score
,field_value_factor
,decay functions
等,下个章节讲应用场景会有一些涉及到。
一些应用的场景
比如CSDN博客网站,在站内搜索博客时,产品经理可能希望除了基本的相关度之外,把博客点赞数较高的相对排名较高。这就是希望点赞数影响进本的搜索算分。就是一个function sorce典型的应用场景。
插入一些测试数据,
代码语言:javascript复制PUT /blogs/_doc/2
{
"content": "We like elasticsearch",
"votes": 2
}
GET blogs/_search
{
"query": {
"match": {
"content": "elasticsearch"
}
}
}
正常情况下,如果我们执行下面这个查询语句
代码语言:javascript复制GET blogs/_search
{
"query": {
"match": {
"content": "elasticsearch"
}
}
}
显然文档2的评分会更高,因为文档2更短,如果我们使用function score查询,
代码语言:javascript复制GET blogs/_search
{
"query": {
"function_score": {
"query": {
"match": {
"content": "elasticsearch"
}
},
"field_value_factor": {
"field": "likes",
"factor": 2,
"modifier": "sqrt",
"missing": 1
}
}
}
}
结果是文档1的评分更高了,原因在于field_value_factor
这个funcion会执行下面的规则计算一个得分,然后乘以原始的算分。
sqrt(2 * doc['likes'].value)
其中的modifier
还可以是其它的数学公式,比如log
,详细的可以参考官方文档。missing
的意思是说,如果某个文档的likes字段是空的,就默认它的值是1。
同样的例子,比如有一个商品索引,搜索时希望在相关度排序的基础上,销量(sales)更高的商品能排在靠前的位置,也是类似的方案实现。
再说一个应用场景,比如网站的广告,为了提高展示率,希望广告能随机展示出来,不然就总是展示某几个算分高的。但是可能又希望同一个用户看到的广告排名是一致的。这种场景,random_score
就排上用场了,我们可以使用用户id作为随机的seed。
查询语句类似如下:
代码语言:javascript复制
GET kibana_sample_data_ecommerce/_search?
{
"query": {
"function_score": {
"query": {
"match": {
"manufacturer": "优衣库"
}
},
"random_score": {
"seed": "userid"
}
}
}
}
源码层面的分析
源码版本基于ES7.10
function score的核心处理类是AFunctionScoreQuery
类,这个类的生成是在FunctionScoreQueryBuilder
,如下图所示:
FunctionScoreQueryBuilder.java
我们基于一个具体的查询示例来分析源码:
代码语言:javascript复制{
"query": {
"function_score": {
"query": {
"match": {
"content": "elasticsearch"
}
},
"field_value_factor": {
"field": "likes",
"factor": 2,
"modifier": "sqrt",
"missing": 1
}
}
}
}
基于我们上面的示例,这里调用第2个构造函数,因为我们这里只有一个function,boostMode没有指定,用的是默认的multiply。minScore是设置一个最低算分的阈值,就是结果不能低于这个,没有指定默认是null。maxBoost是最高算分的阈值,没有指定的话默认是FLT_MAX。
代码语言:javascript复制FLT_MAX的定义则是
#define FLT_MAX 3.402823466e 38F
Luncene的算分(score),Weight
类是Search过程中很重要的类,它负责生成Scorer,这是一个所有命中文档的迭代器,IndexSearch
类调用的createWeight最终会调用到实际的Query
(这里就是FunctionScoreQuery)中的方法,
FunctionScoreQuery.java
拿到Weight
类之后,上层通用的流程会创建Scorer
(本示例中是FunctionFactorScorer),然后遍历符合条件的文档计算算分(score方法)。
FunctionScoreQuery.java
上面中,factor是最终要和原始算分相乘(默认是相乘)的计算因子。这个因子是怎么算出来的呢?计算规则是调用了FieldValueFactorFunction
类的score方法。
FieldValueFactorFunction.java
看到这里,你应该已经看出来了,这里最终的算分就是
代码语言:javascript复制sqrt(2 * doc['likes'].value) * 原始算分