一些概念
如同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}
},
...
The end
聚合是比较费时间的。普通的查询,如term、match,都是通过倒排索引来匹配,其时间复杂度基本上可以视为o(1)。而聚合是会遍历所有的数据,时间复杂度则是o(n),随着多层的嵌套,其时间复杂度线性增长。
所以,最好不要对大量的数据进行聚合,尤其不要嵌套聚合。应该在聚合之前对这大量的数据先过滤,筛选出有意义的数据再进行聚合。
关于过滤+聚合可以参考后续博文。
Reference
官方文档—Aggregations:https://www.elastic.co/guide/en/elasticsearch/guide/current/aggregations.html