前言

最近在公司做了一个类似大盘、看板的功能,所有的数据全部存储在ES里面,完全需要自己去ES里面捞数据,并没有一个结构化、过滤好的数据源,完全需要自己对ES的数据进行过滤、筛选、统计,所以就需要用到聚合了,下面是功能图,在图里面其实展示了很多重要信息

  1. 根据时间范围、其他条件进行筛选数据
  2. 指标数据以每天为单位进行展示
  3. 统计所有数据,变成总量数据

功能展示效果图

这里就需要考验到对ES聚合的使用了,因为不可能查询每个指标,都访问一遍ES,不仅指标多,指标也要根据日期进行分桶,不可能每次都发一次请求,即便是多线程,那也对ES压力太大了,使用了过多的网络资源来满足需求,况且时间范围越多,请求次数也越多,所以很不合理

这个时候就体现出来聚合的重要性了,ES底层自己支持大量的聚合方式,可以满足按天为单位聚合数据,展示每天数据量有多少,也可以在其上层进行过滤,展示某个数据每天有多少 (不得不说,这简直太棒了)

底层的指标都是我来计算提供数据的,所以对这块很熟,也使用了策略设计模式去做这个内容,满足其拓展性

聚合是什么

首先要明白,聚合是什么

聚合分析是数据库中重要的功能特性,完成对一个查询的数据集中数据的聚合计算,如:找出某字段(或计算表达式的结果)的最大值、最小值,计算和、平均值等。ES作为搜索引擎兼数据库,同样提供了强大的聚合分析能力。

对一个数据集求最大、最小、和、平均值等指标的聚合,在ES中称为指标聚合(metric)

而关系型数据库中除了有聚合函数外,还可以对查询出的数据进行分组group by,再在组上进行指标聚合。在ES中group by称为分桶,桶聚合bucketing

通过聚合,我们可以统计出绝大部分的数据,也可以查出来自己想要的指标,满足其使用性

用法

按天聚合数据

{
  "aggs": {
    "daily_hits_true": {
      "filter": {
        "bool": {
          "must": [
            {
              "range": {
                "statDate": {
                  "gte": 1700668800000,
                  "lt": 1701359999000
                }
              }
            },
            {
              "nested": {
                "path": "searchFields",
                "query": {
                  "bool": {
                    "must": [
                      {
                        "term": {
                          "searchFields.name": "C_S_TDRESULT"
                        }
                      },
                      {
                        "term": {
                          "searchFields.value.raw": "true"
                        }
                      }
                    ]
                  }
                }
              }
            }
          ]
        }
      },
      "aggs": {
        "daily_hits": {
          "date_histogram": {
            "field": "statDate",
            "interval": "1d",
            "format": "yyyy-MM-dd"
          }
        }
      }
    }
  }
}

上面的聚合就是一个非常简单的例子,我根据时间范围(statDate)进行筛选数据,判断C_S_TDRESULT是否为true进行过滤
最后添加一个 daily_hits 聚合条件统计出每天的数据
这里的聚合使用的是ES的 date_histogram,他可以按照指定时间范围的内容进行统计,例如几小时,也是完全可以的

field: 根据哪个字段进行统计
interval: 时间周期是多少,1d就是按照一天为单位统计数据
format: 展示出来的时间格式是什么,如果按照1小时统计,需要填写小时的单位,需求是按照天统计,所以不关注时分秒的内容

去重聚合

去重聚合顾名思义,假如你数据存在一个 PHONE_NUMBER 字段

  1. 第1条数据: PHONE_NUMBER = qwe
  2. 第2条数据: PHONE_NUMBER = abc
  3. 第3条数据: PHONE_NUMBER = qwe

最终去重聚合统计出来的结果是2,这个2的数据就是聚合后的结果

{
  "aggs": {
    "daily_hits": {
      "date_histogram": {
        "field": "statDate",
        "interval": "1d",
        "format": "yyyy-MM-dd"
      },
      "aggregations": {
        "searchFields": {
          "nested": {
            "path": "searchFields"
          },
          "aggregations": {
            "relativeInAcNumFilter": {
              "filter": {
                "bool": {
                  "must": [
                    {
                      "term": {
                        "searchFields.name": "S_S_MOBINO"
                      }
                    }
                  ]
                }
              },
              "aggregations": {
                "relativeInAcNum": {
                  "cardinality": {
                    "field": "searchFields.value.raw"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

顶部的 daily_hits 就是按天聚合,主要是看最里面的 relativeInAcNum 聚合
其中的 cardinality 才是去重的关键,他根据 searchFields.name = S_S_MOBINO 内容进行去重

后话

虽然这样聚合可以满足我们的指标结果,当指标类型过多时,还是会产生一定的ES压力,并且也要根据时间范围而定,当数据量特别大的情况下,ES查询效率也会变慢,有时候倒不如直接多线程执行多条ES操作了

主要选择还是在于自身的体量上,一般来说都放在ES上直接进行大聚合,一次操作,查询多个结果,这样也要求ES需要极高的效率,虽然ES是一个搜索效率极高的组件,有时候也需要考虑他的性能

这里其实还有很多可以优化的点,因为按天搜索出来的数据,只有当天的数据需要保证实时性,也就是聚合的数据只需要查找当天的即可,所以可以在每天的半夜,统计昨天一整天的数据,落库存储至数据库,以此来减轻ES的压力。当存储在ES的数据量越多,聚合的效率也会降低,压力也会飙升,如若时间范围更长,筛选的数据也会更多,所以将死数据存入数据库类似的介质或许是一种选择

最后修改:2023 年 12 月 08 日
如果觉得我的文章对你有用,请随意赞赏