怎样基于 Elasticsearch 实现排序沉底或前置

打印 上一主题 下一主题

主题 762|帖子 762|积分 2286

在搜索场景的应用中,存在希望根据某个或某些字段来调解排序评分,从而实现排序沉底或置顶效果的利用需求。以商机管理中的扫街场景为例,当我们在扫街场景中须要寻找一个商户时,希望这个商户离的近、GMV 潜力大、被他人跟进过的次数越少越好。在策略上离的近在排序中的权紧张比 GMV 潜力大更大,因为我们希望就近拜访,在间隔差不多的情况下优先拜访能带来更多 GMV 的商家。


  • 如果这个商家是一个“激励商家”且高转化意愿,大概须要对这样的商家在排序上做置顶;
  • 如果这个商家已经被其他人重复开辟过了,那这样的商户开辟成本就会比力大,被他人跟进的次数越多,排序应该更靠后,所以就须要实现沉底的效果。
下文将先容 ES 所能够支持的丰富多样的排序本领,以及差别排序本领所详细实用的场景。
提升查询权重 - Boost

在查询时利用 Boost 参数能让一个查询语句比其他语句更紧张。所以在相对简单的场景下,用这种方式可以实现低改动成本、快速变动。在这里须要留意的是任何范例的查询都能担当 Boost 参数,Boost 只是对 _score 的一个影响因子,将 Boost 参数设置为2,并不代表 _score 是原来的 2 倍。当没有表现设置 Boost 时,默认值为 1。
  1. GET /_search
  2. {
  3.     "query":{
  4.         "bool":{
  5.             "should":[
  6.                 {
  7.                     "match":{
  8.                         "title":{
  9.                             "query":"elastic search",
  10.                             "boost": 2
  11.                         }
  12.                     }
  13.                 },
  14.                 {
  15.                     "match":{ 2
  16.                         "content":"elastic search"
  17.                     }
  18.                 }
  19.             ]
  20.         }
  21.     }
  22. }
复制代码
修改查询相关度 - 组合查询

查询场景:要查询文档中包含 Elasticsearch or (Golang or Go) or function_score 的文档。
  1. GET /_search
  2. {
  3.     "query":{
  4.         "bool":{
  5.             "should":[
  6.                 {"term":{"content":"elasticsearch"}},
  7.                 {"term":{"content":"Golang"}},
  8.                 {"term":{"content":"Go"}},
  9.                 {"term":{"content":"function_score"}}
  10.             ]
  11.         }
  12.     }
  13. }
复制代码
虽然上述查询能把只要包含了 or 条件中的所有文档都查询出来,而且每个条件的紧张程度都一样。但实际 Golang or Go 是一个组合条件,和其他 2 个条件是并列的关系,所以下面是更好的写法。
用 Bool 查询把 Golang or Go 包起来,这样如今的 Golang or Go 跟其他两个条件就处于顶层相互竞争的关系了。原来每个词的紧张性是 25%,如今 Elasticsearch 和 function_score 紧张性各占 33.3%, Golang or Go 加起来占 33.3%。
  1. GET /_search
  2. {
  3.     "query":{
  4.         "bool":{
  5.             "should":[
  6.                 {   "term":{"content":"elasticsearch"}},
  7.                 {   "term":{"content":"function_score"}},
  8.                 {
  9.                     "bool":{
  10.                         "should":[
  11.                             {"term":{"content":"Golang"}},
  12.                             {"term":{"content":"Go"}}
  13.                         ]
  14.                     }
  15.                 }
  16.             ]
  17.         }
  18.     }
  19. }
复制代码
排序沉底 - Boosting 查询

查询场景:文档中要包含 Elasticsearch,但不希望包含 MySQL。


  • 方案一:利用 must_not
