Elasticsearch Script度量聚集教程
前面有兩篇博文詳細介紹了Elasticsearch的度量聚集。本文補充介紹腳本度量,實現使用腳本自定義邏輯提供度量輸出。
1. 語法介紹
這裏通過示例代碼來說明:
POST ledger/_search?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"
}
}
}
}
}
上面示例的腳本使用存儲腳本,也可以是內聯腳本。首先我們介紹腳本範圍,共分爲四個階段執行:
init_script
對所有集合的文檔最先執行的腳本,可選腳本,一般用於設置初始化狀態。
map_script
每個集合文檔執行一次。必須提供腳本。
combine_script
在集合文檔腳本執行之後,每個分片執行一次。必須提供腳本,用於從每個分片返回信息。
reduce_script
在所以分片返回結果後相應節點上執行一次。必須提供腳本。腳本能訪問combine_script的數組類型結果。
返回值類型
雖然任何有效腳本對象都可以在單個腳本中使用,但腳本必須僅返回或存儲在狀態對象中以下類型:
- 基本類型
- String類型
- Map(包含key和這裏列舉的類型值)
- Array(包括這裏列舉類型值元素)
params
可選,用於給init_script, map_script 、 combine_script 三個腳本傳遞參數變量。讓用戶可以控制聚集的行爲,在不同腳本之間存儲狀態。缺省值爲:"params" : {}
2. 示例
定義映射
PUT ledger
{
"mappings": {
"properties": {
"type": {
"type": "keyword"
},
"amount": {
"type": "integer"
}
}
}
}
導入示例數據
PUT /ledger/_bulk?refresh
{"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}
實現需求
使用腳本聚集計算利潤:銷售額-成本。實現代碼:
POST ledger/_search?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" : 10,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"profit" : {
"value" : 170.0
}
}
}
過程解釋
- 分片說明
這裏共四條數據:
{"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}
假設第一、三兩天在Shard A,另外兩條在Shard B .下面我們詳細對每一步驟進行說明。
- 初始化(init_script)之前
state初始化爲一個空對象。
"state" : {}
- 初始化(init_script)之後
任何集合文檔執行之前每個分片執行一次,因此在每個分片上產生一個對象:
Shard A
"state" : {
"transactions" : []
}
Shard B
"state" : {
"transactions" : []
}
- map_script之後
每個分片對每個文檔運行map_script腳本:
Shard A
"state" : {
"transactions" : [ 80, -30 ]
}
Shard B
"state" : {
"transactions" : [ -10, 130 ]
}
- combine_script 之後
文檔收集之後,combine_script腳本在每個分片上執行,通過累加事務數組的值計算每個分片的利潤並傳給響應節點:
Shard A
50
Shard B
120
- reduce_script 之後
reduce_script腳本收到包括合併每個分片的結果的數組states
:
"states" : [
50,
120
]
最後通過累加聚集上面數組的值,生成最終返回的利潤結果:
{
...
"aggregations": {
"profit": {
"value": 170
}
}
}
到此詳細的執行流程解釋完成。最後需要說明的是如果腳本聚集的父分組沒有收集到任何記錄,那麼響應會是null,因此處理腳本聚集時需要考慮這種情況。
3. 總結
本文介紹了Elasticsearch的腳本聚集,詳細介紹了其語法,並通過示例完整說明了其map-reduce的執行過程。