Elasticsearch 聚合
一、前言
聚合是一种基于搜索的数据汇总,通过组合可以完成复杂的操作。聚合可以对文档进行汇总、分组等。通过聚合,我们会得到一个数据的概览,是分析和总结全部的数据,而不是寻找单个文档。
二、分类
- Bucket Aggregation:一些列满足特定条件的文档的集合,类似MySQL的“group by”
- Metric Aggregation:一些数学运算,可以对文档字段进行统计分析,比如max、min、sum等。
- Pipeline Aggregation:对其他的聚合结果进行二次聚合
- Matrix Aggregation:支持对多个字段的操作并提供一个结果矩阵,7.x版本合并到Metric Aggregation中了。
三、聚合结构
{
"size": 0,
["query": {}, ]?
"aggs" : {
"${my_name}" : {
"${aggregation_type}" : {
<aggregation_body>
}
[,"meta" : { [<meta_data_body>] } ]?
[,"aggs" : { [<sub_aggregation>]+ } ]?
}
[,"${my_name}" : { ... } ]*
}
}
- 聚合可以进行嵌套,比如上面的“aggs”内部又嵌套了一个“aggs”
- “aggs”是简写,也可以写完整“aggregations”
- 最上面的“size”一般设置为0,聚合操作用于统计数据,无需输出文档
- query 查询,可选
- my_name 自定义名字
四、测试数据
使用kibana导入“kibana_sample_data_flights”,这个是飞机的航班信息,有地区、价格、天气等信息。
操作路径:Home –> 添加数据 –> 样例数据 –> Sample flight data
五、Bucket Aggregation
1、子聚合
根据目的地(DestCountry)进行分组,查看航班的数量
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"dest_count": {
"terms": {
"field": "DestCountry"
}
}
}
}
2、数字区间分组
根据价格区间进行分组,比如0到100元多少个,100到200元多少个
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"price_stat": { // 自定义名字
"histogram": {
"field": "AvgTicketPrice",
"interval": 100 // 指定区间
}
}
}
}
输出的结果中,key为“100.0”代表0到100.0的数据,计算公式如下
bucket_key = Math.floor(value / interval) * interval
3、日期区间分组
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"price_stat": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "month"
}
}
}
}
注意:日期间隔设置,7.x版本用“calendar_interval”,老版本用“interval”。
支持的时间间隔表达式
- 分钟:minute, 1m
- 小时:hour, 1h
- 天:day, 1d
- 星期:week, 1w
- 月:month, 1M
- 季度:quarter, 1q
- 年:year, 1y
六、Metric Aggregation
计算度量这类的聚合操作是以使用一种方式或者从文档中提取需要聚合的值为基础的。这些数据不但可以从文档(使用数据属性)的属性中提取出来,也可以使用脚本生成。
支持max、min、count、sum、avg、stats(各种统计信息)、cardinality(去重后数量)、percentiles(百分位)、geo_bounds(地理边界)
1、最值
输出航班的最大价格,最小价格
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"max_price": {
"max": {
"field": "AvgTicketPrice"
}
},
"mix_price": {
"min": {
"field": "AvgTicketPrice"
}
}
}
}
2、嵌套操作
输出各个目的地航班的最大价格,最小价格
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"dest_count":{
"terms": {
"field": "DestCountry"
},
"aggs": {
"max_price": {
"max": {
"field": "AvgTicketPrice"
}
},
"min_price": {
"min": {
"field": "AvgTicketPrice"
}
}
}
}
}
}
3、stats
一次性输出各种统计结果,包括count、min、max、sum、avg
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"my_stats":{
"stats": {
"field": "AvgTicketPrice"
}
}
}
}
4、cardinality
去重后数量统计
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"my_cardinality":{
"cardinality": {
"field": "DestCountry"
}
}
}
}
5、top_hits
top_hits 操作,最开头的几个文档。
获取去每个国家的航班的最小价格,下面的“”size”: 5”代表获取5个国家的航班,“”size”: 2”代表最低的2个价格。
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"my_count": {
"terms": {
"field": "DestCountry",
"size": 5
},
"aggs": {
"my_min_price": {
"top_hits": {
"size": 2,
"sort": [
{
"AvgTicketPrice": {
"order": "asc"
}
}
]
}
}
}
}
}
}
6、ranges 自定义范围分组
比如下面,小于200一个分组,200到500一个分组,大于500个分组,可以指定输出的key。
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"my_price_range":{
"range": {
"field": "AvgTicketPrice",
"ranges": [
{
"to": 200
},
{
"from": 200,
"to": 500
},
{
"key": ">500",
"from": 500
}
]
}
}
}
}
7、百分位聚合
百分位聚合,可以利用百分位聚合的结果评估数据分布,判断数据是否扭曲,判断数据是否双峰分布等。压测的时候经常使用,比如95百分位对应的值表示这个值大于95%的所有值。假设结果是“10%:12ms ,…, 70%:55ms, 99%:100ms”,说明通常情况下(70%),网页的响应时间在12ms~55ms,99%的网页在100ms内加载完成。
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"my_price_percentiles":{
"percentiles": {
"field": "AvgTicketPrice",
"percents": [
1,
5,
25,
50,
75,
95,
99
]
}
}
}
}
8、地理边界聚合
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"my_geo_bounds": {
"geo_bounds": {
"field": "DestLocation",
"wrap_longitude": true
}
}
}
}
9、优化 Terms 聚合的性能
设置 eager_global_ordinals 为true,会在内存中预先加载这些数据。
七、Pipeline Aggregation
对聚合分析的结果再次做聚合分析。
分两类
- Sibling:结果和现有分析结果同级。有min_bucket、max_bucket、avg_bucket、sum_bucket、stats_bucket、percentiles_bucket
- Parent:结果内嵌到现有的聚合分析结果之中。有derivative(差值,与前一个的差值,用于看趋势)、cumulative_sum(累计求和)、moving_avg(移动平均,数据在一个固定大小窗口里的平均值)
说明,bucket_path参数,指定路径,如果是二级路径,注意有一个“>”。
1、Sibling的例子
根据不同的目的地获取平均票据,并对这些平均票价做分析。
注意,my_distance
,my_avg_price
,my_result
这三个是自定义的变量名,buckets_path指定路径。
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"my_distance": {
"terms": {
"field": "DestCountry"
},
"aggs": {
"my_avg_price": {
"avg": {
"field": "AvgTicketPrice"
}
}
}
},
"my_result": {
"stats_bucket": {
"buckets_path": "my_distance>my_avg_price"
}
}
}
}
2、Parent的例子
统计每50km的平均票价,并查看其波动
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"my_distance": {
"histogram": {
"field": "DistanceKilometers",
"interval": 50
},
"aggs": {
"my_avg_price": {
"avg": {
"field": "AvgTicketPrice"
}
},
"my_result": {
"derivative": {
"buckets_path": "my_avg_price"
}
}
}
}
}
}
八、排序
根据数量(_count)进行排序,数量相同根据返回的key进行排序
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"dest_count": {
"terms": {
"field": "DestCountry",
"order": [
{
"_count": "asc"
},
{
"_key": "desc"
}
]
}
}
}
}
根据最终返回的结果进行排序,比如下面的my_stats
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"my_distance": {
"terms": {
"field": "DestCountry",
"order": {
"my_stats.min": "asc"
}
},
"aggs": {
"my_stats": {
"stats": {
"field": "AvgTicketPrice"
}
}
}
}
}
}
九、聚合分析的原理及精准度问题
Terms 聚合分析不准的原因,数据分散在多个分片上,Coordinating Node 无法获取数据全貌。
打开 show_term_doc_count_error,可以多看到两个返回值。
- doc_count_error_upper_bound:被遗漏的term 分桶,包含的文档,有可能的最大值
- sum_other_doc_count:除了返回结果 bucket的 terms 以外,其他 terms 的文档总数(总数-返回的总数)
那么如何解决呢?
- 解决方案 1:当数据量不大时,设置 Primary Shard 为 1;实现准确性。
- 解决方案 2:在分布式数据上,设置 shard_size 参数,提高精确度。原理:每次从 Shard 上额外多获取数据,提升准确率,但会降低响应时间。
shard_size 的默认大小 “shard_size = size * 1.5 * 10”,可以根据自己的需要进行设置。
十、资料
- https://www.elastic.co/guide/cn/elasticsearch/guide/current/aggregations.html
- https://www.elastic.co/guide/en/elasticsearch/reference/7.x/search-aggregations.html
- https://www.elastic.co/guide/en/elasticsearch/reference/7.x/tune-for-search-speed.html
- https://www.elastic.co/guide/en/elasticsearch/reference/7.x/search-aggregations-bucket-terms-aggregation.html
- Elasticsearch核心技术与实战(阮一鸣–极客时间)