在这个方案里利用的 must_not 过于严格,会把包含了 MySQL 的文档排除出去。但如果一个文档中包含了许多 Elasticsearch,只是恰好包含了一个 MySQL,那就错过了一个“好”的文档。
  1. GET /_search
  2. {
  3.     "query":{
  4.         "bool":{
  5.             "must":{
  6.                 "match":{
  7.                     "content":"elasticsearch"
  8.                 }
  9.             },
  10.             "must_not":{
  11.                 "match":{
  12.                     "content":"mysql"
  13.                 }
  14.             }
  15.         }
  16.     }
  17. }
复制代码


  • 方案二:用 Boosting 查询
通过正向查询确定希望文档匹配哪些内容;在负向查询中,文档如果匹配了这些内容,那么对 _score 须要减分。减分的权重由 negative_boost 来控制,是一个 0-1 的数值。最终的评分 new_score = 正向查询的score * negative_boost。
  1. GET /_search
  2. {
  3.     "query":{
  4.         "boosting":{
  5.             "positive":{
  6.                 "match":{
  7.                     "content":"elasticsearch"
  8.                 }
  9.             },
  10.             "negative":{
  11.                 "match":{
  12.                     "content":"mysql"
  13.                 }
  14.             },
  15.             "negative_boost":0.8
  16.         }
  17.     }
  18. }
复制代码
多查询评分同等 - constant_score 查询

某些场景下,我们大概并不关心 TF/IDF,只关心这个词是否在文档中出现过,好比出差找一个酒店,希望尽大概有如下设施:WIFI、Gym(健身房)、Breakfast(早餐)。


  • 方案一:match 查询
这种查询,实际还是用 TF/IDF,会整体去思量这些词在酒店描述中出现的是否频仍。但实际用户大概并不关心出现是否频仍,只关心这些设施是否尽大概包含了,且包含得越多越好。
  1. GET /_search
  2. {
  3.     "query":{
  4.         "match":{
  5.             "hotel_desc":"WIFI Gym Breadfast"
  6.         }
  7.     }
  8. }
复制代码


  • 方案二:constant_score 查询
通过 constant_score 查询,酒店描述中匹配了一个设施评分就举行 +1,设施匹配的越多评分就越高。但不会因为在酒店描述中多次提到 WIFI 评分就提升,因为每个设施的评分最多也只有 1 分。但大概每个设施的紧张程度不一样,有些设施更有价值,那么我们就可以为差别的设施增加差别的权重。而给每个权重设置查询权重就是最前面讲的 Boost 参数。
  1. GET /_search
  2. {
  3.     "query":{
  4.         "bool":{
  5.             "should":[
  6.                 {
  7.                     "constant_score":{
  8.                         "query":{
  9.                             "match":{
  10.                                 "hotel_desc":"WIFI"
  11.                             }
  12.                         }
  13.                     }
  14.                 },
  15.                 {
  16.                     "constant_score":{
  17.                         "query":{
  18.                             "match":{
  19.                                 "hotel_desc":"Gym"
  20.                             }
  21.                         },
  22.                         "boost":2
  23.                     }
  24.                 },
  25.                 {
  26.                     "constant_score":{
  27.                         "query":{
  28.                             "match":{
  29.                                 "hotel_desc":"Breakfast"
  30.                             }
  31.                         }
  32.                     }
  33.                 }
  34.             ]
  35.         }
  36.     }
  37. }
复制代码
constant_score 查询的更多应用

上文 Boosting Query 中提到 negative_boost 可以降低原来的评分,但如果 Positive 正向查询中用的是 Filter 查询,在根本就没有评分的情况下,可以把 Filter 查询改为 Term 或 Match 查询,但是对于只要过滤不须要评分的查询,好比过滤代价的查询,应该尽大概用 Filter 查询,可利用缓存、跳过评分,实行速率更快。这个时候,也可以用 constant_score 把 Filter 查询包含进去。
  1. GET /_search
  2. {
  3.     "query":{
  4.         "boosting":{
  5.             "positive":{
  6.                "constant_score":{
  7.                     "filter":{
  8.                         "term":{
  9.                             "price":50
  10.                         }
  11.                     }
  12.             }
  13.             },
  14.             "negative":{
  15.                   "match":{
  16.                     "content":"mysql"
  17.                 }
  18.             },
  19.             "negative_boost":0.8
  20.         }
  21.     }
  22. }
