2. Metric聚合实战:avg、sum、min、max、value_count、cardinality、stats、extended_stats

好,咱们直接进入正题。Metric聚合,说白了就是做数学计算的。你想想看,我们存了那么多数据到ES里,总不能光查出来看看吧?总得算个平均值、加个总数什么的。这一章我就带你挨个过一遍最常用的几个Metric聚合。

2.1 avg聚合——算平均值

这个最简单,也最常用。比如你想知道所有订单的平均金额,直接上avg

GET /orders/_search
{
  "size": 0,
  "aggs": {
    "avg_price": {
      "avg": {
        "field": "price"
      }
    }
  }
}

返回结果里会有一个value字段,就是平均值。嗯,这里要注意一点:如果字段里有空值,ES默认会忽略掉,不会把它算成0。我个人习惯是提前确认一下数据质量,别到时候算出来一个莫名其妙的数。

小技巧:如果你需要把空值当成0来算,可以用missing参数:"missing": 0

2.2 sum聚合——求和

这个更直接,就是把所有值加起来。我在项目中遇到过这样一个场景:统计某个时间段内所有订单的总销售额。用sum聚合,几毫秒就出结果了。

GET /orders/_search
{
  "size": 0,
  "aggs": {
    "total_revenue": {
      "sum": {
        "field": "price"
      }
    }
  }
}

你可能会问:那如果字段是字符串类型怎么办?ES会报错。所以建索引的时候,数值字段一定要用integerfloatdouble这些类型。

2.3 min和max聚合——最小值和最大值

这两个放一起说,因为用法几乎一样。一个找最小,一个找最大。

GET /orders/_search
{
  "size": 0,
  "aggs": {
    "min_price": {
      "min": {
        "field": "price"
      }
    },
    "max_price": {
      "max": {
        "field": "price"
      }
    }
  }
}

我曾经用这个组合排查过一个数据异常的问题。当时发现某天的订单最低价是负数,一查才知道是系统bug导致退款金额被错误地记录成了订单金额。你看,minmax不光能算数,还能帮你做数据质量检查。

2.4 value_count聚合——统计文档数

这个聚合统计的是某个字段有值的文档数量。注意,不是总文档数,是字段非空的文档数。

GET /orders/_search
{
  "size": 0,
  "aggs": {
    "price_count": {
      "value_count": {
        "field": "price"
      }
    }
  }
}

为什么需要这个?举个例子:你有一个用户表,其中phone字段是可选的。你想知道有多少用户填了手机号,用value_count就对了。

注意:如果你用_count,那是统计所有文档数,包括字段为空的。这两个别搞混了。

2.5 cardinality聚合——去重计数

这个就厉害了。它类似于SQL里的COUNT(DISTINCT)。比如你想知道有多少个不同的用户下了单:

GET /orders/_search
{
  "size": 0,
  "aggs": {
    "unique_users": {
      "cardinality": {
        "field": "user_id"
      }
    }
  }
}

说实话,cardinality在数据量大的时候性能会有点吃紧。ES内部用的是HyperLogLog算法,是个近似值,不是精确的。但好处是内存占用很小。我一般会设置一个precision_threshold参数,比如10000,这样在精度和性能之间取个平衡。

GET /orders/_search
{
  "size": 0,
  "aggs": {
    "unique_users": {
      "cardinality": {
        "field": "user_id",
        "precision_threshold": 10000
      }
    }
  }
}

2.6 stats聚合——一键五连

如果你觉得一个个写avgsumminmaxcount太麻烦,ES提供了一个打包好的聚合——stats。一次请求,五个结果全出来。

GET /orders/_search
{
  "size": 0,
  "aggs": {
    "price_stats": {
      "stats": {
        "field": "price"
      }
    }
  }
}

返回结果长这样:

字段 含义
count 非空文档数
min 最小值
max 最大值
avg 平均值
sum 总和

我个人特别喜欢用这个,尤其是在做数据探索阶段。一条查询下去,数据的整体分布就心里有数了。

2.7 extended_stats聚合——加强版stats

extended_stats是在stats的基础上,额外增加了更多统计指标:

  • sum_of_squares:平方和
  • variance:方差
  • std_deviation:标准差
  • std_deviation_bounds:标准差范围(默认是±2个标准差)
GET /orders/_search
{
  "size": 0,
  "aggs": {
    "price_extended_stats": {
      "extended_stats": {
        "field": "price"
      }
    }
  }
}

返回结果里会多出这些字段。标准差这个东西,说白了就是看数据波动大不大。我曾经用这个分析过服务器响应时间,发现标准差特别大,说明有的请求特别慢,有的特别快。后来一查,是缓存命中率不稳定导致的。

实战建议:在做报表或者监控看板时,extended_stats非常有用。比如你想设置一个告警规则:当响应时间超过均值+2倍标准差时,触发告警。这个聚合直接给你算好了。

2.8 综合实战:一个完整的报表查询

最后,我给你看一个我在项目中实际用过的查询。当时需要做一个销售数据看板,要求展示:总订单数、总销售额、平均客单价、最高单笔金额、最低单笔金额、以及不同用户的去重数。

GET /orders/_search
{
  "size": 0,
  "aggs": {
    "order_count": {
      "value_count": {
        "field": "order_id"
      }
    },
    "total_sales": {
      "sum": {
        "field": "price"
      }
    },
    "avg_order": {
      "avg": {
        "field": "price"
      }
    },
    "max_order": {
      "max": {
        "field": "price"
      }
    },
    "min_order": {
      "min": {
        "field": "price"
      }
    },
    "unique_users": {
      "cardinality": {
        "field": "user_id",
        "precision_threshold": 10000
      }
    }
  }
}

你看,一次查询就把所有指标都拿到了。前端直接渲染,后端不用写任何计算逻辑。这就是ES聚合的魅力所在。

避坑指南:我曾经犯过一个错误——在text类型字段上做cardinality聚合。结果ES报错说字段需要开启fielddata。后来我改成keyword类型就没事了。所以记住:聚合操作尽量用keyword或数值类型,别用text

好了,这一章的内容就到这里。Metric聚合是ES聚合查询的基础,掌握了这些,后面学Bucket聚合和Pipeline聚合就会轻松很多。下一章我们聊聊Bucket聚合,到时候你会看到怎么把数据分组、分桶,那才是真正好玩的开始。