ElasticSearch:在禁用 Groovy 的 _score 字段上进行聚合

Posted

技术标签:

【中文标题】ElasticSearch:在禁用 Groovy 的 _score 字段上进行聚合【英文标题】:ElasticSearch: aggregation on _score field w/ Groovy disabled 【发布时间】:2015-07-17 03:51:03 【问题描述】:

我见过的每个对 _score 字段进行聚合或与之相关的示例(例如,ElasticSearch: aggregation on _score field?)似乎都需要使用脚本。出于安全原因,ElasticSearch 默认禁用动态脚本,有没有办法在不求助于将脚本文件加载到每个 ES 节点或重新启用动态脚本的情况下完成此操作?

我的原始聚合如下所示:

"aggs": 
    "terms_agg": 
        "terms": 
            "field": "field1",
            "order": "max_score": "desc"
        ,
     "aggs": 
         "max_score": 
             "max": "script": "_score"
         ,
         "top_terms": 
             "top_hits": "size": 1
         
      

尝试将表达式指定为 lang 似乎不起作用,因为 ES 会引发错误,指出分数只能在用于排序时访问。我想不出任何其他按分数字段排序我的存储桶的方法。有人有什么想法吗?

编辑:澄清一下,我的限制是不能修改服务器端。即,作为 ES 安装或配置的一部分,我无法添加或编辑任何内容。

【问题讨论】:

【参考方案1】:

一种可能的方法是使用其他可用的脚本选项。 mvel 似乎无法使用,除非启用动态脚本。而且,除非a more fine-grained control of scripting enable/disable 达到1.6 版本,否则我认为不可能为mvel 而不是groovy 启用动态脚本。

我们留下了默认启用的nativemustache(用于模板)。我不认为自定义脚本可以使用 mustache 完成,如果可能的话我没有找到方法,我们只剩下 native (Java) 脚本。

这是我的看法:

创建NativeScriptFactory的实现:
package com.foo.script;

import java.util.Map;

import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.NativeScriptFactory;

public class MyScriptNativeScriptFactory implements NativeScriptFactory 

    @Override
    public ExecutableScript newScript(Map<String, Object> arg0) 
        return new MyScript();
    


AbstractFloatSearchScript 的实现例如:
package com.foo.script;

import java.io.IOException;

import org.elasticsearch.script.AbstractFloatSearchScript;

public class MyScript extends AbstractFloatSearchScript 

    @Override
    public float runAsFloat() 
        try 
            return score();
         catch (IOException e) 
            e.printStackTrace();
        
        return 0;
    


或者,构建一个简单的 Maven 项目以将所有内容联系在一起。 pom.xml:
<properties>
    <elasticsearch.version>1.5.2</elasticsearch.version>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
    <dependency>
        <groupId>org.elasticsearch</groupId>
        <artifactId>elasticsearch</artifactId>
        <version>$elasticsearch.version</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

<build>
    <sourceDirectory>src</sourceDirectory>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>
构建它并获取生成的 jar 文件。 将 jar 放在 [ES_folder]/lib 中

编辑 elasticsearch.yml 并添加 script.native.my_script.type: com.foo.script.MyScriptNativeScriptFactory

重启 ES 节点。

在聚合中使用它:

  "aggs": 
    "max_score": 
      "max": 
        "script": "my_script",
        "lang": "native"
      
    
  

我上面的示例只是将_score 作为脚本返回,但当然,它可以用于更高级的场景。

编辑:如果您不允许触摸实例,那么我认为您没有任何选择。

【讨论】:

不幸的是,这不是一个理想的解决方案。此外,编写一个 groovy 脚本并将其放在 /scripts/ 中会容易得多。例如,在我的例子中,我可以在一个名为 _score.groovy 的文件中写入 _score 并将其放在 scripts/ 中,它会被 ES 自动拾取。根据我的测试,我什至不必更改"script": "_score" 代码来使用“script_file”,它就可以工作。问题是,这个解决方案需要直接访问修改节点,我不能保证。 我给了你一个替代方案,考虑到你的限制,我认为这是唯一可能的。您说您不想使用 Groovy(因为动态脚本的事情)并且不想“将脚本文件加载到每个 ES 节点或重新启用动态脚本”。相反,如果您想使用脚本文件,那么可以 - 更简单,但您说您不想这样做。如果您保持最初的限制,我认为除了native 脚本之外别无他法。干杯。 啊,那么我想我的限制不够明确,因为您的替代方案属于那个桶(没有双关语)。我的限制是我无法控制并且无法修改其配置的 ES 集群。我将编辑帖子以澄清。谢谢!【参考方案2】:

