一些概念
如同sql里面的group by关键字,Elasticsearch(以下简称es)也是支持聚合、统计的。
es的聚合有两个比较重要的概念:
- bucket :桶
- Metrics :指标
Buckets
Buckets简单来说就是满足特定条件的文档的集合。
如同我们经常使用的sql中group by A,B,C...,每个A,B,C等这样的关键字都可以将其视为桶。因为数据库的表都是二维表的形式,es的Buckets并不支持多个字段,但是可以嵌套。这个后续会说到。
Metrics
Metrics 就是对Buckets里面的数据进行各种统计计算。
如同sql里面group by后,你可以使用关键字count、max、avg等来统计,计算最大、最小、平均值。es当然也都支持,而且还有一些更多的sao操作,后续说到。
基本聚合
说明:关于对es的交互,可以先去翻翻我之前写的博文。这里不再累述,仅会给出查询表达式。后续皆是。
aggs关键字
aggs关键字使用规则比较简单:给聚合取个名,指定要聚合的关键字。比如:
{
"size": 0,
"aggs": {
"srvNameAgg": {
"terms": {
"field": "srvName"
}
}
}
}
这个就表示对srvName字段进行聚合,"size":0表示不需要hits的结果。结果如下:
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 10000,
"max_score": 0,
"hits": []
},
"aggregations": {
"srvNameAgg": {
"doc_count_error_upper_bound": 81,
"sum_other_doc_count": 5633,
"buckets": [
{
"key": "sDynSvc",
"doc_count": 1514
},
{
"key": "sAppQry",
"doc_count": 518
},
...略
聚合的结果是在aggregations.你的聚合名.buckets里面。也可以看到。
嵌套聚合
正如之前所说的。es的聚合并支持多个字段的聚合,也就是说,你不能在一个aggs下多个字段聚合。 BUT,这个问题可以通过别的script方式实现,但是需要改下es原有的配置,有兴趣可以看看这个:https://www.elastic.co/guide/en/elasticsearch/reference/6.3/search-aggregations-metrics-scripted-metric-aggregation.html
其实,我们通过sql group by出来的结果,比如这种,聚合出来的二维表, 我们同样可以通过Json节点的形式来进行描述:
{
"DMDB":{
"DRS":{
"DMDB.DRS.DrSendType":"",
"DMDB.DRS.ProcessExists":"",
},
"DRS.B":{
"DMDB.DRS.B.DrSendType":"",
"DMDB.DRS.B.ProcessExists":""
},
...
},
"HBase":{
"HMaster":{
"Hadoop.HBase.HMaster.HaStatus":"",
"Hadoop.HBase.HMaster.ProcessExists":""
},
"HRegionServer":{
"Hadoop.HBase.HRegionServer.Blocked":"",
"Hadoop.HBase.HRegionServer.ProcessExists":""
}
...
},
"HDFS":{
"DataNode":{
"Hadoop.HDFS.DataNode.ProcessExists":""
},
"NameNode":{
"Hadoop.HDFS.NameNode.HaStatus":"",
"Hadoop.HDFS.NameNode.ProcessExists":""
}
...
},
"YARN":{
...
}
}
所以,针对这种多聚合,我们直接嵌套桶(buket)的方式。
{
"size": 0,
"aggs": {
"srvNameAgg": {
"terms": {
"field": "srvName"
},
"aggs":{
"retCodeAgg":{
"terms":{
"field":"retCode"
},
"aggs":{
"retMsgAgg": {
"terms": {
"field": "retMsg"
}
}
}
}
}
}
}
}
返回结果形如:
{
...,
"aggregations": {
"srvNameAgg": {
"doc_count_error_upper_bound": 81,
"sum_other_doc_count": 5633,
"buckets": [
{
"key": "sPFeeQuery",
"doc_count": 497,
"retCodeAgg": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "0",
"doc_count": 465,
"retMsgAgg": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "ok!",
"doc_count": 465
}
]
}
},
{
"key": "422001001",
"doc_count": 32,
"retMsgAgg": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 22,
"buckets": [
{
"key": "用户不存在 ERRID:10901775",
"doc_count": 1
},
{
"key": "用户不存在 ERRID:13251308",
"doc_count": 1
}
]
}
}
]
}
},
{
"key": "sUserOrdQry",
"doc_count": 360,
"retCodeAgg": {
...
Tips:重点观察下buckets下面的key及doc_count
需要注意的是,经过本人的测试,聚合嵌套的越多效率也就越地低。尤其是某一层的桶可能返回数据特别多(比如后续说到的对时间粒度的聚合),聚合效率更是尤为低下。
聚合指标
从上面可以看出,es默认是返回了统计的结果,也就是类似数据库的count(*)的结果。它同样也是能支持avg、min、max等计算。
比如:求esb里面服务的平均执行时间:
{
"size":0,
"aggs":{
"srv_aggs":{
"terms":{
"field":"srvName"
},
"aggs":{
"avg_executeTime":{
"avg":{
"field":"executeTime"
}
}
}
}
}
}
结果如下:
"took": 15,
"timed_out": false,
"_shards": {...},
"hits": {...},
"aggregations": {"srv_aggs": {
"doc_count_error_upper_bound": 49,
"sum_other_doc_count": 2607,
"buckets": [
{
"key": "sAppQry",
"doc_count": 2440,
"avg_executeTime": {"value": 107.825}
},
{
"key": "sPFeeQuery",
"doc_count": 1483,
"avg_executeTime": {"value": 219.701955495617}
},
{
"key": "sUserOrdQry",
"doc_count": 1427,
"avg_executeTime": {"value": 91.43587946741415}
},
{
"key": "sQryCreditInfo",
"doc_count": 444,
"avg_executeTime": {"value": 59.240990990990994}
},
...
统计
正如之前所说,es不仅仅能支持求最大、最小、平均等功能,它还有一个实用的功能:类似条形图的统计。
histogram关键字
用法同aggs下的其他关键字。它通过一个interval关键字来指定统计的区间。 比如:统计下执行时间的分布:
{
"size":0,
"aggs":{
"executeTimeHistogram":{
"histogram":{
"field":"executeTime",
"interval":10
}
}
}
}
返回结果形如:
{
"took": 6,
"timed_out": false,
"_shards": {...},
"hits": {...},
"aggregations": {"executeTimeHistogram": {"buckets": [
{
"key": 0,
"doc_count": 832
},
{
"key": 10,
"doc_count": 1137
},
{
"key": 20,
"doc_count": 621
},
{
"key": 30,
"doc_count": 559
},
{
"key": 40,
"doc_count": 660
},
{
"key": 50,
"doc_count": 712
},
{
"key": 60,
"doc_count": 497
},
...
从而可以很容易将其转换为图标的形式:
看起来是不是更直观了呢.....
时间统计
如果搜索是在 Elasticsearch 中使用频率最高的,那么构建按时间统计的date_histogram应该就是紧随其后了。
用法同histogram,但是date_histogram只能是用于时间的类型。interval里面就是指定year、month、day、hour、minute、second。还能包括quarter等日期关键字。
比如,统计每分钟的调用量:
{
"size":0,
"aggs":{
"callTimeAggs":{
"date_histogram":{
"field":"@timestamp",
"interval":"minute"
}
}
}
}
返回结果如下:
{
"took": 4,
"timed_out": false,
"_shards": {...},
"hits": {...},
"aggregations": {"callTimeAggs": {"buckets": [
{
"key_as_string": "2019-09-01T00:03:00.000Z",
"key": 1567296180000,
"doc_count": 1183
},
{
"key_as_string": "2019-09-01T00:04:00.000Z",
"key": 1567296240000,
"doc_count": 1691
},
{
"key_as_string": "2019-09-01T00:05:00.000Z",
"key": 1567296300000,
"doc_count": 1618
},
可能注意到buckets里面的key都是时间戳的类型,所以,我们也可以看出date的类型在es中是以时间戳的形式保存的。
如果你想自己指定key_as_string的时间类型,可以使用format关键字指定格式,如:
{
"size":0,
"aggs":{
"callTimeAggs":{
"date_histogram":{
"field":"@timestamp",
"interval":"minute",
"format":"yyyy-MM-dd HH🇲🇲ss"
}
}
}
}
可能你会发现上述的结果,是从2019-09-01T00:03:00.000Z开始的,那2019-09-01T00:02:00.000Z、2019-09-01T00:01:00.000Z怎么没显示呢?尽管它们的没有结果。
为此,可以使用min_doc_count与extended_bounds来强制返回空的聚合。
- min_doc_count :指定最小的返回值。比如指定0,强制返回空 buckets
- extended_bounds : 指定返回区间。通过其min、max来指定,指定的格式根据你的format作为标准,如果没有format,就根据interval。
比如:只需要00:00-00:10这10分钟的所有聚合结果
{
"size":0,
"aggs":{
"callTimeAggs":{
"date_histogram":{
"field":"@timestamp",
"interval":"minute",
"format":"yyyy-MM-dd HH:mm",
"min_doc_count":0,
"extended_bounds":{
"min":"2019-09-01 00:00",
"max":"2019-09-01 00:10"
}
}
}
}
}
The end
聚合是比较费时间的。普通的查询,如term、match,都是通过倒排索引来匹配,其时间复杂度基本上可以视为o(1)。而聚合是会遍历所有的数据,时间复杂度则是o(n),随着多层的嵌套,其时间复杂度线性增长。
所以,最好不要对大量的数据进行聚合,尤其不要嵌套聚合。应该在聚合之前对这大量的数据先过滤,筛选出有意义的数据再进行聚合。
关于过滤+聚合可以参考后续博文。
Reference
官方文档—Aggregations:https://www.elastic.co/guide/en/elasticsearch/guide/current/aggregations.html
发表评论