对ES官网的reference的翻译,同时也是备忘,ES版本为7.5

下面是正文翻译

==================================================================================================

脚本式指标聚合

使用脚本来提供指标输出的指标聚合。

例子(init_script为可选参数,其他参数都是必须填写的):

curl http://host_ip:host_port/ledger/_search?pretty
-H 'content-type: application/json'
-d '{
    "size": 0,
    "query": {
        "match_all": {}
    }
    "aggs": {
        "profit":{
            "scripted_metric": {
                "init_script" : "state.transactions = []", 
                "map_script" : "state.transactions.add(doc.type.value == 'sale' ? doc.amount.value : -1 * doc.amount.value)",
                "combine_script" : "double profit = 0; for (t in state.transactions) { profit += t } return profit",
                "reduce_script" : "double profit = 0; for (a in states) { profit += a } return profit"
            }
        }
    }
}'

上面的聚合证实了我们可以如何使用脚本聚合从销售额和开销中计算总盈利。

上面的聚合的响应:

{
    "took": 218,
    ...
    "aggregations": {
        "profit": {
            "value": 240.0
        }
   }
}

上面的例子也能使用保存的脚本(init、map和combine脚本用到的参数必须要在全局的params对象中指明,这样能够保证参数可以在脚本之间共享):

curl http://host_ip:host_port/ledger/_search?pretty
-H 'content-type: application/json'
-d '{
    "size": 0,
    "aggs": {
        "profit": {
            "scripted_metric": {
                "init_script" : {
                    "id": "my_init_script"
                },
                "map_script" : {
                    "id": "my_map_script"
                },
                "combine_script" : {
                    "id": "my_combine_script"
                },
                "params": {
                    "field": "amount" 
                },
                "reduce_script" : {
                    "id": "my_reduce_script"
                }
            }
        }
    }
}'

关于脚本更多详细的内容可以参考脚本文档。

允许的返回类型

尽管在单个脚本内可以使用任意有效的脚本对象,但脚本在state对象中返回或存储的必须是以下类型:

1)原始类型

2)字符串

3)map(只包含列举在此处的4种类型的key和value)

4)array(只包含列举在此处的4种类型的元素)

脚本的范围

脚本式指标聚合在它执行过程的4个阶段中使用下面几类脚本:

init_script

在文档的任意collection开始之前执行。允许聚合建立任意初始状态。

在上面的例子中,init_script在state对象中创建了一个array transactions

map_script

对每个被收集的文档都执行一次,该脚本为必备脚本。如果没有定义combine_script,由map_script生成的结果需要被存储在state对象中。

在上面的例子中,map_script检查type字段的值,如果type的值为sale,那么amount字段的值被添加到transaction数组中,如果type的值不是sale,那么amount字段的负值被添加到transaction数组中。

combine_script

当文档收集过程完成之后,每个分片都会执行一次combine_script.这个脚本是必须的,它允许聚合合并从每个分片返回的状态。

在上面的例子中,combine_script遍历所有存储好的transactions中并将profit变量中的值累加最终返回profit。(可以理解为上面三个script都是在分片内部进行的,但map_script都是针对文档的粒度,而combine_script是聚合分片内部每个文档的结果)

reduce_script

当所有的分片都返回结果之后会在coordinating节点执行一次reduce_script,这个脚本是必须的。reduce_script对变量states拥有访问权限,states变量是保存有每个分片combine_script结果的数组。

在上面的例子中,reduce_script遍历由每个分片返回的profit并在返回最终的profit值(会在聚合的响应中返回)之前对这些返回值进行累加。

例子

想象你把下面的文档存储到有2个分片的索引:

curl -X PUT "host_ip:host_port/transactions/_bulk?refresh&pretty" 
-H 'Content-Type: application/json' 
-d'
    {"index":{"_id":1}}
    {"type": "sale","amount": 80}
    {"index":{"_id":2}}
    {"type": "cost","amount": 10}
    {"index":{"_id":3}}
    {"type": "cost","amount": 30}
    {"index":{"_id":4}}
    {"type": "sale","amount": 130}
'

假如文档1和3在分片A上,文档2和4在分片B上,下面是每个阶段的聚合结果的具体分析。

在init_script之前

state被初始化为新的空对象:

"state": {}
在init_script之后

在每个分片上,文档收集过程执行之前,init_script都会运行一次,我们在每个分片上都会有一个拷贝:

分片A:

"state" : {
    "transactions" : []
}

分片B:

"state" : {
    "transactions" : []
}
在map_script之后

每个分片会收集文档并在每个收集的文档上运行map_script:

分片A:

"state" : {
    "transactions" : [ 80, -30 ]
}

分片B:

"state" : {
    "transactions" : [ -10, 130 ]
}
在combine_script之后

当文档收集过程完成之后,combine_script在每个分片上执行,并通过把transaction数组中的值累加来把所有的transactions降维成单个profit数字传递给coordinating节点:

分片A

50

分片B

120
在reduce_script之后

reduce_script接收states数组(由每个分片的conbine script的结果组成):

"states" : [
    50,
    120
]

reduce_script将每个分片的响应减少为一个最终的profit值(通过累加响应值)并将最终的这个profit值作为聚合的结果返回:

{
    ...

    "aggregations": {
        "profit": {
            "value": 170
        }
   }
}
其他的参数params

可选参数。params对象的内容会作为变量传递给init_script, map_script和combine_script。这种用途能够允许用户控制聚合的行为以及保存脚本间的状态。如果params没有定义的话,默认行为表示如下:

"params" : {}
空的桶

如果脚本式指标聚合的父桶没有收集任何文档的话,空的聚合响应(null值)会从分片中返回。在这种情况下,reduce_script的states变量会包含null作为从这个分片返回的响应。因此reduce_script应该预料并处理null响应的情况。