ElasticSearch 至少在 1.7.1 版本和可能更早的版本中还提供了 Lucene 的 Expression 脚本语言的使用 - 由于 Expression 默认情况下是沙盒化的,它可以用于动态内联脚本,其方式与 Groovy 大致相同。在我们的案例中,我们的生产 ES 集群刚刚从 1.4.1 升级到 1.7.1,我们决定不再使用 Groovy,因为它的非沙盒特性,尽管我们仍然想使用动态脚本,因为随着我们继续微调我们的应用程序及其搜索层,它们提供的易于部署和灵活性。

虽然编写本机 Java 脚本来替代我们的动态 Groovy 函数分数在我们的案例中可能也是可行的,但我们想看看将 Expression 用于我们的动态内联脚本语言的可行性。阅读完文档后,我发现我们可以简单地将“lang”属性从"groovy" 更改为"expression" 在内联function_score 脚本中,并在script.inline: sandbox 文件中设置script.inline: sandbox 属性 -功能评分脚本无需任何其他修改即可工作。因此,我们现在可以继续在 ElasticSearch 中使用动态内联脚本,并在启用沙盒的情况下这样做(因为 Expression 默认是沙盒)。显然,还应实施其他安全措施,例如在应用程序代理和防火墙后面运行 ES 集群,以确保外部用户无法直接访问您的 ES 节点或 ES API。但是,这是一个非常简单的更改,目前已经解决了 Groovy 缺乏沙盒的问题以及在不使用沙盒的情况下运行的问题。

虽然将您的动态脚本切换到 Expression 可能仅在某些情况下有效或适用(取决于您的内联动态脚本的复杂性),但似乎值得分享这些信息,希望它能帮助其他开发人员。

请注意,作为其他受支持的 ES 脚本语言之一,Mustache 似乎仅可用于在您的搜索查询中创建模板。它似乎不适用于任何更复杂的脚本需求,例如function_score 等,尽管我不确定在第一次阅读更新的 ES 文档时这是否完全明显。

最后,另一个需要注意的问题是,在最新的 ES 版本中,Lucene Expression 脚本的使用被标记为实验性功能,并且文档指出,由于这个脚本扩展正在经历重大此时的开发工作,其用法或功能可能会在 ES 的后续版本中发生变化。因此,如果您确实切换到对任何脚本(动态或其他)使用 Expression,则应在您的文档/开发人员说明中注明,以便在下次升级 ES 安装之前重新访问这些更改,以确保您的脚本保持兼容并作为预计。

至少在我们的情况下,除非我们愿意允许在最新版本的 ES 中再次启用非沙盒动态脚本(通过script.inline: on 选项),以便内联 Groovy 脚本可以继续运行,切换Lucene Expression 脚本似乎是目前最好的选择。

看看未来版本中 ES 的脚本选择会发生哪些变化将会很有趣,特别是考虑到 Groovy 的(显然无效的)沙盒选项将在 2.0 版中完全删除。希望可以实施其他保护措施来启用动态 Groovy 使用,或者 Lucene Expression 脚本可能会取代 Groovy 并启用开发人员已经在使用的所有类型的动态脚本。

有关 Lucene Expression 的更多说明,请参阅此处的 ES 文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html#_lucene_expressions_scripts - 此页面也是有关计划从 ES v2.0+ 中删除 Groovy 沙盒选项的说明的来源。更多 Lucene Expression 文档可以在这里找到:http://lucene.apache.org/core/4_9_0/expressions/index.html?org/apache/lucene/expressions/js/package-summary.html

【讨论】:

以上是关于ElasticSearch:在禁用 Groovy 的 _score 字段上进行聚合的主要内容,如果未能解决你的问题,请参考以下文章

Elasticsearch 无法使用 lang groovy 运行内联脚本 [doc ....]

Elasticsearch的Groovy Script自定义评分检索

开启elastic search 脚本

Elasticsearch 顶尖高手(19)—基于groovy脚本执行partial update

(37)ElasticSearch基于groovy脚本执行partial update

Elasticsearch自定义过滤插件实现复杂逻辑过滤