【问题标题】:ElasticSearch: aggregation on _score field w/ Groovy disabledElasticSearch:在禁用 Groovy 的 _score 字段上进行聚合
【发布时间】: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 安装或配置的一部分,我无法添加或编辑任何内容。

【问题讨论】:

    标签: groovy elasticsearch scripting


    【解决方案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

    【讨论】:

      猜你喜欢
      • 2014-08-24
      • 1970-01-01
      • 2019-04-02
      • 1970-01-01
      • 1970-01-01
      • 2016-05-29
      • 2023-04-03
      • 1970-01-01
      • 2019-01-12
      相关资源
      最近更新 更多