赞
踩
官网:Aggregations | Elasticsearch Guide [8.13] | Elastic
Elasticsearch除搜索以外,提供了针对ES 数据进行统计分析的功能。聚合(aggregations)可以让我们极其方便的实现对数据的统计、分析、运算。
基本语法
聚合查询的语法结构与其他查询相似,通常包含以下部分:
- GET <index_name>/_search
- {
- "aggs": {
- "<aggs_name>": { // 聚合名称需要自己定义
- "<agg_type>": {
- "field": "<field_name>"
- }
- }
- }
- }
- SELECT size COUNT(*) FROM products GROUP BY size
- #bucket聚合的DSL类比实现:
- {
- "aggs": {
- "by_size": {
- "terms": {
- "field": "size"
- }
- }
- }
- SELECT MIN(price), MAX(price) FROM products
- #Metric聚合的DSL类比实现:
- {
- "aggs":{
- "avg_price":{
- "avg":{
- "field":"price"
- }
- }
- }
- }
示例数据:
- DELETE /member
- #创建索引库
- PUT /member
- {
- "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 /member/_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}
查询最低最高和平均工资:
- POST /member/_search
- {
- "size": 0,
- "aggs": {
- "max_salary": {
- "max": {
- "field": "salary"
- }
- },
- "min_salary": {
- "min": {
- "field": "salary"
- }
- },
- "avg_salary": {
- "avg": {
- "field": "salary"
- }
- }
- }
- }
对salary进行统计:
- POST /member/_search
- {
- "size": 0,
- "aggs": {
- "stats_salary": {
- "stats": {
- "field":"salary"
- }
- }
- }
- }
cardinate对搜索结果去重:
- POST /member/_search
- {
- "size": 0,
- "aggs": {
- "cardinate": {
- "cardinality": {
- "field": "job.keyword"
- }
- }
- }
- }
按照一定的规则,将文档分配到不同的桶中,从而达到分类的目的。ES提供的一些常见的 Bucket Aggregation。
桶聚合可以用于各种场景,例如:
获取job的分类信息
- GET /member/_search
- {
- "size": 0,
- "aggs": {
- "jobs": {
- "terms": {
- "field":"job.keyword"
- }
- }
- }
- }
聚合可配置属性有:
默认情况下,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照_count降序排序。我们可以指定order属性,自定义聚合的排序方式:
- GET /member/_search
- {
- "size": 0,
- "aggs": {
- "jobs": {
- "terms": {
- "field":"job.keyword",
- "size": 10,
- "order": {
- "_count": "asc"
- }
- }
- }
- }
- }
限定聚合范围
- GET /member/_search
- {
- "query": {
- "range": {
- "salary": {
- "gte": 20000
- }
- }
- },
- "size": 0,
- "aggs": {
- "jobs": {
- "terms": {
- "field":"job.keyword",
- "size": 10,
- "order": {
- "_count": "asc"
- }
- }
- }
- }
- }
注意:对 Text 字段进行 terms 聚合查询,会失败抛出异常
解决:对 Text 字段打开 fielddata,支持terms aggregation
- PUT /member/_mapping
- {
- "properties" : {
- "job":{
- "type": "text",
- "fielddata": true
- }
- }
- }
-
- # 对 Text 字段进行分词,分词后的terms
- POST /member/_search
- {
- "size": 0,
- "aggs": {
- "jobs": {
- "terms": {
- "field":"job"
- }
- }
- }
- }
对job.keyword 和 job 进行 terms 聚合,分桶的总数并不一样
- POST /member/_search
- {
- "size": 0,
- "aggs": {
- "cardinate": {
- "cardinality": {
- "field": "job"
- }
- }
- }
- }
Range & Histogram
Range 示例:按照工资的 Range 分桶
- #Salary Range分桶,可以自己定义 key
- POST member/_search
- {
- "size": 0,
- "aggs": {
- "salary_range": {
- "range": {
- "field":"salary",
- "ranges":[
- {
- "to":10000
- },
- {
- "from":10000,
- "to":20000
- },
- {
- "key":">20000",
- "from":20000
- }
- ]
- }
- }
- }
- }
Histogram示例:按照工资的间隔分桶
- #工资0到5万,以 2000一个区间进行分桶
- POST member/_search
- {
- "size": 0,
- "aggs": {
- "salary_histrogram": {
- "histogram": {
- "field":"salary",
- "interval":2000,
- "extended_bounds":{
- "min":0,
- "max":50000
- }
- }
- }
- }
- }
top_hits应用场景: 当获取分桶后,桶内最匹配的顶部文档列表
- # 指定size,不同工种中,年纪最大的3个员工的具体信息
- POST /member/_search
- {
- "size": 0,
- "aggs": {
- "jobs": {
- "terms": {
- "field":"job.keyword"
- },
- "aggs":{
- "old_member":{
- "top_hits":{
- "size":3,
- "sort":[
- {
- "age":{
- "order":"desc"
- }
- }
- ]
- }
- }
- }
- }
- }
- }
嵌套聚合示例
- # 嵌套聚合1,按照工作类型分桶,并统计工资信息
- POST member/_search
- {
- "size": 0,
- "aggs": {
- "Job_salary_stats": {
- "terms": {
- "field": "job.keyword"
- },
- "aggs": {
- "salary": {
- "stats": {
- "field": "salary"
- }
- }
- }
- }
- }
- }
-
- # 多次嵌套。根据工作类型分桶,然后按照性别分桶,计算工资的统计信息
- POST member/_search
- {
- "size": 0,
- "aggs": {
- "Job_gender_stats": {
- "terms": {
- "field": "job.keyword"
- },
- "aggs": {
- "gender_stats": {
- "terms": {
- "field": "gender"
- },
- "aggs": {
- "salary_stats": {
- "stats": {
- "field": "salary"
- }
- }
- }
- }
- }
- }
- }
- }
支持对聚合分析的结果,再次进行聚合分析。
Pipeline 的分析结果会输出到原结果中,根据位置的不同,分为两类:
min_bucket示例
在员工数最多的工种里,找出平均工资最低的工种
- # 平均工资最低的工种
- POST member/_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"
- }
- }
- }
- }
Stats示例
- # 平均工资的统计分析
- POST member/_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示例
Percentile ranks aggregation | Elasticsearch Guide [7.17] | Elastic
- # 平均工资的百分位数
- POST member/_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 member/_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"
- }
- }
- }
- }
- }
- }
ES聚合分析的默认作用范围是query的查询结果集,同时ES还支持以下方式改变聚合的作用范围:
- POST member/_search
- {
- "size": 0,
- "query": {
- "range": {
- "age": {
- "gte": 20
- }
- }
- },
- "aggs": {
- "jobs": {
- "terms": {
- "field":"job.keyword"
-
- }
- }
- }
- }
-
- #Filter
- POST member/_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 member/_search
- {
- "aggs": {
- "jobs": {
- "terms": {
- "field": "job.keyword"
- }
- }
- },
- "post_filter": {
- "match": {
- "job.keyword": "Dev Manager"
- }
- }
- }
-
-
- #global
- POST member/_search
- {
- "size": 0,
- "query": {
- "range": {
- "age": {
- "gte": 40
- }
- }
- },
- "aggs": {
- "jobs": {
- "terms": {
- "field":"job.keyword"
-
- }
- },
-
- "all":{
- "global":{},
- "aggs":{
- "salary_avg":{
- "avg":{
- "field":"salary"
- }
- }
- }
- }
- }
- }
指定order,按照count和key进行排序:
指定size,就能返回相应的桶
- #排序 order
- #count and key
- POST member/_search
- {
- "size": 0,
- "query": {
- "range": {
- "age": {
- "gte": 20
- }
- }
- },
- "aggs": {
- "jobs": {
- "terms": {
- "field":"job.keyword",
- "order":[
- {"_count":"asc"},
- {"_key":"desc"}
- ]
-
- }
- }
- }
- }
-
-
- #排序 order
- #count and key
- POST member/_search
- {
- "size": 0,
- "aggs": {
- "jobs": {
- "terms": {
- "field":"job.keyword",
- "order":[ {
- "avg_salary":"desc"
- }]
-
-
- },
- "aggs": {
- "avg_salary": {
- "avg": {
- "field":"salary"
- }
- }
- }
- }
- }
- }
-
-
- #排序 order
- #count and key
- POST member/_search
- {
- "size": 0,
- "aggs": {
- "jobs": {
- "terms": {
- "field":"job.keyword",
- "order":[ {
- "stats_salary.min":"desc"
- }]
-
-
- },
- "aggs": {
- "stats_salary": {
- "stats": {
- "field":"salary"
- }
- }
- }
- }
- }
- }
ElasticSearch在对海量数据进行聚合分析的时候会损失搜索的精准度来满足实时性的需求。
不精准的原因: 数据分散到多个分片,聚合是每个分片的取 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 大小。
适用场景:数据量大、分片数多的集群业务场景。
在Terms Aggregation的返回中有两个特殊的数值:
方案3:将size设置为全量值,来解决精度问题
将size设置为2的32次方减去1也就是分片支持的最大值,来解决精度问题。
原因:1.x版本,size等于 0 代表全部,高版本取消 0 值,所以设置了最大值(大于业务的全量值)。
全量带来的弊端就是:如果分片数据量极大,这样做会耗费巨大的CPU 资源来排序,而且可能会阻塞网络。
适用场景:对聚合精准度要求极高的业务场景,由于性能问题,不推荐使用。
方案4:使用Clickhouse/ Spark 进行精准聚合
适用场景:数据量非常大、聚合精度要求高、响应速度快的业务场景。
eager_global_ordinals | Elasticsearch Guide [7.17] | Elastic
本质:以空间换时间
为了支持聚合和其他需要按文档查找字段值的操作,Elasticsearch 使用一种称为 doc values的数据结构。基于术语的字段类型(例如)使用序数keyword映射存储其 doc values 以获得更紧凑的表示。此映射的工作原理是根据每个术语的字典顺序为其分配一个增量整数或序数 。字段的 doc values 仅存储每个文档的序数而不是原始术语,并使用单独的查找结构在序数和术语之间进行转换。
在聚合期间使用序数可以大大提高性能。例如,terms聚合仅依靠序数将文档收集到分片级别的存储桶中,然后在跨分片组合结果时将序数转换回其原始术语值。
每个索引段都定义自己的序数映射,但聚合会收集整个分片的数据。因此,为了能够使用序数进行聚合等分片级操作,Elasticsearch 创建了一个统一的映射,称为 全局序数。全局序数映射建立在段序数之上,通过为每个段维护从全局序数到本地序数的映射来工作。
适用场景:高基数聚合 。高基数聚合场景中的高基数含义:一个字段包含很大比例的唯一值。
global ordinals 中文翻译成全局序号,是一种数据结构,应用场景如下:
global ordinals 使用一个数值代表字段中的字符串值,然后为每一个数值分配一个 bucket(分桶)。
global ordinals 的本质是:启用 eager_global_ordinals 时,会在刷新(refresh)分片时构建全局序号。这将构建全局序号的成本从搜索阶段转移到了数据索引化(写入)阶段。
创建索引的同时开启:eager_global_ordinals。
- PUT my-index-000001/_mapping
- {
- "properties": {
- "tags": {
- "type": "keyword",
- "eager_global_ordinals": true
- }
- }
- }
注意:开启 eager_global_ordinals 会影响写入性能,因为每次刷新时都会创建新的全局序号。为了最大程度地减少由于频繁刷新建立全局序号而导致的额外开销,请调大刷新间隔 refresh_interval。
- PUT my-index-000001/_settings
- {
- "index": {
- "refresh_interval": "30s"
- }
Index Sorting | Elasticsearch Guide [7.17] | Elastic
- PUT my-index-000001
- {
- "settings": {
- "index": {
- "sort.field": [ "username", "date" ],
- "sort.order": [ "asc", "desc" ]
- }
- },
- "mappings": {
- "properties": {
- "username": {
- "type": "keyword",
- "doc_values": true
- },
- "date": {
- "type": "date"
- }
- }
- }
- }
注意:预排序将增加 Elasticsearch 写入的成本。在某些用户特定场景下,开启索引预排序会导致大约 40%-50% 的写性能下降。也就是说,如果用户场景更关注写性能的业务,开启索引预排序不是一个很好的选择。
Node query cache settings | Elasticsearch Guide [7.17] | Elastic
节点查询缓存(Node query cache)可用于有效缓存过滤器(filter)操作的结果。如果多次执行同一 filter 操作,这将很有效,但是即便更改过滤器中的某一个值,也将意味着需要计算新的过滤器结果。
例如,由于 “now” 值一直在变化,因此无法缓存在过滤器上下文中使用 “now” 的查询。
那怎么使用缓存呢?通过在 now 字段上应用 datemath 格式将其四舍五入到最接近的分钟/小时等,可以使此类请求更具可缓存性,以便可以对筛选结果进行缓存。
- PUT /my_index/_doc/1
- {
- "create_time":"2022-05-11T16:30:55.328Z"
- }
-
- #下面的示例无法使用缓存
- GET /my_index/_search
- {
- "query":{
- "constant_score": {
- "filter": {
- "range": {
- "create_time": {
- "gte": "now-1h",
- "lte": "now"
- }
- }
- }
- }
- }
- }
-
- # 下面的示例就可以使用节点查询缓存。
- GET /my_index/_search
- {
- "query":{
- "constant_score": {
- "filter": {
- "range": {
- "create_time": {
- "gte": "now-1h/m",
- "lte": "now/m"
- }
- }
- }
- }
- }
- }
上述示例中的“now-1h/m” 就是 datemath 的格式。
如果当前时间 now 是:16:31:29,那么range query 将匹配 my_date 介于:15:31:00 和 15:31:59 之间的时间数据。同理,聚合的前半部分 query 中如果有基于时间查询,或者后半部分 aggs 部分中有基于时间聚合的,建议都使用 datemath 方式做缓存处理以优化性能。
聚合语句中,设置:size:0,就会使用分片请求缓存缓存结果。size = 0 的含义是:只返回聚合结果,不返回查询结果。
- GET /es_db/_search
- {
- "size": 0,
- "aggs": {
- "remark_agg": {
- "terms": {
- "field": "remark.keyword"
- }
- }
- }
- }
Multi search API | Elasticsearch Guide [7.17] | Elastic
Elasticsearch 查询条件中同时有多个条件聚合,默认情况下聚合不是并行运行的。当为每个聚合提供自己的查询并执行 msearch 时,性能会有显著提升。因此,在 CPU 资源不是瓶颈的前提下,如果想缩短响应时间,可以将多个聚合拆分为多个查询,借助:msearch 实现并行聚合。
- GET my-index-000001/_msearch
- { }
- {"query" : {"match" : { "message": "this is a test"}}}
- {"index": "my-index-000002"}
- {"query" : {"match_all" : {}}}
-
- #常规的多条件聚合实现
- GET /member/_search
- {
- "size": 0,
- "aggs": {
- "job_agg": {
- "terms": {
- "field": "job.keyword"
- }
- },
- "max_salary":{
- "max": {
- "field": "salary"
- }
- }
- }
- }
- # msearch 拆分多个语句的聚合实现
- GET _msearch
- {"index":"member"}
- {"size":0,"aggs":{"job_agg":{"terms":{"field": "job.keyword"}}}}
- {"index":"member"}
- {"size":0,"aggs":{"max_salary":{"max":{"field": "salary"}}}}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。