页面加载中...

Elasticsearch的聚合

| Elasticsearch | 0 条评论 | 840浏览

一些概念

如同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出来的结果,比如这种,聚合出来的二维表, https://blog.abreaking.com/upload/2019/09/vftklp9uk0gujqv8f8g7c0qna8.jpg 我们同样可以通过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
      },
      ...

从而可以很容易将其转换为图标的形式: http://blog.abreaking.com/upload/2019/09/2un4h4oki8i5tq579vhqi4ifgb.jpg

看起来是不是更直观了呢.....

时间统计

如果搜索是在 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

发表评论

最新评论

    来第一个评论吧!