复制代码
控制评分的终极武器 - function_score 查询

function_score 查询最实用的场景:一个正向影响因子,一个负向影响因子,好比跟 votes(投票数)和 date(日期)结合起来,投票数越高的越排前面,日期越早的越排后面。
根本概念


  • weight:为每个文档应用一个简单而不被规范化的权重提升值:当 weight 为 2 时,最闭幕果为 2 * _score。
  • field_value_factor:利用这个值来修改_score,如将 votes 热度作为思量因素。
  • random_score:为每个用户都利用一个差别的随机评分对结果排序,但对某一详细用户来说,看到的序次始终是同等的。
  • 衰减函数 - linear、exp、gauss:将浮动值结合到评分_score中,例如结果文档的最新发布时间,结合经纬度的间隔,给文档一个综合评分。
  • script_score:如果需求超出以上函数能够支持的范围,可以自界说脚本来控制评分。
按热度提升权重

查询场景:POI 查询,希望将 POI 在抖音的搜索热度高的排在前面,同时根据 POI 名字全文搜索时,作为主要的排序依据。
用 function_score 包含主查询和函数,fields_value_factor 函数会被应用到每个被主查询查出来的文档上,每个文档的 douyin_hot 字段必须有值供函数盘算,如果没有必须利用 missing 属性来提供默认值。
如此设置后,每个文档的最终评分 _score 修改为:new_score = old_score * number_of douyin_hot,这个结果其实是不符合预期的,因为抖音热度的值一样寻常很大,会完全覆盖掉原来的评分。
  1. GET /_search
  2. {
  3.     "query":{
  4.         "function_score":{
  5.             "query":{
  6.                 "match": {
  7.                     "query":"烧烤",
  8.                     "fields":"poi_name"
  9.                 }
  10.             },
  11.             "field_value_factor":{
  12.                 "field":"douyin_hot",
  13.                 "missing": 0
  14.             }
  15.         }
  16.     }
  17. }
复制代码
  1. [/code] [size=2]Modifier 函数[/size]
  2. 此中一种优化方式是利用 Modifier 来平滑 douyin_hot 的值,好比热度 0 和热度 1 的差别应该比热度 100 和热度 101 的差别更大,这里可以应用 modifier: "log1p",实际寄义是:new_score = old_score * log( 1 + number of douyin_hot )。Modifier 可选的值有:[b]none(默认状态)、log、log1p、log2p、ln、ln1p、ln2p、square、sqrt、reciprocal。[/b]如下图,利用 Modifier: log1p 后评分曲线就会平缓许多。
  3. [align=center][img=416,272]https://img-blog.csdnimg.cn/direct/248cbbb9a8e2417486dbcea577b36c85.png[/img][/align]
  4. [size=2]Factor 调节因子[/size]
  5. 上面通过 Modifier 平滑了 douyin_hot 的值,如果仍然认为辅助因子的影响力不够,曲线过于平缓,那就可以通过 Factor 举行调节。添加了 Factor 后,得到的结果是双倍效果,评分如下:new_score = old_score * log ( 1 + factor * number of douyin_hot )。
  6. [code]GET /_search
  7. {
  8.     "query":{
  9.         "function_score":{
  10.             "query":{
  11.                 "match": {
  12.                     "query":"烧烤",
  13.                     "fields":"poi_name"
  14.                 }
  15.             },
  16.             "field_value_factor":{
  17.                 "field":"douyin_hot",
  18.                 "missing": 0,
  19.                 "modifier":"log1p",
  20.                 "factor":2
  21.             }
  22.         }
  23.     }
  24. }
