1. 程式人生 > >乾貨 | 通透理解Elasticsearch聚合

乾貨 | 通透理解Elasticsearch聚合

使用Elasticsearch的過程中,除了全文檢索,或多或少會做統計操作,而做統計操作勢必會使用Elasticsearch聚合操作。
類似mysql中group by的terms聚合用的最多,但當遇到複雜的聚合操作時,往往會捉襟見肘、不知所措…
這也是社群中聚合操作幾乎每天都會被提問的原因。

本文基於官方文件,梳理出聚合的以下幾個核心問題,目的:將Elasticsearch的聚合結合實際場景說透。

1、Elasticsearch聚合最直觀展示

區別於倒排索引的key value的全文檢索,聚合兩個示例如下:
如下圖,是基於某特定分類的聚合統計結果。
在這裡插入圖片描述
如下圖:是基於月份的聚合統計結果。
在這裡插入圖片描述

2、Elasticsearch聚合定義

聚合有助於基於搜尋查詢提供聚合資料。 它基於稱為聚合的簡單構建塊,可以組合以構建複雜的資料。
基本語法結構如下:

"aggregations" : {
    "<aggregation_name>" : {
        "<aggregation_type>" : {
            <aggregation_body>
        }
        [,"meta" : {  [<meta_data_body>] } ]?
        [,"aggregations" : { [<sub_aggregation>]+ } ]?
    }
    [,"<aggregation_name_2>" : { ... } ]*
}

3、Elasticsearch聚合分類

在這裡插入圖片描述

3.1 分類1:Metric聚合

基於一組文件進行聚合。所有的文件在一個檢索集合裡,文件被分成邏輯的分組。
類比Mysql中的: MIN(), MAX(), STDDEV(), SUM() 操作。

        單值Metric
                |
               v
SELECT AVG(price) FROM products


         多值Metric
          |          |
          v          v
SELECT MIN(price), MAX(price) FROM products
Metric聚合的DSL類比實現:
{
    "aggs":{
        "avg_price":{
            "avg":{
                "field":"price"
            }
        }
    }
}

Metric聚合操作對比:

Aggregation Elasticsearch MySQL
Avg Yes Yes
Cardinality——去重唯一值 Yes (Sample based) Yes (Exact)——類似:distinct
Extended Stats Yes StdDev bounds missing
Geo Bounds Yes for future blog post
Geo Centroid Yes for future blog post
Max Yes Yes
Percentiles Yes Complex SQL or UDF
Percentile Ranks Yes Complex SQL or UDF
Scripted Yes No
Stats Yes Yes
Top Hits——很重要,易被忽視 Yes Complex
Value Count Yes Yes

其中,Top hits子聚合用於返回分組中Top X匹配結果集,且支援通過source過濾選定欄位值。

分類2:Bucketing聚合

基於檢索構成了邏輯文件組,滿足特定規則的文件放置到一個桶裡,每一個桶關聯一個key。
類比Mysql中的group by操作,
Mysql使用舉例:

           基於size 分桶 ...、
SELECT size COUNT(*) FROM products GROUP BY size 

+----------------------+
| size     |  COUNT(*) |
+----------------------+ 
| S        |   123     | <--- set of rows with size = S
| M        |   456     |
| ...      |  ...      |

bucket聚合的DSL類比實現:

{
  "query": {
    "match": {
      "title": "Beach"
    }
  },
  "aggs": {
    "by_size": {
      "terms": {
        "field": "size"
      }
    },
    "by_material": {
      "terms": {
        "field": "material"
      }
    }
  }
}

Bucketing聚合對比

Aggregation Elasticsearch MySQL
Childen——父子文件 Yes for future blog post
Date Histogram——基於時間分桶 Yes Complex
Date Range Yes Complex
Filter Yes n/a (yes)
Filters Yes n/a (yes)
Geo Distance Yes for future blog post
GeoHash grid Yes for future blog post
Global Yes n/a (yes)
Histogram Yes Complex
IPv4 Range Yes Complex
Missing Yes Yes
Nested Yes for future blog post
Range Yes Complex
Reverse Nested Yes for future blog post
Sampler Yes Complex
Significant Terms Yes No
Terms——最常用 Yes Yes

分類3:Pipeline聚合

