Elasticsearch聚合:不只是搜索,更是强大的数据分析引擎
嘿,你好!如果你正在使用Elasticsearch(简称ES),很可能已经体会过它闪电般的搜索速度。但ES的魅力远不止于此。当你的索引里塞满了成千上万甚至数百万的文档(比如用户日志、商品信息、传感器数据),你肯定不满足于仅仅找到它们,更想知道:
- 哪个牌子的T恤卖得最好?
- 过去一小时内,我们网站的平均响应时间是多少?
- 不同价格区间的商品各有多少种?
这些问题,单纯的搜索(query)可回答不了。这时候,就轮到ES的另一个强大武器——**聚合(Aggregations)**登场了!
聚合允许你在数据“现场”进行计算和总结,直接返回分析结果,而不是把成吨的原始数据捞出来再用其他工具处理。这不仅效率高,而且能让你实时洞察数据背后的故事。
但是,刚接触聚合时,很多人会被两个核心概念搞得有点晕:bucket(桶)和 metric(指标)。它们是啥关系?谁负责啥?别急,今天我就带你彻底弄清楚这两个家伙,让你轻松玩转ES聚合分析。
核心概念:bucket 和 metric,分工明确的两兄弟
想象一下,你面前有一大堆五颜六色的乐高积木(代表你的ES文档)。你想整理一下,并了解一些统计信息。
Bucket(桶)聚合:分类整理大师
bucket聚合的作用,就是按某种规则把积木分门别类地放进不同的桶里。它的核心职责是 分组(Grouping)。
- 规则:可以按照积木的颜色分(红色一桶、蓝色一桶、黄色一桶...)。
- 规则:也可以按照积木的形状分(方形一桶、长条形一桶、带轮子的一桶...)。
- 规则:还可以按照积木的大小分(大积木一桶、中积木一桶、小积木一桶...)。
在ES里,“规则”通常就是文档的某个字段值。比如:
- 按T恤的color字段分桶。
- 按商品的brand字段分桶。
- 按日志的timestamp字段按时间段(每小时、每天)分桶。
- 按产品的price字段按价格区间分桶。
bucket聚合执行完后,你会得到一系列的“桶”,每个桶里都装着符合该桶分类标准的文档。同时,ES还会贴心地告诉你每个桶里有多少个文档(doc_count)。
常见的 bucket 聚合类型有:
- terms聚合:最常用!根据字段的确切值进行分组。比如,按T恤颜色(- red,- blue,- green)分桶,每个颜色一个桶。
- range聚合:根据数值或日期范围进行分组。比如,按价格分桶:0-50元一个桶,51-100元一个桶,101元以上一个桶。
- date_range聚合:专门用于日期范围分组,可以自定义任意范围,比如“上周”、“本月”。
- histogram聚合:根据数值字段,按固定间隔分组。比如,按商品重量,每隔100g分一个桶(0-100g, 100-200g, ...)。
- date_histogram聚合:根据日期字段,按固定时间间隔分组。这是分析时序数据的利器!比如,按天、按小时、按分钟统计销售额或日志数量。
- filters聚合:可以自定义多个查询条件,每个条件对应一个桶。比如,一个桶放所有“红色”且“纯棉”的T恤,另一个桶放所有“蓝色”的T恤。
- nested聚合:用于聚合- nested类型的字段内部的数据。
- significant_terms聚合:找出某个子集(比如某个分类下的商品)相对于全体数据来说,哪些词项(term)是“异常”或“显著”的。常用于发现特征或异常模式。
记住,bucket聚合的核心产出是 “桶的列表”,以及每个桶对应的文档数量。
Metric(指标)聚合:数据计算专家
好了,现在积木已经按颜色分好桶了(bucket聚合干的活)。接下来,你想知道:
- 每个颜色的积木,平均有多重?
- 所有红色积木的总价值是多少?
- 所有积木中,最贵的那块价值多少?
- 蓝色积木有多少种不同的形状?
这些具体的计算任务,就是 metric 聚合的职责。它的核心是 计算(Calculating)。
metric聚合通常作用于一个或多个桶内的文档(或者作用于查询匹配的所有文档,如果不结合bucket使用的话),对指定的数值字段进行计算,然后输出一个单一的指标值。
常见的 metric 聚合类型有:
- avg聚合:计算数值字段的平均值。比如,计算某个分类下商品的平均价格。
- sum聚合:计算数值字段的总和。比如,计算某个时间段内的总销售额。
- min聚合:找出数值字段的最小值。比如,找到最便宜的商品价格。
- max聚合:找出数值字段的最大值。比如,找到最高的访问延迟。
- stats聚合:一次性计算出某个数值字段的多个统计指标:- count(文档数),- min,- max,- avg,- sum。很实用!
- extended_stats聚合:比- stats更丰富,额外包含平方和、方差、标准差等统计指标。
- value_count聚合:计算某个字段有多少个值(即多少文档包含该字段,注意不是去重计数)。
- cardinality聚合:计算某个字段的唯一值(基数)的近似数量。比如,统计有多少个独立访客(UV)。注意,为了性能,它通常是近似计算(基于HyperLogLog++算法),但对于大数据量来说非常高效。
- percentiles聚合:计算数值字段的百分位数。比如,查看95%的请求响应时间是多少(TP95)。
- percentile_ranks聚合:给定一个或多个数值,计算它们处于字段值的哪个百分位排名。
- top_hits聚合:比较特殊,它不是计算数值,而是从每个桶中抽样出几个“顶级”匹配的文档。比如,找出每个分类下销量最高的3个商品。
- scripted_metric聚合:允许你用脚本来自定义复杂的计算逻辑,非常灵活但也需要注意性能。
记住,metric聚合的核心产出是 “计算出的指标值”。
强强联合:Bucket + Metric = 精准分析
单独使用bucket或metric有时也有用,但ES聚合真正的威力在于将它们组合起来!
通常的模式是:
- 先用 bucket聚合将文档分组。
- 然后,在每个桶内部(或者说,作用于每个桶里的文档),使用 metric聚合进行计算。
这就像我们刚才的乐高例子:
- bucket:按颜色分桶(红桶、蓝桶、黄桶...)。
- metric:在每个颜色桶内部,计算积木的平均重量 (- avg)。
最终结果就是:“红色积木平均重xx克”,“蓝色积木平均重yy克”,“黄色积木平均重zz克”... 是不是非常清晰?
实战演练:计算每种颜色T恤的平均价格
假设我们有一个名为 tshirts 的索引,里面的文档结构类似这样:
{
  "color": "red",
  "brand": "NiceBrand",
  "price": 19.99,
  "stock": 100
}
{
  "color": "blue",
  "brand": "CoolWear",
  "price": 25.50,
  "stock": 50
}
{
  "color": "red",
  "brand": "AnotherBrand",
  "price": 15.00,
  "stock": 20
}
// ... 更多T恤文档
我们的目标是:计算出每种颜色(color)的T恤的平均价格(price)。
这显然需要 bucket 和 metric 联手:
- bucket聚合:按- color字段分桶(使用- terms聚合)。
- metric聚合:在每个颜色桶内部,计算- price字段的平均值(使用- avg聚合)。
对应的ES查询DSL(领域特定语言)大概是这样的:
GET /tshirts/_search
{
  "size": 0, // 我们不关心具体的T恤文档,只需要聚合结果
  "aggs": { // "aggs" 是 "aggregations" 的缩写
    "group_by_color": { // 这是我们给 bucket 聚合起的名字,可以自定义
      "terms": { "field": "color" }, // 指定使用 terms 聚合,作用于 color 字段
      "aggs": { // 在 bucket 聚合内部,可以嵌套下一层聚合(这里是 metric 聚合)
        "average_price": { // 这是我们给 metric 聚合起的名字
          "avg": { "field": "price" } // 指定使用 avg 聚合,作用于 price 字段
        }
      }
    }
  }
}
解析一下这个查询:
- "size": 0:告诉ES,我们不想要搜索结果(hits),只对聚合结果感兴趣。这样可以节省网络带宽和处理时间。
- "aggs":聚合操作的根节点。
- "group_by_color":这是我们定义的第一个聚合,一个- bucket聚合。名字可以随便取,只要清晰易懂。
- "terms": { "field": "color" }:指定了这个- bucket聚合的类型是- terms,它会根据- color字段的值来创建桶。
- "aggs": { ... }(内层):关键点来了! 这个内层的- aggs定义了要在每个- group_by_color产生的桶内部执行的子聚合。
- "average_price":这是我们定义的子聚合(- metric聚合)的名字。
- "avg": { "field": "price" }:指定了这个子聚合的类型是- avg,它会计算当前桶内所有文档的- price字段的平均值。
预期的结果(简化版):
ES会返回类似这样的聚合结果:
{
  // ... (省略了took, timed_out等元数据)
  "hits": { // 因为 size=0,所以 hits 数组为空
    "total": { ... },
    "max_score": null,
    "hits": []
  },
  "aggregations": {
    "group_by_color": { // 对应我们定义的 bucket 聚合名字
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [ // 这里是 bucket 聚合的核心产出:桶的列表
        {
          "key": "red", // 桶的标识,即颜色值
          "doc_count": 150, // 这个桶里有 150 件红色T恤
          "average_price": { // 对应我们定义的 metric 聚合名字
            "value": 17.495 // 计算出的红色T恤的平均价格
          }
        },
        {
          "key": "blue",
          "doc_count": 120,
          "average_price": {
            "value": 22.80
          }
        },
        {
          "key": "green",
          "doc_count": 80,
          "average_price": {
            "value": 19.99
          }
        }
        // ... 其他颜色的桶
      ]
    }
  }
}
解读结果:
看到 aggregations -> group_by_color -> buckets 了吗?这清晰地展示了 bucket 聚合的结果:一个包含多个桶的数组。
- 每个桶对象都有一个 key(代表这个桶是根据哪个color值分出来的)和一个doc_count(这个桶里有多少文档)。
- 最妙的是,每个桶对象内部,还包含了我们定义的 metric聚合average_price的结果!比如,key为red的桶里,average_price的value是17.495。这就直接告诉我们:红色T恤共有150件,它们的平均价格是17.495元。
是不是一下子就把复杂的数据分析任务变得简单直观了?这就是 bucket 和 metric 聚合协同工作的魅力!
不仅仅是简单组合:嵌套聚合的威力
你甚至可以进行更复杂的嵌套:
- Bucket 嵌套 Bucket:先按品牌分桶,然后在每个品牌桶内,再按颜色分桶。这样就能知道“NiceBrand”牌下,红色T恤有多少件,蓝色T恤有多少件。
- Bucket 嵌套 Bucket 再嵌套 Metric:在上面按品牌再按颜色分桶后,计算每个“品牌-颜色”组合下的平均价格。
GET /tshirts/_search
{
  "size": 0,
  "aggs": {
    "group_by_brand": { // 第一层 bucket: 按品牌
      "terms": { "field": "brand" },
      "aggs": {
        "group_by_color": { // 第二层 bucket: 在品牌桶内按颜色
          "terms": { "field": "color" },
          "aggs": {
            "average_price": { // Metric: 计算品牌-颜色组合的平均价
              "avg": { "field": "price" }
            }
          }
        }
      }
    }
  }
}
这种层层嵌套的能力,让你可以对数据进行多维度、细粒度的钻取分析,构建复杂的数据透视表和报表变得轻而易举。
总结:记住核心分工
现在,你应该对Elasticsearch聚合中的 bucket 和 metric 有了清晰的认识:
- Bucket聚合:负责 “分桶”,将文档按规则 分组。产出是 桶的列表。
- Metric聚合:负责 “计算”,对桶内(或全体)文档的字段值进行统计运算。产出是 计算出的指标值。
它们最常见的用法是组合出击:先用 bucket 把数据切分成有意义的小组,再用 metric 对每个小组进行量化分析。通过灵活的嵌套,你可以构建出功能强大的数据分析应用。
当然,聚合的世界远不止这些。还有管道聚合(Pipeline Aggregations)用于在聚合结果之上再进行聚合(比如计算各分类销售额占总销售额的百分比),以及矩阵聚合(Matrix Aggregations)等更高级的类型。但掌握了 bucket 和 metric 这两大基石,你就已经打开了ES数据分析的大门。
希望这篇解释能帮你扫清障碍!下次遇到需要分析ES数据的场景,大胆地用聚合吧,你会发现它比你想象的更强大、更方便。动手试试看,用你自己的数据跑几个聚合查询,感觉会更真切!

