当前位置: 首页 > news >正文

Elasticsearch 的聚合(Aggregations)操作详解

目录

1. 概述

2. 聚合类型分类详解

2.1 桶聚合(Bucket Aggregations)

2.1.1  基础桶聚合

2.1.2 特殊桶聚合

2.1.3 高级桶聚合

2.2 指标聚合(Metric Aggregations)

2.2.1 单值指标聚合(Single-value Metrics)

2.2.2 多值指标聚合(Multi-value Metrics)

2.3 管道聚合(Pipeline Aggregation)

2.3.1 数学计算类

2.3.2 桶操作类

2.3.3  流程控制类

3. 聚合的作用范围

4. 聚合排序

5. ES聚合分析不精准原因分析

6. Elasticsearch 聚合性能优化


1. 概述

Elasticsearch 的聚合(Aggregations)功能用于对数据进行统计、分析和分组,类似 SQL 中的 GROUP BY 与聚合函数(如 SUMAVG)。其核心分为桶聚合指标聚合管道聚合三类

基本语法

聚合查询的语法结构与其他查询相似,通常包含以下部分:
  • 查询条件:指定需要聚合的文档,可以使用标准的 Elasticsearch 查询语法,如 term、match、range 等等。
  • 聚合函数:指定要执行的聚合操作,如 sum、avg、min、max、terms、date_histogram 等等。每个聚合命令都会生成一个聚合结果。
  • 聚合嵌套:聚合命令可以嵌套,以便更细粒度地分析数据。