對聚合的結果而不是原始資料集進行操作。
想象一下,你有一個日間交易的網上商店,想要了解所有產品的按照庫存日期分組的平均價格。
在SQL中你可以寫:

SELECT in_stock_since, AVG(price) FROM products GROUP BY in_stock_since。

ES使用舉例:
以下Demo實現更復雜,按月統計銷售額,並統計出月銷售額>200的資訊。
下一節詳細給出DSL,不再重複。

分類4:Matrix聚合

ES6.4官網釋義:此功能是實驗性的,可在將來的版本中完全更改或刪除。

3、Elasticsearch聚合完整舉例

3.1 步驟1:動態Mapping,匯入完整資料

POST _bulk
{"index":{"_index":"cars","_type":"doc","_id":"1"}}
{"name":"bmw","date":"2017-06-01", "color":"red", "price":30000}
{"index":{"_index":"cars","_type":"doc","_id":"2"}}
{"name":"bmw","date":"2017-06-30", "color":"blue", "price":50000}
{"index":{"_index":"cars","_type":"doc","_id":"3"}}
{"name":"bmw","date":"2017-08-11", "color":"red", "price":90000}
{"index":{"_index":"cars","_type":"doc","_id":"4"}}
{"name":"ford","date":"2017-07-15", "color":"red", "price":20000}
{"index":{"_index":"cars","_type":"doc","_id":"5"}}
{"name":"ford","date":"2017-07-01", "color":"blue", "price":40000}
{"index":{"_index":"cars","_type":"doc","_id":"6"}}
{"name":"bmw","date":"2017-08-01", "color":"green", "price":10000}
{"index":{"_index":"cars","_type":"doc","_id":"7"}}
{"name":"jeep","date":"2017-07-08", "color":"red", "price":110000}
{"index":{"_index":"cars","_type":"doc","_id":"8"}}
{"name":"jeep","date":"2017-08-25", "color":"red", "price":230000}

3.2 步驟2:確認Mapping

GET cars/_mapping

3.3 步驟3:Matric聚合實現

求車的平均價錢。

POST cars/_search
{
  "size": 0,
  "aggs": {
    "avg_grade": {
      "avg": {
        "field": "price"
      }
    }
  }
}

3.4 步驟4:bucket聚合與子聚合實現

按照車品牌分組,組間按照車顏色再二次分組。

POST cars/_search
{
  "size": 0,
  "aggs": {
    "name_aggs": {
      "terms": {
        "field": "name.keyword"
      },
      "aggs": {
        "color_aggs": {
          "terms": {
            "field": "color.keyword"
          }
        }
      }
    }
  }
}

3.5 步驟5:Pipeline聚合實現

按月統計銷售額,並統計出總銷售額大於200000的月份資訊。

POST /cars/_search
{
  "size": 0,
  "aggs": {
    "sales_per_month": {
      "date_histogram": {
        "field": "date",
        "interval": "month"
      },
      "aggs": {
        "total_sales": {
          "sum": {
            "field": "price"
          }
        },
        "sales_bucket_filter": {
          "bucket_selector": {
            "buckets_path": {
              "totalSales": "total_sales"
            },
            "script": "params.totalSales > 200000"
          }
        }
      }
    }
  }
}

4、Elasticsearch聚合使用指南

認知前提:知道Elasticsearch聚合遠比Mysql中種類要多,可實現的功能點要多。
遇到聚合問題,基於4個分類,查詢對應的官網API資訊。
以最常見場景為例:

  1. 確定是否是分組group by 操作,如果是,使用bucket聚合中的terms聚合實現;
  2. 確定是否是按照時間分組操作,如果是,使用bucket聚合中date_histogram的聚合實現;
  3. 確定是否是分組,組間再分組操作,如果是,使用bucket聚合中terms聚合內部再terms或者內部top_hits子聚合實現;
  4. 確定是否是求最大值、最小值、平均值等,如果是,使用Metric聚合對應的Max, Min,AVG等聚合實現;
  5. 確定是否是基於聚合的結果條件進行判定後取結果,如果是,使用pipline聚合結合其他聚合綜合實現;

多嘗試,多在kibana的 dev tool部分多驗證。

這裡寫圖片描述
打造Elasticsearch基礎、進階、實戰第一公眾號!