复制代码
  1. [/code] [size=2]Boost_mode 权重模式[/size]
  2. 上面在算 new_score 时,都是在 old_score 的基础上乘一个值,如果这个值还是很大评分就会放大许多。因此可以通过 boost_mode 来控制新老分数的结合方式。通过下图可以看到用加和的评分结合方式,评分曲线就会平滑许多。
  3. [list]
  4. [*] [b]multiply[/b]:乘积,默认方式, new_score = old_score * 函数值。这个方式很容易放大老的评分。
  5. [*] [b]sum[/b]:加和,new_score = old_score + 函数值。这是比力常用的方式。
  6. [*] [b]min[/b]:取较小值,new_score = min(old_score, 函数值)。
  7. [*] [b]max[/b]:取最大值。
  8. [*] [b]replace[/b]:函数值代替 old_score。
  9. [/list] [align=center][align=center][img=400,261]https://img-blog.csdnimg.cn/direct/a0c462bcb55547409f0c0bb20d7648de.png[/img][/align][/align]
  10. [size=2][b]Max_boost 权重上限[/b][/size]
  11. 限制函数的最大效果是起到一个掩护的作用,避免辅助评分太高完全压过主查询的评分。这里不管field_value_factor 怎样举行盘算,函数值最大不会凌驾 1.5,由此就起到一个掩护的作用。
  12. [code]GET /_search
  13. {
  14.     "query":{
  15.         "function_score":{
  16.             "query":{
  17.                 "match": {
  18.                     "query":"烧烤",
  19.                     "fields":"poi_name"
  20.                 }
  21.             },
  22.             "field_value_factor":{
  23.                 "field":"douyin_hot",
  24.                 "missing": 0,
  25.                 "modifier":"log1p",
  26.                 "factor":2
  27.             },
  28.             "boost_mode":"sum",
  29.             "max_boost":1.5
  30.         }
  31.     }
  32. }
复制代码
越靠近越好 - 衰减函数

利用场景:以选择酒店为例,用户大概会思量远近、代价,如果位置在景区里而且代价也可以担当,那也在选择范围内;如果位置离游玩的位置近,差价可以抵平路途利用的钱,这也是可以选择的。所以酒店的排序,不会是单一维度的,许多都是“智能”排序的。function_score 提供了衰减函数使我们可以在多个维度之间做出权衡。
有三种衰减函数 - Linear 线性、Exp 指数、Gauss 高斯,可以操纵数值、时间、经纬度,担当如下参数:


  • origin:中央点或最佳值,落在 origin 上,评分为 1.0。
  • offset:以原点为中央,设置一个非零的偏移量 Offset 覆盖一个数值范围,在这个范围内,评分都是 1.0。
  • scale:衰减率,即一个文档远离 [ origin-offset, origin+offset ] 范围时,评分改变的速率。
  • decay:从 origin 衰减到 scale 位置时,评分的值,默认是 0.5。

    • 如下图,origin 是 40,offset 是5,scale 也设置为 5,decay=0.5,标识当值偏移到 [ origin - offset - scale, origin + offset + scale ] 时,评分 = decay = 0.5,这样就决定了曲线的陡度。

  1. [/code] [code]GET /_search
  2. {
  3.     "query": {
  4.         "function_score":{
  5.             "functions":[
  6.                 {
  7.                     "gauss":{
  8.                         "location":{
  9.                             "origin":{"lat":10.1, "lon":0.11},
  10.                             "offset":"1km",
  11.                             "scale":"2km"
  12.                         }
  13.                     }
  14.                 },
  15.                 {
  16.                     "gauss":{
  17.                         "price":{
  18.                             "origin":"200",
  19.                             "offset":"50",
  20.                             "scale":"30"
  21.                         }
  22.                     },
  23.                     "weight":2
  24.                 },
  25.             ]
  26.         }
  27.     }
  28. }
复制代码
总结

通过以上先容我们可以了解到 ES 可支持的排序本领还是非常丰富的,我们要实现置顶或沉底效果,并不能非黑即白的选择,更多的场景中须要根据实际须要做多个本领的组合,只管 function_score 表现优异,但越复杂的查询对 ES 的查询压力也越大,当数据量很大时必须要权衡性能和排序效果。上线前,也须要在仿真环境中验证大数据量下的性能表现。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

忿忿的泥巴坨

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表