使用Elasticsearch(ES)作为搜索引擎时我们常常需要根据文档的属性值自定义它们的排序,为用户提供高质量的搜索结果。
以下内容已在 ElasticSearch 7.1.1 Linux 集群中测试。
ES中的rest_api中提供了自定义评分选项,分别为function_score下的function_script和script_score。其中function_score可以用于简单的评分需求,但是当我们要加入文档PageRank的影响,加入时间衰减因子,且根据文档类型给予不同的乘法因子等,简单的function_script就无能为力了;而script_score,也就是本文将介绍的内容,可以用于满足复杂的,高度定制化的评分需求。
painless语言可以采用动态隐式类型声明,类似groovy的语法,如:
def i = 1;
也可以使用静态显式类型,类似Java的语法:
int i = 1;
基于明确好于隐式的原则,接下来实例中采用强类型的Java语法。
Java ES前端代码:
/**这里的script-id为我们通过ES的_scripts API储存在ES集群中的值一下为Kibana devtools中更新和获取最新值方法,更新实时生效下次请求就会有最新的排序结果更新方法(script-id是自已定的script的名字):POST _scripts/script-id{"script": {"lang" : "painless","source" : "// Your script score code here "}}获取方法:GET _scripts/script-id*/
// 在Java 中使用stored script_score:Map scriptParams = new HashMap() {
private static final long serialVersionUID = 1L;
{
put("now", new Date().toInstant().toEpochMilli());
}
};
Script script = new Script(ScriptType.STORED, null, "script-id", scriptParams);
ScriptScoreFunctionBuilder scriptScoreBuilder = ScoreFunctionBuilders.scriptFunction(script);
FunctionScoreQueryBuilder functionScoreBuilder = QueryBuilder
.functionScoreQuery(/* originalBuilder */, scriptScoreBuilder)
.boostMode(CombineFunction.REPLACE);
painless示例,在下面的脚本中我们用painless脚本实现了等价于内置高斯衰减的函数
// params、_score和doc是特殊变量
// params用来获取在(Java)代码中生成得到的值,// 如new Date()long now = params['now']; // 从params获取now变量的值,这个值在每次接受请求时 // 由Java生成,比如:new Date().toInstant().toEpochMilli() // 处于安全考虑,painless中不允许new Date()的使用。
// _score是当前文档的BM25评分。// 以下painless代码实现了获取当前时间和根据当前时间更新评分。
// doc是当前文档// 可以通过doc['SomeProp']获取文档的SomeProp属性的包装对象// 通过doc['SomeProp'].value获取文档的SomeProp属性真实值
// 假设索引中有类型为Date的pubDate字段,记录的文档的发表时间。// 用doc['pubDate'].value获取值long docTime = doc['pubDate'].value.toInstant().toEpochMilli();
long docVal = now - docTime;
long year = 1000L * 3600 * 24 * 365; // year in milliseconds
long gaussScale = year * 1.5;
long gaussOffset = year * 0;
double gaussDecay = 0.5;
double sigmaSquare = - (Math.pow(gaussScale, 2) / (2 * Math.log(gaussDecay)));
double gaussMultiplier = Math.exp(-1 * Math.pow(Math.max(0, Math.abs(docTime - now) - gaussOffset), 2) / 2 / sigmaSquare);
return _score * gaussMultiplier;
参考