使用Elasticsearch的过程中,除了全文检索,或多或少会做统计操作,而做统计操作势必会使用Elasticsearch聚合操作。
类似mysql中group by的terms聚合用的最多,但当遇到复杂的聚合操作时,往往会捉襟见肘、不知所措…..这也是社区中聚合操作几乎每天都会被提问的原因。
本文基于官方文档,梳理出聚合的以下几个核心问题,目的:将Elasticsearch的聚合结合实际场景说透。
1、Elasticsearch聚合最直观展示
区别于倒排索引的key value的全文检索,聚合两个示例如下: 如下图,是基于某特定分类的聚合统计结果。
如下图:是基于月份的聚合统计结果。
2、Elasticsearch聚合定义
聚合是ES除了搜索功能外提供的针对ES数据做统计分析的功能。
搜索引擎的搜索部分侧重于过滤和搜索,而聚合侧重于数据统计和分析。
基本语法结构如下:
代码语言:javascript复制 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聚合分类
3.1 分类1:Metric聚合
基于一组文档进行聚合。所有的文档在一个检索集合里,文档被分成逻辑的分组。
类比Mysql中的: MIN(), MAX(), STDDEV(), SUM() 操作。
代码语言:javascript复制 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使用举例:
代码语言:javascript复制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类比实现:
代码语言:javascript复制 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中你可以写:
代码语言:javascript复制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,导入完整数据
代码语言:javascript复制 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
代码语言:javascript复制1GET cars/_mapping
3.3 步骤3:Matric聚合实现
求车的平均价钱。
代码语言:javascript复制 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聚合与子聚合实现
按照车品牌分组,组间按照车颜色再二次分组。
代码语言:javascript复制 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的月份信息。
代码语言:javascript复制 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信息。 以最常见场景为例:
- 确定是否是分组group by 操作,如果是,使用bucket聚合中的terms聚合实现;
- 确定是否是按照时间分组操作,如果是,使用bucket聚合中date_histogram的聚合实现;
- 确定是否是分组,组间再分组操作,如果是,使用bucket聚合中terms聚合内部再terms或者内部top_hits子聚合实现;确定是否是分组,组间再分组操作,
- 确定是否是求最大值、最小值、平均值等,如果是,使用Metric聚合对应的Max, Min,AVG等聚合实现;
- 确定是否是基于聚合的结果条件进行判定后取结果,如果是,使用pipline聚合结合其他聚合综合实现;
多尝试,多在kibana的 dev tool部分多验证。
参考: 1、http://t.cn/R8Gk7V0 2、http://t.cn/EhxwB63 3、http://t.cn/EhxwDKR