Elasticsearch6使用painless脚本打分

业务搜索通常都需要对召回数据进行排序,返回更有价值的信息给用户。

ES默认按文本相关性排序,通常我们会通过嵌入脚本的形式来修改ES的打分机制,从而影响排序结果。

ES在5.x+版本后发明了一种语法类似javascript/groovy的专用脚本语言painless,我们需要写一个painless脚本,脚本中可以获取文本相关性得分,也可以获取文档的各个字段内容,也可以获取查询请求中传入的临时参数,综合来计算一个新的分数替代默认的文本相关性得分。

function_score

要实现自定义打分,需要使用funcion_score语法,它将query包装在内部,从而实现先召回 -> 再文本相关性评分 -> 最后自定义打分的功能。

官方文档链接:https://www.elastic.co/guide/en/elasticsearch/reference/6.1/query-dsl-function-score-query.html#function-field-value-factor

这一块内容我建议大家详细的掌握一下各个参数的作用。

painless

这个脚本语言和javascript很像,从名字也可以看出它的目标就是降低学习成本,让你上手就可以做事情。

官方文档链接:https://www.elastic.co/guide/en/elasticsearch/painless/6.1/painless-getting-started.html

这个脚本语法其实不用投入很多精力学习,它除了一些基础的if else之外,还提供了一些数据结构。

对于我们开发来说,最重要的是知道如何获取文本相关性,获取文档各个字段的值,获取请求传入的临时参数。

脚本在ES不同的请求中,获取上下文变量的方式不同,需要参考官方链接:https://www.elastic.co/guide/en/elasticsearch/reference/master/modules-scripting-fields.html

painless调试

因为painless是专用ES的嵌入式脚本,没有独立的runtime环境,所以必须通过ES请求来执行它,这样脚本才能得到执行,同时也能访问到请求的上下文变量。

为了调试painless,我们需要学会一个Debug命令,参考官方链接:https://www.elastic.co/guide/en/elasticsearch/painless/6.1/painless-debugging.html

一个初步的脚本结构是这样的,它使用内联的painless代码来做一些初步的调试:

主要关注4点:

  1. 最外层的query context内直接嵌套一层function_score,而function_score内重新创建query context。
  2. 与内层query context平级放置functions自定义打分函数,我的最终相关性希望是:文本相关性 * 脚本相关性,所以boost_mode=multiply;大家可以根据自己的需求和调试结果,换不同的boost_mode去尝试。
  3. 出于性能考虑,优先对非text字段使用doc获取内容(字段列存储,效率最高),其次如果字段配置了store=true则使用_fields获取内容,最后才考虑使用params._source获取整个文档并从中提取某个字段。
  4. 上述source直接inline了一段painless代码,ES第一次收到就会编译代码并缓存,这种适合比较短小的脚本,传输没有太大的代价。

stored painless

如果我们的painelss打分脚本逻辑比较重,那么可能不适合每次查询时内联在请求中,所以需要先把脚本提交到ES保存,随后请求中只需要携带脚本的ID即可,参考官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/master/modules-scripting-using.html#modules-scripting-stored-scripts

首先,把脚本上传到ES,需要给它指定一个ID唯一标识:

ES6.0非常人性化,因为以前这种打分脚本都是需要运维去部署到每个ES节点的目录下的,而现在完全不需要了。

查询的时候,不再需要携带脚本代码,而是指定一个ID即可:

实现打分逻辑

这一块我也是刚开始摸索,给大家举个例子作为敲门砖吧:

这个打分脚本的逻辑:文章的点赞和评论数相乘,然后通过log1p归一化成一个比较小的值,作为打分返回。

下面是查询时的函数配置:

一共有2个算分函数。

第一个是自定义painless脚本函数,返回的是基于热度的归一化分值。

第二个是内置衰减函数,基于时间进行分数衰减,1天内的不降分(也就是1分),2天之前的降到0.5分。

2个函数的得分指定了score_mode=multiply,所以两个分数进行直接相乘作为函数的总得分。

最后通过boost_mode=multiply指定,文本相关性得分_score将和上述函数总得分再次相乘,得到文档的最终相关性得分。

目前我还没有基于实际大规模的文档进行调研,脚本打分应该怎么归一化,用权重加和还是用乘法,我其实也分不清区别。

有相关经验的同学欢迎留言指教。

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~