全部产品
云市场

使用cava编写排序脚本

更新时间:2020-04-08 15:05:33

cava排序脚本简介

OpenSearch支持两阶段排序,粗排和精排。目前cava实现的排序脚本只支持在精排阶段生效。本文只聚焦在通过cava如何编写排序脚本,至于脚本的创建和使用请参考排序脚本api使用手册。另外也可以通过我们提供的命令行工具来创建排序脚本。
与排序表达式相比,使用cava编写排序脚本具有更强的灵活性,可以定制性。用户可以在脚本中通过cava支持的语法和OpenSearch提供的feature lib来实现自己的业务逻辑。

cava排序脚本接口

为了能够使用自定义的脚本进行算分排序,用户需要实现opensearch提供的算分接口类。接口类的代码如下:

  1. package users.scorer;
  2. import com.aliyun.opensearch.cava.framework.OpsScoreParams;
  3. import com.aliyun.opensearch.cava.framework.OpsScorerInitParams;
  4. class UserScorer {
  5. //可以定义一些成员变量
  6. boolean init(OpsScorerInitParams params) {
  7. //实现你的代码,初始请求级别的变量,比如类的成员变量
  8. return true;
  9. }
  10. double score(OpsScoreParams params) {
  11. double score = 0;
  12. //实现你的代码,并将算分结果赋值给score
  13. return score;
  14. }
  15. };

接口类UserScorer在包users.scorer下面,用户不能修改类名或者包名,否则编译会报错。UserScorer提供了init和score两个方法,用户可以在这个两个方法中实现自己的业务逻辑。
在opensearch中,对于每一个请求opensearch会先调用UserScorer的init方法初始化一些请求级别的变量(比如类的成员变量),该方法对于每个请求只会执行一次,如果返回失败请求终止。然后对于每个命中的且参与精排的文档,opensearch会依次调用score方法对文档进行算分,最终会根据算分结果进行排序。
init接口的输入参数为OpsScorerInitParams,通过该参数用户可以获取一些请求级别的资源。我们建议UserScorer的成员变量在init阶段进行初始化。
score接口的输入参数为OpsScoreParams,通过该参数用户可以获取一些请求和doc级别的资源。score接口对于每个参与算分的文档都会调用一次,所以对于请求级别的资源(比如kvpairs中的一些参数,一些feature对象的创建)尽量不要在score接口中获取(可以在init接口中获取),对于doc级别的资源可以在score接口中进行获取。
init和score的函数定义用户不能修改(比如改变返回值类型或者输入参数),否则编译的时候会报错。

cava排序脚本示例

  1. package users.scorer;
  2. import cava.lang.CString;
  3. import com.aliyun.opensearch.cava.framework.OpsScoreParams;
  4. import com.aliyun.opensearch.cava.framework.OpsScorerInitParams;
  5. import com.aliyun.opensearch.cava.framework.OpsRequest;
  6. import com.aliyun.opensearch.cava.framework.OpsKvPairs;
  7. import com.aliyun.opensearch.cava.framework.OpsDoc;
  8. import com.aliyun.opensearch.cava.features.TextRelevance; //引用需要使用的特征
  9. class UserScorer {
  10. TextRelevance _textRelevance; //定义算分特征作为成员变量
  11. boolean init(OpsScorerInitParams params) {
  12. if (!params.getDoc().requireAttribute("shop_margin")) { //算分中使用的属性字段,需要在init接口中声明
  13. return false;
  14. }
  15. _textRelevance = new TextRelevance("name"); //算分特征在init接口中声明
  16. return true;
  17. }
  18. double score(OpsScoreParams params) {
  19. float shopMargin = params.getDoc().docFieldFloat("shop_margin"); //获取文档中字段的值
  20. float textScore = _textRelevance.evaluate(params); //计算特征分数
  21. double score = textScore * 30.0 + shopMargin;
  22. return score;
  23. }
  24. }

开发排序脚本注意事项

  • OpenSearch提供的特征类,建议定义为UserScorer的成员变量,并在init函数中初始化,在score函数中进行算分,如果在score阶段初始化会造成极大的性能浪费。
  • 对于请求中自定义的参数,建议定义为UserScorer的成员变量,并在init函数中获取它的值。
  • 如果要获取文档中的字段,字段首先要在应用结构中定义为属性字段,然后再init函数中进行声明,在score函数中获取具体的值。
  • 由于目前只支持用户上传单个文件,所以只能在users.scorer包下面定义类。
  • 可以使用import关键字引用opensearch提供的系统库,但是目前不支持import com.aliyun.opensearch.cava.framework.*;这种语法。
  • 对于单次查询请求,排序脚本运行时使用内存的上限是40M,如果内存使用超过限制,查询请求会报错并返回,所以不要在脚本中进行大块内存操作,特别是不要在score函数中频繁的进行new操作,或者使用大量的字符串。对于内存超过限制的请求,opensearch在报错的同时也会有结果返回(尽量保证有结果),只是其中一部分文档使用了排序脚本进行算分,一部分没有。
  • 在排序脚本中可以使用for循环,或者进行函数调用。为了避免死循环和无限次函数调用,对于单次查询请求,opensearch限制排序脚本中for循环和函数调用的次数不能超过100000次,如果超过限制查询请求会报错并提前返回。
  • 使用排序脚本排序的文档数受限于rerank_size参数,用户可以通过该参数来调整参与排序的文档个数,从而避免触发内存和循环次数的限制。