大家好我是迷途,一个在互联网行业,摸爬滚打的学子。热爱学习,热爱代码,热爱技术。热爱互联网的一切。再也不怕elasticsearch系列,帅途会慢慢由浅入深,为大家剖析一遍,各位大佬请放心,虽然这个系列帅途有时候更新的有点慢,但是绝对不会烂尾!
文章目录
- 前言
- 正文
- 1、聚合的一些基本概念
- 2、环境准备
- 3、数据准备
- 4、Bucketing聚合
- 5、Metric聚合
- 7、Matrix聚合
- 8、Pipeline聚合
- 总结
前言
本文主要讲解es中的聚合查询,一般来说在es中,复杂查询,我们不会使用太多,但是在一些特定的场景下,我们就可能需要使用到。例如我们在使用es做一个大型的日志分析系统的时候(ELK),那么我们对数据的一些统计聚合等都是需要我们进行操作的,工欲善其事必先利其器,所以今天帅途就和大家一起来学习es的聚合查询吧。
正文
1、聚合的一些基本概念
- aggs关键字
聚合关键字,优先级低,一个aggs里面可以有多个聚合,每个聚合是相互独立的,如果本次查询中有query无论确query位置在何处,我们聚合查询的都是query之后的参数
- 例:查询出性别为女的数据,然后求他们年龄的平均值(query优先级高于aggs)
{
"aggs" : { "avg_grade" : {"avg" : { "field" : "age" }}},
"query": {"term": { "gender": "女" }}
}
- ES的4种聚合
- 参考官方文档:elasticsearch官方文档
- Bucketing
桶,es的4种聚合类型之一,将满足同一种条件的数据放在一个桶中,类似于mysql中的sql中的group by 语句。与指标聚合不同,桶聚合可以包含子聚合,例如张三(数据)在地球(桶)中,同时他也属于地球之内的中国(子桶)中
- Metric
指标(度量),指标聚合,类似于sql中的avg、sum、max等,在metric中主要有两种聚合分别是单值聚合(single-value numeric)和多值聚合(multi-value numeric)。顾名思义,单值聚合就是在本次聚合中只有一个值而多值聚合则是在聚合中有多个值
- Matrix
矩阵,矩阵聚合,可以对多个字段进行聚合,生成数据矩阵。类似于分别使用Metric的多值聚合对数据进行聚合,目前矩阵聚合还处于测试阶段,可能后期会完全更改或者删除。并且不支持脚本
- Pipeline
管道,管道聚合,它作用于其他聚合的结果之上,主要分为两种聚合Parent(父母)、Sibling(兄弟),管道聚合支持链路但是不能在管道聚合中在写子聚合。
Parent(父母):对父聚合处理后的数据进行处理,不会生成新的桶。在父聚合之内。
Sibling(兄弟):同级聚合,对当前同一级的聚合数据进行处理,生成新桶,必须处于多聚合之内。
2、环境准备
PostMan、ES7.X、ElasticSearch-Head
3、数据准备
这里帅途已经预先添加好了数据,我们使用head查看,数据内容与格式。有些小伙伴可能不太了解head与kibana的区别,head与kibana都是es的数据可视化工具,在日常的开发当中,可以根据我们的需求进行选择,帅途一般索引管理等用head,数据查询之类使用kibana。
我们在customer索引下准备了10条数据,他们的类型为userType,各位小伙伴注意,在es7.0之后一个index只能对应一个type,在后面可能会完全弃用。
- 准备数据
4、Bucketing聚合
- 根据性别进行分组
http://112.74.48.31:9200/customer/_search?size=0 ---GET
{
"aggs":{
"metoo_group":{ --自定义聚合名称
"terms":{ -- 匹配关键字
"field":"gender" --gener字段
}
}
}
}
size=0表示我们对这个查询本身查询出来的数据不感兴趣,只查看聚合后的数据
在我们执行上述代码之后,发现一个问题:Fielddata is disabled on text fields by default. Set fielddata=true on [gender] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead.
众所周知es底层是基于lucene,默认使用倒排索引,来提升我们的查询效率,而在我们对text字段进行聚排序(sort),统计(aggs)的时候,就需要使用到FieldData(正排索引),在es5.0之后这个默认为关闭状态,因为正排索引使用的是 doc values 将数据缓存在内存当中,典型的使用空间换时间,当我们内存不够时,os会为我们将数据写入到磁盘中,甚至在我们海量的情况下会出现OOM等异常。
正排索引:将文档加载到内存(磁盘)中,从文档 Id 到文档内容、单词的关联关系,也就是说可以通过 Id获取到文档的内容。
倒排索引:将文档内容进行分词,每个分词对应文档id,如果一个分词对应了多个文档id,则生成索引矩阵。帅途这里发现一篇非常有意思的文章,对倒排索引和正排索引不太理解的同学可以去看看,参考:终于有人把Elasticsearch原理讲透了!
接下来我们手动修改index中gender字段的FieldData属性,开启正排索引,并进行分组查询。
http://112.74.48.31:9200/customer/_mapping -POST
{
"properties":{
"gender":{ -- 字段名称
"type":"text", -- 字段类型 修改报错
"fielddata":true --开启正排索引
}
}
}
修改之后,我们再次调用聚合函数进行分组查询,查看返回值
{
"took": 46,
"timed_out": false,
"_shards": { -- 分片,不了解的同学可以去看帅途之前的博客
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"metoo_group": { -- 我们自定义的桶名
"doc_count_error_upper_bound": 0, --被遗漏的terms 可能的最大值
"sum_other_doc_count": 0, --除了返回给用户的terms,剩下的terms总数
"buckets": [
{
"key": "男",
"doc_count": 6
},
{
"key": "女",
"doc_count": 4
}
]
}
}
}
- 根据性别进行分组,分别求出平均年龄
http://112.74.48.31:9200/customer/_search?size=0 ---GET
{
"aggs":{
"metoo_group":{
"terms":{
"field":"gender"
},
"aggs":{ --在metoo_group的基础之上创建子聚合,计算平均年龄
"sum_group":{ --自定义的聚合名称
"avg":{ --对age字段进行求平均数
"field":"age"
}
}
}
}
}
}
查看返回的聚合结果
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"metoo_group": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "男",
"doc_count": 6,
"sum_group": { -- 子聚合返回值
"value": 32.166666666666664
}
},
{
"key": "女",
"doc_count": 4,
"sum_group": {
"value": 26.75
}
}
]
}
}
}
5、Metric聚合
- 求所有人的总年龄(sum聚合)
http://112.74.48.31:9200/customer/_search?size=0 -GET
{
"aggs" : {
"metoo_sum": {
"sum": { --求和关键字
"field": "age"
}
}
}
}
返回值解析
{
"took": 14,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": { -- 索引的文档,由于我们设置的size=0,所以这里不展示
"value": 10,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": { -- 聚合桶返回值
"metoo_sum": { --我们自定义的桶名
"value": 300.0
}
}
}
单值聚合基本上都大同小异,帅途这里把格式列出来之后,一些常用的聚合参数直接替换即可,avg(平均)、sum(求和)、max*(最大值)、min(最小值)、cardinality(唯一)等。
- 加权求平均值
http://112.74.48.31:9200/customer/_search?size=0 -GET
{
"aggs" : {
"metoo_weighted": {
"weighted_avg": {
"value": {
"field": "age"
},
"weight": {
"field": "grade"
}
}
}
}
}
在一些特定场景我们可以能需要用到加权平均,例如我们在统计全国的平均成绩,每个省份总分都是一样的,但是由于每个省份的试卷难度不同,可能上海的试卷要难一点,在现有分数上要加上当前成绩的0.2(权重),北京的试卷简单一点在当前成绩上减去0.2,那么我们就需要使用到:weighted_avg,加权平均。(这里帅途使用age为平均数据,grade为权重数据,计算age加权之后的平均值)
查看返回值
{
"took": 14,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"metoo_weighted": { -- 加权平均之后的值,加权平均虽然我们在条件桶中写了两个参数,但是返回值是一个值。
"value": 34.973333333333336
}
}
}
- 多值聚合
http://112.74.48.31:9200/customer/_search?size=0 -GET
{
"aggs" : {
"metoo_stats" : {
"stats" : { -- 统计聚合,查询多种聚合值,返回值在一个桶中
"field" : "age"
}
}
}
}
查询统计聚合返回值解析
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"metoo_stats": { --统计聚合返回值,包含基础查询返回值
"count": 10,
"min": 18.0,
"max": 58.0,
"avg": 30.0,
"sum": 300.0
}
}
}
- 拓展统计
http://112.74.48.31:9200/customer/_search?size=0 -GET
{
"aggs" : {
"metoo_extended_stats" : { --对统计集合的拓展,比统计聚合返回值更全面
"extended_stats" : {
"field" : "age"
}
}
}
}
7、Matrix聚合
- 矩阵统计
http://112.74.48.31:9200/customer/_search?size=0 -GET
{
"aggs": {
"metoo_matrix_stats": {
"matrix_stats": { -- 矩阵统计关键字
"fields": ["age", "grade"] --我们对age和grade两个字段进行统计
}
}
}
}
矩阵统计目前处于测试阶段,在后期的发型版本中可能被完全修改或者删除,其实矩阵统计就是在原来metric统计聚合的基础上增加了多字段进行统计。下面附上返回值视图。第一行为字段grade的拓展统计数据,第二行为字段age的拓展统计数据。
更具上面我们可以看出,返回值结果生成了一个数据矩阵,下面我们查看json返回值
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"metoo_matrix_stats": {
"doc_count": 10, -- 检索文档数量
"fields": [ -- 统计字段参数数组
{ -- 每个字段的统计数据
"name": "grade",
"count": 10,
"mean": 68.30000000000001,
"variance": 939.5666666666667,
"skewness": -1.0970876343301157,
"kurtosis": 3.4328633556223305,
"covariance": {
"grade": 939.5666666666667,
"age": -40.088888888888896
},
"correlation": {
"grade": 1.0,
"age": -0.09900937761061816
}
},
{
"name": "age",
"count": 10,
"mean": 29.600000000000005,
"variance": 174.4888888888889,
"skewness": 0.9785339698632999,
"kurtosis": 2.9787389943709,
"covariance": {
"grade": -40.088888888888896,
"age": 174.4888888888889
},
"correlation": {
"grade": -0.09900937761061816,
"age": 1.0
}
}
]
}
}
}
8、Pipeline聚合
- Parent聚合
Bucket Sort Aggregation聚合,父级管道聚合,对父级管道的输出结果进行分组,同时也可以排序
{
"size": 0,
"aggs" : {
"one_aggs" : { --第一个聚合根据性别分组
"terms" : {
"field" : "gender"
},
"aggs": {
"tow_aggs": { --第二个聚合,分组之后求出男女年龄平均值
"avg": {
"field": "age"
}
},
"three_aggs": { --第三个聚合将第二聚合的输出作为参数,进行排序。
"bucket_sort": {
"sort": [
{"tow_aggs": {"order": "asc"}} --正序
],
"from":0, --- 分页参数,从第一页开始,每页展示三条数据,如果父聚合输出过多可以从这里截断
"size": 3 --分页默认从0开始
}
}
}
}
}
}
参数值解析
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"one_aggs": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [ -- 父级管道,不会生成新桶,在父桶基础上加强数据
{
"key": "女",
"doc_count": 5,
"tow_aggs": {
"value": 25.0
}
},
{
"key": "男",
"doc_count": 5,
"tow_aggs": {
"value": 34.2
}
}
]
}
}
}
- Sibling聚合
Avg Bucket Aggregation聚合,同级管道聚合,主要用于指标聚合,求平均值
{
"size": 0,
"aggs": {
"one_aggs": { -- 第一个聚合,根据性别分组
"terms": {
"field": "gender"
},
"aggs": { -- 第二个聚合,分组之后分别求出男和女的总年龄
"tow_aggs": {
"sum": {
"field": "age"
}
}
}
},
"three_aggs": { --第三个聚合 Avg Bucket Aggregation求出男和女总年龄的平均值
"avg_bucket": {
"buckets_path": "one_aggs>tow_aggs"
}
}
}
}
返回值解析
{
"took": 92,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"one_aggs": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [ --前两个聚合的返回值
{
"key": "女",
"doc_count": 5,
"tow_aggs": {
"value": 125.0
}
},
{
"key": "男",
"doc_count": 5,
"tow_aggs": {
"value": 171.0
}
}
]
},
"three_aggs": { --Avg Bucket Aggregation 求出的男女总年龄平均值
"value": 148.0
}
}
}
总结
帅途这里只是列举部分常用的聚合和一些特定的聚合,如果小伙伴们想要了解更多聚合可以参考es官方文档,里面列举了非常多的聚合,能够满足各位小伙伴开发所需。我们也需要明确一些基本概念,这样在面试的时候,面试官问道才能对答如流。
- 1、es总共有4种类型聚合分别是:Bucketing(桶)、Metric(指标、度量)、Matrix(矩阵)、(Pipeline)管道
- 2、Bucketing桶聚合可以嵌套,大桶里面可以套小桶对大桶的数据再次筛选。可以嵌套多层
- 3、Metric指标聚合,指标聚合分为单值聚合与多值聚合,它里面是一些查询函数,类似于sql中的avg、sum等
- 4、Matrix(矩阵聚合),目前在官方文档中矩阵聚合只有一种聚合方式,matrix_stats,它相当于Metric聚合中stats统计聚合,将单字段变成了多字段。返回数据矩阵(目前处于测试阶段,未来可能完全更改或删除)
- 5、Pipeline管道聚合,管道聚合分为Parent父管道聚合,在父聚合的基础之上操作,返回值不会生成新桶,而是在原有桶内。Sibling兄弟管道聚合,对同级聚合进行操作,操作后的数据不会影响其他聚合的输出,会生成新的桶,使用兄弟管道聚合同级必须有两个或以上的聚合。
最后,希望本篇文章能对各位小伙伴有帮助,目前很多关于es的文章都是基于5.0或者更早之前写的。其实es5.0和7.0区别还是蛮大的不只是一些核心概念的弃用与删除,还有一些检索方面的优化与变动等。