使用Elasticsearch的过程中,除了全文检索,或多或少会做统计操作,而做统计操作势必会使用Elasticsearch聚合操作。

类似mysql中group by的terms聚合用的最多,但当遇到复杂的聚合操作时,往往会捉襟见肘、不知所措…..这也是社区中聚合操作几乎每天都会被提问的原因。

本文基于官方文档,梳理出聚合的以下几个核心问题,目的:将Elasticsearch的聚合结合实际场景说透。

1、Elasticsearch聚合最直观展示

区别于倒排索引的key value的全文检索,聚合两个示例如下:
如下图,是基于某特定分类的聚合统计结果。

干货 | 通透理解Elasticsearch聚合_sql


如下图:是基于月份的聚合统计结果。

干货 | 通透理解Elasticsearch聚合_elasticsearch_02


2、Elasticsearch聚合定义

聚合是ES除了搜索功能外提供的针对ES数据做统计分析的功能。

搜索引擎的搜索部分侧重于过滤和搜索,而聚合侧重于数据统计和分析。

基本语法结构如下:

1"aggregations" : {
2 "<aggregation_name>" : {
3 "<aggregation_type>" : {
4 <aggregation_body>
5 }
6 [,"meta" : { [<meta_data_body>] } ]?
7 [,"aggregations" : { [<sub_aggregation>]+ } ]?
8 }
9 [,"<aggregation_name_2>" : { ... } ]*
10}

3、Elasticsearch聚合分类

干货 | 通透理解Elasticsearch聚合_mysql_03

3.1 分类1:Metric聚合

基于一组文档进行聚合。所有的文档在一个检索集合里,文档被分成逻辑的分组。

类比Mysql中的: MIN(), MAX(), STDDEV(), SUM() 操作。

1        单值Metric
2 |
3 v
4SELECT AVG(price) FROM products
5
6
7 多值Metric
8 | |
9 v v
10SELECT MIN(price), MAX(price) FROM products
11Metric聚合的DSL类比实现:
12{
13 "aggs":{
14 "avg_price":{
15 "avg":{
16 "field":"price"
17 }
18 }
19 }
20}

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使用举例:

1           基于size 分桶 ...、
2SELECT size COUNT(*) FROM products GROUP BY size
3
4+----------------------+
5| size | COUNT(*) |
6+----------------------+
7| S | 123 | <--- set of rows with size = S
8| M | 456 |
9| ... | ... |

bucket聚合的DSL类比实现:

1{
2 "query": {
3 "match": {
4 "title": "Beach"
5 }
6 },
7 "aggs": {
8 "by_size": {
9 "terms": {
10 "field": "size"
11 }
12 },
13 "by_material": {
14 "terms": {
15 "field": "material"
16 }
17 }
18 }
19}

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中你可以写:

1SELECT 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,导入完整数据

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

3.2 步骤2:确认Mapping

1GET cars/_mapping

3.3 步骤3:Matric聚合实现

求车的平均价钱。

1POST cars/_search
2{
3 "size": 0,
4 "aggs": {
5 "avg_grade": {
6 "avg": {
7 "field": "price"
8 }
9 }
10 }
11}

3.4 步骤4:bucket聚合与子聚合实现

按照车品牌分组,组间按照车颜色再二次分组。

1POST cars/_search
2{
3 "size": 0,
4 "aggs": {
5 "name_aggs": {
6 "terms": {
7 "field": "name.keyword"
8 },
9 "aggs": {
10 "color_aggs": {
11 "terms": {
12 "field": "color.keyword"
13 }
14 }
15 }
16 }
17 }
18}

3.5 步骤5:Pipeline聚合实现

按月统计销售额,并统计出总销售额大于200000的月份信息。

1POST /cars/_search
2{
3 "size": 0,
4 "aggs": {
5 "sales_per_month": {
6 "date_histogram": {
7 "field": "date",
8 "interval": "month"
9 },
10 "aggs": {
11 "total_sales": {
12 "sum": {
13 "field": "price"
14 }
15 },
16 "sales_bucket_filter": {
17 "bucket_selector": {
18 "buckets_path": {
19 "totalSales": "total_sales"
20 },
21 "script": "params.totalSales > 200000"
22 }
23 }
24 }
25 }
26 }
27}

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部分多验证。


参考:
1、http://t.cn/R8Gk7V0

2、http://t.cn/EhxwB63

3、http://t.cn/EhxwDKR