基本概念
桶
简单来说就是满足特定条件的文档的集合。当聚合开始被执行,每个文档里面的值通过计算来决定符合哪个桶的条件。如果匹配到,文档将放入相应的桶并接着进行聚合操作。
指标
桶能让我们划分文档到有意义的集合,但是最终我们需要的是对这些桶内的文档进行一些指标的计算。分桶是一种达到目的的手段:它提供了一种给文档分组的方法来让我们可以计算感兴趣的指标。
大多数 指标 是简单的数学运算(例如最小值、平均值、最大值,还有汇总),这些是通过文档的值来计算。在实践中,指标能让你计算像平均薪资、最高出售价格、95%的查询延迟这样的数据。
桶和指标
聚合 是由桶和指标组成的。 聚合可能只有一个桶,可能只有一个指标,或者可能两个都有。也有可能有一些桶嵌套在其他桶里面。
聚合
聚合框架目的就是使搜索查询能够提供聚合数据。它是基于称为聚合的简单构建块,它可以帮助我们构建复杂的数据。Elasticsearch有不同类型的聚合,每种聚合都有自己的目的和最终的数据输出。为了更好地理解这些类型,通常将它们分为四种主要类型:
- 分桶聚合(Bucketing)
- 指标聚合(Metric)
- 矩阵聚合(Matrix)
- 管道聚合(Pipeline)
使用聚合的结构:
"aggregations" : {
"<aggregation_name>" : {
"<aggregation_type>" : {
<aggregation_body>
},
[, "meta" : { [<meta_data_body>] } ]
[, "aggregations" : { ..... } ]
}
[, "<aggregation_name_2>": {......} ]
}
指标聚合
该聚合是根据要聚合的文档提取出来字段值来进行指标计算。
平均值(avg)
单值指标聚合,用于计算从文档中提取的数值的平均值。这些值可以从文档中的数字字段中提取,也可以由脚本生成。
$ curl -XPOST "localhost:9200/bank/_search?pretty&size=0" -d'
{
"aggs": {
"avg_agg" : { "avg" : { "field": "balance" } }
}
}'
# 由脚本生成指标
$ curl -XPOST "localhost:9200/bank/_search?pretty&size=0" -d'
{
"aggs": {
"avg_agg" : {
"avg" : {
"script": {
"source": "doc.balance.value"
}
}
}
}
}'
求和(sum)
单指标聚合,对文档中字段的数值进行求和。
$ curl -XPOST "localhost:9200/bank/_search?pretty&size=0" -d'
{
"aggs": {
"sum_agg" : { "sum" : { "field": "balance" } }
}
}'
最大值(max)
单指标聚合,可返回从聚合文档中提取的数值中的最大值。
$ curl -XPOST "localhost:9200/bank/_search?pretty&size=0" -d'
{
"aggs": {
"max_agg" : { "max" : { "field": "balance" } }
}
}'
最小值(min)
单指标聚合,可返回从聚合文档中提取的数值中的最小值。
$ curl -XPOST "localhost:9200/bank/_search?pretty&size=0" -d'
{
"aggs": {
"min_agg" : { "min" : { "field": "balance" } }
}
}'
地理边界(geo_bounds)
用于计算包含一个字段geo_point值的边界框。
$ curl -XPOST "localhost:9200/restaurants/_search?pretty&size=0" -d'
{
"query": {
"match": { "name": "restaurant" }
},
"aggs": {
"geo_restaurant" : {
"geo_bounds" : {
"field": "location"
}
}
}
}'
地理中心(geo_centroid)
根据要聚合文档求出地理位置上的的中心点。
curl -XPOST "localhost:9200/restaurants/_search?pretty&size=0" -d'
{
"aggs": {
"geo_restaurant" : {
"geo_centroid" : {
"field": "location"
}
}
}
}'
百分比(perecentiles)
多指标聚合,该聚合针对从聚合文档中提取的数值计算一个或多个百分位数。假设日志数据包含接口响应时间,对管理员来说平均和中位数加载时间并无太大用处,最大值可能更有用。
curl -XPOST "localhost:9200/bank/_search?pretty&size=0" -d'
{
"aggs": {
"age_outlier" : {
"percentiles" : {
"field": "age",
}
}
}
}'
# 指定某个百分比范围
curl -XPOST "localhost:9200/bank/_search?pretty&size=0" -d'
{
"aggs": {
"age_outlier" : {
"percentiles" : {
"field": "age",
"percens": [90, 95, 99]
}
}
}
}'
# 指定TDigest compression
curl -XPOST "localhost:9200/bank/_search?pretty&size=0" -d'
{
"aggs": {
"age_outlier" : {
"percentiles" : {
"field": "age",
"tdigest":{
"compression": 200
}
}
}
}
}'
# 指定HDR Histogram
$ curl -XPOST "localhost:9200/bank/_search?pretty&size=0" -d'
{
"aggs": {
"age_outlier" : {
"percentiles" : {
"field": "age",
"hdr": {
"number_of_significant_value_digits": 5
}
}
}
}
}
}'
- 使用此指标时,需要牢记一些准则:
百分位的准确度与百分位的 极端程度 相关,也就是说 1 或 99 的百分位要比 50 百分位要准确。这只是数据结构内部机制的一种特性,但这是一个好的特性,因为多数人只关心极端的百分位。 - 对于数值集合较小的情况,百分位非常准确。如果数据集足够小,百分位可能 100% 精确。
- 随着桶里数值的增长,算法会开始对百分位进行估算。它能有效在准确度和内存节省之间做出权衡。 不准确的程度比较难以总结,因为它依赖于 聚合时数据的分布以及数据量的大小。
TDigest
TDigest 算法用节点近似计算百分比:节点越多,准确度越高(同时内存消耗也越大),这都与数据量成正比。compression
参数限制节点的最大数目为20 * compression
。
因此,通过增加压缩比值,可以以消耗更多内存为代价提高百分位数准确性。更大的压缩比值会使算法运行更慢,因为底层的树形数据结构的存储也会增长,也导致操作的代价更高。默认的压缩比值是100
HDR Histogram
高动态范围( HDR ) 直方图
HDR Histogram支持在可以配置的整数值范围内记录和分析采样数据值计数,在该范围内可以配置值精度。 值精度表示为值记录中的重要数字数,并提供值范围内的值量化行为和后续值分辨率。
例如在保持 0到 3,600之间的整数值的同时,可以配置直方图来跟踪观察到的整数值的计数,同时保持在该范围内的值精度为。 因此范围内的值量化不会大于 1/1000 ( 任何值的0.1 %) )。 这个示例直方图可用来跟踪和分析观测响应次数,范围为 1微秒到10微秒,同时保持value微秒的分辨率分辨率,分辨率为1 毫秒,分辨率为 1,000秒,分辨率达秒。 它的最大跟踪值( 1小时) 仍然保持 3.6秒( 或者更好)的分辨率。
百分比等级(percentile_ranks)
多指标聚合,该聚合根据从聚合文档中提取的数值计算出一个或多个百分数等级。
curl -XPOST "localhost:9200/bank/_search?pretty&size=0" -d'
{
"aggs": {
"age_outlier" : {
"percentile_ranks" : {
"field": "balance",
"values": [25000, 50000]
}
}
}
}'
基数聚合(cardinality)
单值指标聚合,用于计算不同值的近似计数。它提供一个字段的基数,即该字段的 distinct 或者 unique 值的数目。可以以如下SQL来帮助理解该聚合:
SELECT COUNT(DISTINCT age) FROM account
去重是一个很常见的操作,可以应用于很多的业务场景:
每天/每月/每年访问网站的独立IP有多少?
curl -XPOST "localhost:9200/bank/_search?pretty&size=0" -d'
{
"aggs": {
"name_count" : {
"cardinality" : {
"field": "firstname.keyword"
}
}
}
}'
precision_threshold接受 0–40,000 之间的数字,更大的值还是会被当作 40,000 来处理。
HyperLogLog
HyperLogLog 可以接受多个元素作为输入,并给出输入元素的基数估算值。基数:集合中不同元素的数量,比如 {'apple', 'banana', 'cherry', 'banana', 'apple'} 的基数就是 3 。
估算值:算法给出的基数并不是精确的,可能会比实际稍微多一些或者稍微少一些,但会控制在合理的范围之内
HyperLogLog 的优点是,即使输入元素的数量或者体积非常非常大,计算基数所需的空间总是固定的、并且是很小的。
bitmap存储1一亿个统计数据大概需要12M内存;而在HLL中,只需要不到1K内存就能做到;redis中实现的HyperLogLog,只需要12K内存,在标准误差0.81%的前提下,能够统计2^64个数据。
Top聚合(top_hits)
top_hits聚合可以有效地用于通过桶聚合器按某些字段对结果集进行分组。该聚合器主要用作子聚合,以便可以按存储分区汇总最匹配的文档。它有3个重要的参数:
- from 获取数据的偏移量
- size 每个桶要返回的最大数量
- sort 如何排序。默认情况下,按分数排序
curl -XPOST "localhost:9200/bank/_search?size=0&pretty=true" -d'
{
"aggs": {
"top_tags": {
"terms": {
"field": "lastname.keyword",
"size": 6
},
"aggs": {
"top_balance_hits": {
"top_hits": {
"sort": [
{
"balance": {
"order": "desc"
}
}
],
"_source": {
"includes": [ "firstname", "lastname", "age", "balance" ]
},
"size" : 1
}
}
}
}
}
}'
分桶聚合
桶聚合不像指标聚合那样计算字段的值,而是创建文档存储桶。每个桶都与一个标准/查询(取决于聚合类型)相关联,该标准/查询确定文档是否“落入”其中,存储桶有效地定义了文档集。除了存储桶本身之外,桶聚合还计算并返回“落入”每个存储桶的文档数量。
过滤聚合(filter)
返回当前文档集中与指定过滤器匹配的文档的单个桶。通常,用于将当前聚合的文档再进一步缩小到一组特定的文档。
curl -XPOST "localhost:9200/bank/_search?size=0&pretty=true" -d'
{
"aggs": {
"gender_filter": {
"filter": { "term" : { "gender.keyword" : "M" } },
"aggs": {
"balance_price": {
"avg": {
"field": "balance"
}
}
}
}
}
}'
多过滤聚合(filters)
定义一个多桶聚合,其中每个桶都与一个过滤器相关联。每个存储桶将收集与其关联的过滤器匹配的所有文档。
curl -XPOST "localhost:9200/bank/_search?size=0&pretty=true" -d'
{
"aggs": {
"bank_filter": {
"filters": {
"filters": {
"state": { "match": { "state.keyword" : "AZ" } },
"name": { "match": { "lastname.keyword" : "Hess" } }
}
}
}
}
}'
curl -XPOST "localhost:9200/bank/_search?size=0&pretty=true" -d'
{
"aggs": {
"bank_filter": {
"filters": {
"other_bucket": true,
"filters": {
"state": { "match": { "state.keyword" : "AZ" } },
"name": { "match": { "lastname.keyword" : "Hess" } }
}
}
}
}
}'
范围聚合(range)
基于多桶的聚合,使用户能够定义一组范围,每个范围代表一个桶。在汇总过程中,将按每个范围检查每个文档中提取的值,并“存储”相关/匹配的文档。请注意,此聚合包括起始值,但不包括每个范围的起始值。
# 范围聚合
curl -XPOST localhost:9200/bank/_search?size=0 -d'
{
"aggs": {
"age_range": {
"range": {
"field": "age",
"ranges": [
{ "to": 30 },
{ "from": 20, "to": 25 },
{ "from": 40 }
]
}
}
}
}'
# 在桶内再进行操作
curl -XPOST localhost:9200/bank/_search?size=0 -d'
{
"aggs": {
"age_range": {
"range": {
"field": "age",
"ranges": [
{ "to": 30 },
{ "from": 20, "to": 25 },
{ "from": 40 }
]
},
"aggs": {
"balance_max": {
"max": { "field": "balance" }
}
}
}
}
}'
日期范围聚合(date_range)
该聚合与其他范围聚合之间的主要区别在于,可以在Date Math表达式中指定from和to值,还可以指定一种日期格式,通过该日期格式将返回from和to响应字段。请注意,此聚合包括起始值,但不包括每个范围的起始值。
curl -XPOST localhost:9200/logstash-*/_search?size=0 -d'
{
"aggs": {
"age_range": {
"date_range": {
"field": "utc_time",
"format": "yyyy-MM",
"ranges": [
{ "to": "now+10M" },
{ "from": "now-10M" }
]
}
}
}
}'
格式字母其定义如下:
字母缩写 | 描述 | 类型 | 默认值 |
---|---|---|---|
C | century of era (>=0) | number | 20 |
Y | year of era (>=0) | year | 1996 |
x | weekyear | year | 1996 |
w | week of weekyear | number | 27 |
e | day of week | number | 2 |
E | day of week | text | Tuesday; Tue |
y | year | year | 1996 |
D | day of year | number | 189 |
M | month of year | month | July; Jul; 07 |
d | day of month | number | 10 |
a | halfday of day | text | PM |
K | hour of halfday (0~11) | number | 0 |
h | clockhour of halfday (1~12) | number | 12 |
H | hour of day (0~23) | number | 0 |
k | clockhour of day (1~24) | number | 24 |
m | minute of hour | number | 30 |
s | second of minute | number | 55 |
S | fraction of second | number | 978 |
z | time zone | text | Pacific Standard Time; PST |
Z | time zone offset/id | zone | -0800; -08:00; America/Los_Angeles |
' | escape for text | delimiter | '' |
日期直方图聚合(date_histogram)
直方图的多桶聚合,但只能应用于日期值。可以通过日期/时间表达式指定间隔。
curl -XPOST "localhost:9200/logstash-*/_search?size=0&pretty" -d'
{
"aggs": {
"utc_time_agg": {
"date_histogram" : {
"field": "utc_time",
"interval": "day",
"format": "yyyy-MM-dd",
"offset": "+6h"
}
}
}
}'
-
interval
时间间隔。可用表达式:year, quarter, month, week, day, hour, minute, second -
key
在Elasticsearch内部,日期表示为64位数字,表示自从该纪元以来以毫秒为单位的时间戳。这个时间戳作为桶的Key返回。key_as_string是使用format参数对时间戳进行格式化后的文本。 -
offset
offset参数用于按指定的正(+)或负偏移(-)更改每个存储区的起始值,例如1h表示一个小时,或1d表示一天。
地理距离聚合(geo_distance)
适用于geo_point字段的多桶聚合,在概念上与范围聚合非常相似。用户可以定义一个原点和一组距离范围。聚合会评估每个文档值到原点的距离,并根据范围来确定它所属的桶(如果文档和原点之间的距离在存储桶的距离范围内,则文档属于存储桶)。
curl -XPOST "localhost:9200/restaurants/_search?size=0&pretty" -d'
{
"aggs": {
"geo_range": {
"geo_distance": {
"field": "location",
"origin": "52.3760, 4.894",
"ranges": [
{ "to": 100000 },
{ "from": 100000, "to": 300000 },
{ "from": 300000 }
]
}
}
}
}'
默认情况下,距离单位为m(米),但也可以设置为:mi(英里),in(英寸),yd(码),km(公里),cm(厘米),mm(毫米)。
curl -XPOST "localhost:9200/restaurants/_search?size=0&pretty" -d'
{
"aggs": {
"geo_range": {
"geo_distance": {
"field": "location",
"origin": "52.3760, 4.894",
"unit": "km",
"ranges": [
{ "to": 100000 },
{ "from": 100000, "to": 300000 },
{ "from": 300000 }
]
}
}
}
}'
该聚合有两种距离计算模式:弧(arc,默认)和平面(plane)。弧计算是最准确的。平面最快但最不准确。当搜索跨越较小的地理区域(约5公里)时,请考虑使用平面。如果在非常大的区域内进行搜索(例如跨大陆搜索),平面将返回较高的误差范围。
curl -XPOST "localhost:9200/restaurants/_search?size=0&pretty" -d'
{
"aggs": {
"geo_range": {
"geo_distance": {
"field": "location",
"origin": "52.3760, 4.894",
"unit": "km",
"distance_type": "plane",
"ranges": [
{ "to": 100000 },
{ "from": 100000, "to": 300000 },
{ "from": 300000 }
]
}
}
}
}'
地理网格聚合(geohash_grid)
适用于geo_point字段的多桶聚合。将地理坐标点分组到以网格为单元的桶中。每个单元格都使用可定义精度的geohash进行标记,聚合中使用的地理哈希可以在1到12之间选择精度(长度为12的最高精度geohash仅覆盖不到一平方米的区域,因此就RAM和结果大小而言,高精度geohash可能会更加昂贵)
- 高精度:高精度地理哈希具有较长的字符串,每个单元仅覆盖较小区域。
- 低精度:低精度地理哈希具有较短的字符串,每个单元都覆盖大面积的区域。
curl -XPOST "localhost:9200/restaurants/_search?size=0&pretty" -d'
{
"aggs": {
"geo_hash_agg": {
"geohash_grid": {
"field": "location",
"precision": 12
}
}
}
}'
curl -XPOST "localhost:9200/restaurants/_search?size=0&pretty" -d'
{
"aggs": {
"zoom_filter": {
"filter" : {
"geo_bounding_box": {
"location": {
"top_left": "52.4, 4.9",
"bottom_right": "52.3, 5.0"
}
}
},
"aggs": {
"zoom_agg": {
"geohash_grid": {
"field": "location",
"precision": 8
}
}
}
}
}
}'
GEOHash长度和区域范围对应表:
序号 | 计算公式 |
---|---|
1 | 5,009.4km x 4,992.6km |
2 | 1,252.3km x 624.1km |
3 | 156.5km x 156km |
4 | 39.1km x 19.5km |
5 | 4.9km x 4.9km |
6 | 1.2km x 609.4m |
7 | 152.9m x 152.4m |
8 | 38.2m x 19m |
9 | 4.8m x 4.8m |
10 | 1.2m x 59.5cm |
11 | 14.9cm x 14.9cm |
12 | 3.7cm x 1.9cm |
词项聚合(terms)
多指标聚合,根据索引库中的文档动态构建桶。基于词项的聚合,如果聚合字段是text的话,会对一个一个的词根进行聚合基于多桶的聚合,每个桶一个对应一个唯一值。
curl -XPOST "localhost:9200/bank/_search?size=0&pretty" -d'
{
"aggs": {
"name_agg": {
"terms" : {
"field": "firstname.keyword",
"size": 3
}
}
}
}'
默认情况下,词项聚合将返回doc_count排序的前十个词项的存储桶。可以通过设置size参数来更改此默认行为。
排序
可以通过设置Order参数来设置桶的顺序。默认情况下,桶会按照doc_count降序排列。不建议按_count升序或按子聚合排序,因为这会增加文档计数上的错误。
curl -XPOST "localhost:9200/bank/_search?size=0&pretty" -d'
{
"aggs": {
"name_agg": {
"terms" : {
"field": "firstname.keyword",
"order": { "_term": "desc" }
},
"aggs": {
"max_balance": { "max": { "field": "balance" } }
}
}
}
}'
过滤
对桶进行过滤。可以设置include和exclude参数来完成,参数接受正则表达式或精确值数组。此外,include子句还可以使用分区表达式。
# 使用include和exclude进行过滤
curl -XPOST "localhost:9200/bank/_search?size=0&pretty" -d'
{
"aggs": {
"name_agg": {
"terms" : {
"field": "address.keyword",
"include": ".*Gatling.*",
"exclude": ".*Street"
}
}
}
}'
# 精确过滤
curl -XPOST "localhost:9200/bank/_search?size=0&pretty" -d'
{
"aggs": {
"name_agg": {
"terms" : {
"field": "address.keyword",
"include": ["105 Onderdonk Avenue", "Street"]
}
}
}
}'
# 分区过滤
curl -XPOST "localhost:9200/bank/_search?size=0&pretty" -d'
{
"aggs": {
"name_agg": {
"terms" : {
"field": "account_number",
"include": {
"partition": 0,
"num_partitions": 20
},
"size": 20,
"order": {
"max_balance": "desc"
}
},
"aggs": {
"max_balance": {
"max": { "field": "balance" }
}
}
}
}
}'
搜集模式
对于具有许多唯一术语和少量所需结果的字段,将子聚合的计算延迟到最上层父级aggs被聚合之前,可能会更有效。通常,聚合树的所有分支都以深度优先的方式进行扩展,然后才进行修剪。在某些情况下,这可能非常浪费,并且会遇到内存限制。如:在电影数据库中查询10个最受欢迎的演员及其5个最常见的联合主演:
curl -XPOST "localhost:9200/bank/_search?size=0&pretty" -d'
{
"aggs": {
"name_agg": {
"terms" : {
"field": "firstname.keyword",
"size": 10,
"collect_mode": "breadth_first"
},
"aggs": {
"tops": {
"terms": {
"field": "firstname.keyword",
"size": 5
}
}
}
}
}
}'
管道聚合
管道聚合主要目的是将信息添加到输出树中,而不是文档集所产生的输出。管道聚合有很多不同的类型,每种类型都与其他聚合计算不同的信息,但是可以将这些类型分为两类:
- Parent:将新计算的桶或聚合添加到现有桶中。
-
Sibling:同级聚合的输出提供的管道聚合,并且能够计算与该同级聚合处于同一级别的新聚合
大多数管道聚合需要另一个聚合作为其输入。输入聚合是通过buckets_path参数定义的,该参数遵循特定格式:
AGG_SEPARATOR = '>' ; # 聚合分隔符
METRIC_SEPARATOR = '.' ; # 指标分隔符
AGG_NAME = <聚合名称> ;
METRIC = <指标名称> ;
PATH = <AGG_NAME> [ <AGG_SEPARATOR>, <AGG_NAME> ]* [ <METRIC_SEPARATOR>, <METRIC> ] ;
管道聚合没有子聚合功能,但是根据其类型,可以引用buckets_path中的另一个管道,从而可以链接管道聚合。
平均值聚合
兄弟管道聚合,用于计算兄弟聚合中指定指标的平均值。指定的指标必须是数字,并且同级聚合必须是多桶聚合。
curl -XPOST "localhost:9200/logstash-*/_search?size=0&pretty" -d'
{
"aggs": {
"utc_time_agg": {
"date_histogram" : {
"field": "utc_time",
"interval": "day",
"format": "yyyy-MM-dd"
},
"aggs": {
"sum_bytes": {
"sum": {
"field": "bytes"
}
}
}
},
"max_day_bytes": {
"avg_bucket": {
"buckets_path": "utc_time_agg>sum_bytes"
}
}
}
}'
常用参数
- gap_policy当管道聚合遇到不存在的值,有点类似于term等聚合的(missing)时所采取的策略,可选择值为:skip、insert_zeros。skip:此选项将丢失的数据视为bucket不存在。它将跳过桶并使用下一个可用值继续计算。insert_zeros:默认使用0代替。
- format用于格式化聚合桶的输出。
桶脚本聚合
父管道聚合。
curl -XPOST "localhost:9200/logstash-*/_search?size=0&pretty" -d'
{
"aggs": {
"utc_time_agg": {
"date_histogram" : {
"field": "utc_time",
"interval": "day",
"format": "yyyy-MM-dd"
},
"aggs": {
"sum_bytes": {
"sum": {
"field": "bytes"
}
},
"jpg_aggs": {
"filter": {
"term": {
"extension.keyword": "jpg"
}
},
"aggs": {
"jpg_bytes": {
"sum": {
"field": "bytes"
}
}
}
},
"jpg_percentage": {
"bucket_script": {
"buckets_path": {
"jpgBytes": "jpg_aggs>jpg_bytes",
"totalBytes": "sum_bytes"
},
"script": "params.jpgBytes/params.totalBytes*100"
}
}
}
}
}
}'
分桶选择聚合
curl -XPOST "localhost:9200/logstash-*/_search?size=0&pretty" -d'
{
"aggs": {
"utc_time_agg": {
"date_histogram" : {
"field": "utc_time",
"interval": "day",
"format": "yyyy-MM-dd"
},
"aggs": {
"sum_bytes": {
"sum": {
"field": "bytes"
}
},
"bytes_bucket_filter": {
"bucket_selector": {
"buckets_path": {
"totalBytes": "sum_bytes"
},
"script": "params.totalBytes>3650000"
}
}
}
}
}
}'
网友评论