GET <index_name>/_search
{"aggs": {"<aggs_name>": { // 聚合名称需要自己定义"<agg_type>": {"field": "<field_name>"}}}
}
  • aggs_name:聚合函数的名称
  • agg_type:聚合种类,比如是桶聚合(terms)或者是指标聚合(avg、sum、min、max等)
  • field_name:字段名称或者叫域名。

聚合类型分类

Elasticsearch 聚合操作主要分为三大类:

聚合类型核心功能典型应用场景
桶聚合将文档分组到"桶"中分组统计、分类分析
指标聚合对桶内文档进行数值计算平均值、求和等数值计算
管道聚合对其他聚合结果进行二次处理复杂统计分析

示例数据准备

DELETE /employees
#创建索引库
PUT /employees
{"mappings": {"properties": {"age":{"type": "integer"},"gender":{"type": "keyword"},"job":{"type" : "text","fields" : {"keyword" : {"type" : "keyword","ignore_above" : 50}}},"name":{"type": "keyword"},"salary":{"type": "integer"}}}
}PUT /employees/_bulk
{ "index" : {  "_id" : "1" } }
{ "name" : "Emma","age":32,"job":"Product Manager","gender":"female","salary":35000 }
{ "index" : {  "_id" : "2" } }
{ "name" : "Underwood","age":41,"job":"Dev Manager","gender":"male","salary": 50000}
{ "index" : {  "_id" : "3" } }
{ "name" : "Tran","age":25,"job":"Web Designer","gender":"male","salary":18000 }
{ "index" : {  "_id" : "4" } }
{ "name" : "Rivera","age":26,"job":"Web Designer","gender":"female","salary": 22000}
{ "index" : {  "_id" : "5" } }
{ "name" : "Rose","age":25,"job":"QA","gender":"female","salary":18000 }
{ "index" : {  "_id" : "6" } }
{ "name" : "Lucy","age":31,"job":"QA","gender":"female","salary": 25000}
{ "index" : {  "_id" : "7" } }
{ "name" : "Byrd","age":27,"job":"QA","gender":"male","salary":20000 }
{ "index" : {  "_id" : "8" } }
{ "name" : "Foster","age":27,"job":"Java Programmer","gender":"male","salary": 20000}
{ "index" : {  "_id" : "9" } }
{ "name" : "Gregory","age":32,"job":"Java Programmer","gender":"male","salary":22000 }
{ "index" : {  "_id" : "10" } }
{ "name" : "Bryant","age":20,"job":"Java Programmer","gender":"male","salary": 9000}
{ "index" : {  "_id" : "11" } }
{ "name" : "Jenny","age":36,"job":"Java Programmer","gender":"female","salary":38000 }
{ "index" : {  "_id" : "12" } }
{ "name" : "Mcdonald","age":31,"job":"Java Programmer","gender":"male","salary": 32000}
{ "index" : {  "_id" : "13" } }
{ "name" : "Jonthna","age":30,"job":"Java Programmer","gender":"female","salary":30000 }
{ "index" : {  "_id" : "14" } }
{ "name" : "Marshall","age":32,"job":"Javascript Programmer","gender":"male","salary": 25000}
{ "index" : {  "_id" : "15" } }
{ "name" : "King","age":33,"job":"Java Programmer","gender":"male","salary":28000 }
{ "index" : {  "_id" : "16" } }
{ "name" : "Mccarthy","age":21,"job":"Javascript Programmer","gender":"male","salary": 16000}
{ "index" : {  "_id" : "17" } }
{ "name" : "Goodwin","age":25,"job":"Javascript Programmer","gender":"male","salary": 16000}
{ "index" : {  "_id" : "18" } }
{ "name" : "Catherine","age":29,"job":"Javascript Programmer","gender":"female","salary": 20000}
{ "index" : {  "_id" : "19" } }
{ "name" : "Boone","age":30,"job":"DBA","gender":"male","salary": 30000}
{ "index" : {  "_id" : "20" } }
{ "name" : "Kathy","age":29,"job":"DBA","gender":"female","salary": 20000}

2. 聚合类型分类详解

2.1 桶聚合(Bucket Aggregations)

按照一定的规则,将文档分配到不同的桶中,从而达到分类的目的。将文档按规则分组到“桶”中,类似 SQL 的 GROUP BY。

桶聚合类型分类

2.1.1  基础桶聚合
聚合类型功能描述示例场景
terms按字段值分组按品牌统计商品数量
range按数值范围分组按价格区间统计商品
date_range按日期范围分组按时间段统计销售额
histogram按固定间隔分组每100元价格段统计商品数
date_histogram按时间间隔分组每月/每周统计销售额
geo_distance按地理位置距离分组按距离商圈范围统计门店

注意:

  • Terms,需要字段支持filedata
    • keyword 默认支持fielddata
    • text需要在Mapping 中开启fielddata,会按照分词后的结果进行分桶
2.1.2 特殊桶聚合
聚合类型功能描述示例场景
filters使用自定义过滤器分组同时统计多个过滤条件结果
nested处理嵌套对象分组统计订单中的商品分类
reverse_nested从嵌套对象返回父文档分组统计含特定商品的订单
children在父子文档关系中进行分组统计作者及其书籍
composite多维度组合分组(替代多级terms)品牌+颜色组合统计
sampler获取代表性样本分组大数据集抽样分析
2.1.3 高级桶聚合
聚合类型功能描述示例场景
significant_terms识别统计显著性高的词项发现异常关键词
geohash_grid按地理网格分组区域热力图分析
variable_width自动调整数值区间宽度分组智能价格分段
adjacency_matrix关系矩阵分组用户行为关联分析

示例

获取job的分类信息

# 对keword 进行聚合
GET /employees/_search
{"size": 0,"aggs": {"jobs": {"terms": {"field":"job.keyword"}}}
}
聚合可配置属性有:
  • field:指定聚合字段
  • size:指定聚合结果数量
  • order:指定聚合结果排序方式
默认情况下,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照_count降序排序。我们可以指定order属性,自定义聚合的排序方式:
GET /employees/_search
{"size": 0,"aggs": {"jobs": {"terms": {"field":"job.keyword","size": 10,"order": {"_count": "desc" }}}}
}

限定聚合范围

#只对salary在10000元以上的文档聚合
GET /employees/_search
{"query": {"range": {"salary": {"gte": 10000 }}}, "size": 0,"aggs": {"jobs": {"terms": {"field":"job.keyword","size": 10,"order": {"_count": "desc" }}}}
}

注意:对 Text 字段进行 terms 聚合查询,会失败抛出异常

解决办法:对 Text 字段打开 fielddata,支持terms aggregation

# 对 Text 字段进行 terms 聚合查询,会失败抛出异常
POST /employees/_search
{"size": 0,"aggs": {"jobs": {"terms": {"field":"job"}}}
}#对 Text 字段打开 fielddata,支持terms aggregation
PUT /employees/_mapping
{"properties" : {"job":{"type":  "text","fielddata": true}}
}# 对 Text 字段进行分词,分词后的terms
POST /employees/_search
{"size": 0,"aggs": {"jobs": {"terms": {"field":"job"}}}
}

Range & Histogram聚合

  • 按照数字的范围,进行分桶
  • 在Range Aggregation中,可以自定义Key
Range 示例:按照工资的 Range 分桶
Salary Range分桶,可以自己定义 key
POST employees/_search
{"size": 0,"aggs": {"salary_range": {"range": {"field":"salary","ranges":[{"to":10000},{"from":10000,"to":20000},{"key":">20000","from":20000}]}}}
}

Histogram示例:按照工资的间隔分桶

#工资0到10万,以 5000一个区间进行分桶
POST employees/_search
{"size": 0,"aggs": {"salary_histrogram": {"histogram": {"field":"salary","interval":5000,"extended_bounds":{"min":0,"max":100000}}}}
}

top_hits应用场景: 当获取分桶后,桶内最匹配的顶部文档列表

# 指定size,不同工种中,年纪最大的3个员工的具体信息
POST /employees/_search
{"size": 0,"aggs": {"jobs": {"terms": {"field":"job.keyword"},"aggs":{"old_employee":{"top_hits":{"size":3,"sort":[{"age":{"order":"desc"}}]}}}}}
}

嵌套聚合示例

# 嵌套聚合1,按照工作类型分桶,并统计工资信息
POST employees/_search
{"size": 0,"aggs": {"Job_salary_stats": {"terms": {"field": "job.keyword"},"aggs": {"salary": {"stats": {"field": "salary"}}}}}
}# 多次嵌套。根据工作类型分桶,然后按照性别分桶,计算工资的统计信息
POST employees/_search
{"size": 0,"aggs": {"Job_gender_stats": {"terms": {"field": "job.keyword"},"aggs": {"gender_stats": {"terms": {"field": "gender"},"aggs": {"salary_stats": {"stats": {"field": "salary"}}}}}}}
}

2.2 指标聚合(Metric Aggregations)

指标聚合主要用于对文档进行数值计算,分为两大类:

2.2.1 单值指标聚合(Single-value Metrics)

聚合类型

功能描述

公式/说明

avg

计算平均值

sum(field)/doc_count

sum

计算总和

Σ(field_values)

min

获取最小值

min(field_values)

max

获取最大值

max(field_values)

value_count

统计非空值数量

count(field_values)

cardinality

计算唯一值数量(去重计数)

distinct_count(field)

2.2.2 多值指标聚合(Multi-value Metrics)

聚合类型

功能描述

包含指标

stats

基础统计汇总

count, min, max, avg, sum

extended_stats

扩展统计(含标准差/方差)

stats + std_deviation, variance

percentiles

计算百分位值(如P95/P99)

[P50, P75, P90, P95]

percentile_ranks

计算值的百分位排名

值X的百分位位置

top_hits

返回每个分组的最相关文档

子聚合中的特殊类型

geo_centroid

计算地理坐标中心点

lat/lon平均值

median_absolute_deviation

计算中位数绝对偏差稳健统计离散度

示例

查询员工的最低最高和平均工资

#多个 Metric 聚合,找到最低最高和平均工资
POST /employees/_search
{"size": 0,  "aggs": {"max_salary": {"max": {"field": "salary"}},"min_salary": {"min": {"field": "salary"}},"avg_salary": {"avg": {"field": "salary"}}}
}

2.3 管道聚合(Pipeline Aggregation)

对聚合分析的结果,再次进行聚合分析。

2.3.1 数学计算类

聚合类型

功能

公式示例

cumulative_sum

累积和

Sₙ = a₁ + a₂ + ... + aₙ

derivative

变化率

Δy/Δx = (y₂-y₁)/(x₂-x₁)

moving_function

移动计算

SMA = (P₁+P₂+...+Pₙ)/n

2.3.2 桶操作类

聚合类型

功能

类似SQL

max/min/avg/sum_bucket

桶值极值/平均

SELECT MAX(subtotal)

stats/extended_stats_bucket

桶统计信息

SELECT STATS(subtotal)

percentiles_bucket

桶百分位值

SELECT PERCENTILE(subtotal, 95)

2.3.3  流程控制类

聚合类型

功能

类似编程概念

bucket_selector

桶过滤

HAVING 子句

bucket_sort

桶排序

ORDER BY
bucket_script自定义脚本函数计算

min_bucket示例

在员工数最多的工种里,找出平均工资最低的工种

# 平均工资最低的工种
POST employees/_search
{"size": 0,"aggs": {"jobs": {"terms": {"field": "job.keyword","size": 10},"aggs": {"avg_salary": {"avg": {"field": "salary"}}}},"min_salary_by_job":{   "min_bucket": {    "buckets_path": "jobs>avg_salary"  }}}
}
  • min_salary_by_job结果和jobs的聚合同级
  • min_bucket求之前结果的最小值
  • 通过bucket_path关键字指定路径

Stats示例

# 平均工资的统计分析
POST employees/_search
{"size": 0,"aggs": {"jobs": {"terms": {"field": "job.keyword","size": 10},"aggs": {"avg_salary": {"avg": {"field": "salary"}}}},"stats_salary_by_job":{"stats_bucket": {"buckets_path": "jobs>avg_salary"}}}
}

percentiles示例

# 平均工资的百分位数
POST employees/_search
{"size": 0,"aggs": {"jobs": {"terms": {"field": "job.keyword","size": 10},"aggs": {"avg_salary": {"avg": {"field": "salary"}}}},"percentiles_salary_by_job":{"percentiles_bucket": {"buckets_path": "jobs>avg_salary"}}}
}

Cumulative_sum示例

#Cumulative_sum   累计求和
POST employees/_search
{"size": 0,"aggs": {"age": {"histogram": {"field": "age","min_doc_count": 0,"interval": 1},"aggs": {"avg_salary": {"avg": {"field": "salary"}},"cumulative_salary":{"cumulative_sum": {"buckets_path": "avg_salary"}}}}}
}

3. 聚合的作用范围

ES聚合分析的默认作用范围是query的查询结果集,同时ES还支持以下方式改变聚合的作用范围:
  • Filter
  • Post Filter
  • Global

示例

#Query
POST employees/_search
{"size": 0,"query": {"range": {"age": {"gte": 20}}},"aggs": {"jobs": {"terms": {"field":"job.keyword"}}}
}#Filter
POST employees/_search
{"size": 0,"aggs": {"older_person": {"filter":{"range":{"age":{"from":35}}},"aggs":{"jobs":{"terms": {"field":"job.keyword"}}}},"all_jobs": {"terms": {"field":"job.keyword"}}}
}#Post field. 一条语句,找出所有的job类型。还能找到聚合后符合条件的结果
POST employees/_search
{"aggs": {"jobs": {"terms": {"field": "job.keyword"}}},"post_filter": {"match": {"job.keyword": "Dev Manager"}}
}#global 
# 使用global聚合来计算所有匹配查询的文档(即所有年龄大于或等于40岁的员工)的平均薪资。
#global聚合的特点是它会考虑查询范围内的所有文档,而不仅仅是某个特定分组或桶中的文档。
POST employees/_search
{"size": 0,"query": {"range": {"age": {"gte": 40}}},"aggs": {"jobs": {"terms": {"field":"job.keyword" }},"all":{"global":{},"aggs":{"salary_avg":{"avg":{"field":"salary"}}}}}
}

4. 聚合排序

指定order,按照count和key进行排序:
  • 默认情况,按照count降序排序
  • 指定size,就能返回相应的桶
#排序 order
#count and key
POST employees/_search
{"size": 0,"query": {"range": {"age": {"gte": 20}}},"aggs": {"jobs": {"terms": {"field":"job.keyword","order":[{"_count":"asc"},{"_key":"desc"}]}}}
}#排序 order
#count and key
POST employees/_search
{"size": 0,"aggs": {"jobs": {"terms": {"field":"job.keyword","order":[  {"avg_salary":"desc"}]},"aggs": {"avg_salary": {"avg": {"field":"salary"}}}}}
}#排序 order
#count and key
POST employees/_search
{"size": 0,"aggs": {"jobs": {"terms": {"field":"job.keyword","order":[  {"stats_salary.min":"desc"}]},"aggs": {"stats_salary": {"stats": {"field":"salary"}}}}}
}

5. ES聚合分析不精准原因分析

ElasticSearch在对海量数据进行聚合分析的时候会损失搜索的精准度来满足实时性的需求。

Terms聚合分析的执行流程:

不精准的原因: 数据分散到多个分片,聚合是每个分片的取 Top X,导致结果不精准。ES 可以不每个分片Top X,而是全量聚合,但势必这会有很大的性能问题。

思考:如何提高聚合精确度?
方案1:设置主分片为1
注意7.x版本已经默认为1。
适用场景:数据量小的小集群规模业务场景。
方案2:调大 shard_size 值
设置 shard_size 为比较大的值,官方推荐:size*1.5+10。shard_size 值越大,结果越趋近于精准聚合结果值。此外,还可以通过show_term_doc_count_error参数显示最差情况下的错误值,用于辅助确定 shard_size 大小。
  • size:是聚合结果的返回值,客户期望返回聚合排名前三,size值就是 3。
  • shard_size: 每个分片上聚合的数据条数。shard_size 原则上要大于等于 size
适用场景:数据量大、分片数多的集群业务场景。
测试: 使用kibana的测试数据
DELETE my_flights
PUT my_flights
{"settings": {"number_of_shards": 20},"mappings" : {"properties" : {"AvgTicketPrice" : {"type" : "float"},"Cancelled" : {"type" : "boolean"},"Carrier" : {"type" : "keyword"},"Dest" : {"type" : "keyword"},"DestAirportID" : {"type" : "keyword"},"DestCityName" : {"type" : "keyword"},"DestCountry" : {"type" : "keyword"},"DestLocation" : {"type" : "geo_point"},"DestRegion" : {"type" : "keyword"},"DestWeather" : {"type" : "keyword"},"DistanceKilometers" : {"type" : "float"},"DistanceMiles" : {"type" : "float"},"FlightDelay" : {"type" : "boolean"},"FlightDelayMin" : {"type" : "integer"},"FlightDelayType" : {"type" : "keyword"},"FlightNum" : {"type" : "keyword"},"FlightTimeHour" : {"type" : "keyword"},"FlightTimeMin" : {"type" : "float"},"Origin" : {"type" : "keyword"},"OriginAirportID" : {"type" : "keyword"},"OriginCityName" : {"type" : "keyword"},"OriginCountry" : {"type" : "keyword"},"OriginLocation" : {"type" : "geo_point"},"OriginRegion" : {"type" : "keyword"},"OriginWeather" : {"type" : "keyword"},"dayOfWeek" : {"type" : "integer"},"timestamp" : {"type" : "date"}}}
}POST _reindex
{"source": {"index": "kibana_sample_data_flights"},"dest": {"index": "my_flights"}
}GET my_flights/_count
GET kibana_sample_data_flights/_search
{"size": 0,"aggs": {"weather": {"terms": {"field":"OriginWeather","size":5,"show_term_doc_count_error":true}}}
}GET my_flights/_search
{"size": 0,"aggs": {"weather": {"terms": {"field":"OriginWeather","size":5,"shard_size":10,"show_term_doc_count_error":true}}}
}
在Terms Aggregation的返回中有两个特殊的数值:
  • doc_count_error_upper_bound : 被遗漏的term 分桶,包含的文档,有可能的最大值
  • sum_other_doc_count: 除了返回结果 bucket的terms以外,其他 terms 的文档总数(总数-返回的总数

方案3:将size设置为全量值,来解决精度问题

将size设置为2的32次方减去1也就是分片支持的最大值,来解决精度问题。

原因:1.x版本,size等于 0 代表全部,高版本取消 0 值,所以设置了最大值(大于业务的全量值)。

全量带来的弊端就是:如果分片数据量极大,这样做会耗费巨大的CPU 资源来排序,而且可能会阻塞网络。

适用场景:对聚合精准度要求极高的业务场景,由于性能问题,不推荐使用。

方案4:使用Clickhouse/ Spark 进行精准聚合

适用场景:数据量非常大、聚合精度要求高、响应速度快的业务场景。

6. Elasticsearch 聚合性能优化

插入数据时对索引进行预排序

  • Index sorting (索引排序)可用于在插入时对索引进行预排序,而不是在查询时再对索引进行排序,这将提高范围查询(range query)和排序操作的性能。
  • 在 Elasticsearch 中创建新索引时,可以配置如何对每个分片内的段进行排序。
  • 这是 Elasticsearch 6.X 之后版本才有的特性。

PUT /my_index
{"settings": {"index":{"sort.field": "create_time","sort.order": "desc"}},"mappings": {"properties": {"create_time":{"type": "date"}}}
}

注意:预排序将增加 Elasticsearch 写入的成本。在某些用户特定场景下,开启索引预排序会导致大约 40%-50% 的写性能下降。也就是说,如果用户场景更关注写性能的业务,开启索引预排序不是一个很好的选择。

使用节点查询缓存

节点查询缓存(Node query cache)可用于有效缓存过滤器(filter)操作的结果。如果多次执行同一 filter 操作,这将很有效,但是即便更改过滤器中的某一个值,也将意味着需要计算新的过滤器结果。

你可以执行一个带有过滤查询的搜索请求,Elasticsearch将自动尝试使用节点查询缓存来优化性能。例如,如果你想缓存一个基于特定字段值的过滤查询,你可以发送如下的HTTP请求:

GET /your_index/_search
{"query": {"bool": {"filter": {"term": {"your_field": "your_value"}}}}
}

使用分片请求缓存

聚合语句中,设置:size:0,就会使用分片请求缓存缓存结果。size = 0 的含义是:只返回聚合结果,不返回查询结果。

GET /es_db/_search
{"size": 0,"aggs": {"remark_agg": {"terms": {"field": "remark.keyword"}}}
}

拆分聚合,使聚合并行化

Elasticsearch 查询条件中同时有多个条件聚合,默认情况下聚合不是并行运行的。当为每个聚合提供自己的查询并执行 msearch 时,性能会有显著提升。因此,在 CPU 资源不是瓶颈的前提下,如果想缩短响应时间,可以将多个聚合拆分为多个查询,借助:msearch 实现并行聚合。

#常规的多条件聚合实现
GET /employees/_search
{"size": 0,"aggs": {"job_agg": {"terms": {"field": "job.keyword"}},"max_salary":{"max": {"field": "salary"}}}
}
# msearch 拆分多个语句的聚合实现
GET _msearch
{"index":"employees"}
{"size":0,"aggs":{"job_agg":{"terms":{"field": "job.keyword"}}}}
{"index":"employees"}
{"size":0,"aggs":{"max_salary":{"max":{"field": "salary"}}}}

http://www.xdnf.cn/news/1176733.html

相关文章:

  • 使用phpstudy极简快速安装mysql
  • Java 大视界 -- Java 大数据在智能家居能源管理与节能优化中的深度应用(361)
  • API安全监测工具:数字经济的免疫哨兵
  • 五、Vue项目开发流程
  • LeetCode 2563.统计公平数对的数目
  • Effective Python 第16条:用get处理字典缺失键,避免in与KeyError的陷阱
  • 【低空经济之无人集群】
  • runc源码解读(一)——runc create
  • C++右值引用与移动语义详解
  • QML 模型
  • git更新内核补丁完整指南
  • Android LiveData 全面解析:原理、使用与最佳实践
  • 【智能协同云图库】智能协同云图库第六弹:空间模块开发
  • 飞腾D3000麒麟信安系统下配置intel I210 MAC
  • Spring AI - 函数调用演示:AI算术运算助手
  • 复盘—MySQL触发器实现监听数据表值的变化,对其他数据表做更新
  • act_hi_taskinst表历史任务记录不同步,无数据
  • 边缘智能体:轻量化部署与离线运行
  • 三维手眼标定
  • 深度分析Java内存结构
  • Hexo - 免费搭建个人博客01 - 安装软件工具
  • IAR Embedded Workbench for ARM 8.1 安装教程
  • Web开发基础与RESTful API设计实践指南
  • 面试实战,问题七,Object类中包含哪些常用方法及其作用,怎么回答
  • python---元组(Tuple)
  • 嵌入式开发学习———Linux环境下数据结构学习(二)
  • M3066ANL网络变压器,常用于NEC方案机顶盒等网络设备M3066AN实现网络信号的稳定传输与电气隔离保护
  • 暑期自学嵌入式——Day06(C语言阶段)
  • 音视频学习(四十三):H264无损压缩
  • opencv学习(图像处理)