2023-12-17
中间件
0

目录

聚合分析概述
什么是聚合分析?
聚合的核心概念
聚合语法结构
聚合的三种核心类型
指标聚合(Metric Aggregations)
桶聚合(Bucket Aggregations)
管道聚合(Pipeline Aggregations)
聚合实战案例
聚合性能优化
理解 doc_values 和 fielddata
最佳实践
Java 代码示例
总结

在掌握了基础查询和全文搜索后,我们将探索 Elasticsearch 最强大的数据分析功能——聚合分析。本文将带你深入理解聚合的核心概念、三种聚合类型及实战应用,让你从数据中提取有价值的商业洞察。

聚合分析概述

什么是聚合分析?

Elasticsearch 聚合是一种强大的数据分析工具,它能够从索引中提取和计算复杂的统计信息。与传统数据库的 GROUP BY 相比,Elasticsearch 的聚合更灵活、更强大,可以处理海量数据并实现实时分析。

聚合的核心思想类似于 SQL 中的分组统计,但功能更为丰富。它允许我们对搜索结果进行多维度、多条件的数据分析和汇总,从而更好地理解数据特征和趋势。

聚合的核心概念

理解聚合需要掌握两个基本概念:

  • 桶(Buckets):满足特定条件的文档集合,相当于 SQL 中的分组

  • 指标(Metrics):对桶内文档计算的统计信息,如总和、平均值、最大值等

简单比喻:假设我们有一群人,按照性别分组(桶),然后计算每个组的平均年龄(指标)。

聚合语法结构

Elasticsearch 的聚合语法基于 JSON 格式,基本结构如下:

json
{ "size": 0, // 不返回原始搜索结果,只关注聚合结果 "aggs": { // 聚合查询的固定关键字 "aggregation_name": { // 自定义聚合名称 "aggregation_type": { // 聚合类型(terms、date_histogram等) "field": "field_name" // 要聚合的字段 }, "aggs": { // 嵌套子聚合(可选) "sub_aggregation_name": { "aggregation_type": { "field": "field_name" } } } } } }

参数说明:

  • size: 0 表示只返回聚合结果,不返回搜索命中的文档

  • 聚合支持嵌套,允许构建复杂的数据分析逻辑

聚合的三种核心类型

指标聚合(Metric Aggregations)

指标聚合用于计算数值型统计结果,返回单个值或多个值。

常用指标聚合类型:

聚合类型描述对应SQL
avg计算字段的平均值AVG()
sum计算字段的总和SUM()
min/max查找字段的最小/最大值MIN()/MAX()
stats同时返回count、sum、min、max、avg多个函数组合
cardinality计算字段不同值的数量COUNT(DISTINCT)
percentiles计算字段的百分位数无直接对应

示例:计算商品价格统计信息

json
GET /products/_search { "size": 0, "aggs": { "price_stats": { "stats": { "field": "price" } }, "distinct_brands": { "cardinality": { "field": "brand.keyword" } } } }

桶聚合(Bucket Aggregations)

桶聚合将文档分组到不同的桶中,每个桶对应一个特定的条件或范围。

常用桶聚合类型:

Terms 聚合 - 按字段值分组

json
// 按品牌分组统计商品数量 GET /products/_search { "size": 0, "aggs": { "brands": { "terms": { "field": "brand.keyword", "size": 10, "order": { "_count": "desc" } } } } }

参数说明:

  • size:指定返回的桶数量

  • order:指定桶的排序方式

Range 聚合 - 按数值范围分组

json
// 按价格区间统计商品 GET /products/_search { "size": 0, "aggs": { "price_ranges": { "range": { "field": "price", "ranges": [ { "to": 100 }, { "from": 100, "to": 500 }, { "from": 500 } ] } } } }

Date Histogram 聚合 - 按时间间隔分组

json
// 按月统计销售记录 GET /sales/_search { "size": 0, "aggs": { "sales_over_time": { "date_histogram": { "field": "sale_date", "calendar_interval": "month", "format": "yyyy-MM" } } } }

管道聚合(Pipeline Aggregations)

管道聚合以其他聚合的结果作为输入,进行二次计算。

常用管道聚合:

  • avg_bucket:计算桶的平均值

  • sum_bucket:计算桶的总和

  • max_bucket/min_bucket:找出桶的最大值/最小值

  • bucket_script:对多个聚合结果执行脚本计算

聚合实战案例

电商数据分析需求:分析每个品牌的商品数量、平均价格和价格统计信息。

json
GET /products/_search { "size": 0, "aggs": { "brand_analysis": { "terms": { "field": "brand.keyword", "size": 5 }, "aggs": { "avg_price": { "avg": { "field": "price" } }, "price_stats": { "stats": { "field": "price" } }, "price_ranges": { "range": { "field": "price", "ranges": [ { "to": 100 }, { "from": 100, "to": 1000 }, { "from": 1000 } ] } } } } } }

员工信息分析需求:分析公司不同年龄段员工的平均薪资和性别分布。

json
GET /employees/_search { "size": 0, "aggs": { "age_ranges": { "range": { "field": "age", "ranges": [ { "from": 20, "to": 30 }, { "from": 30, "to": 40 }, { "from": 40, "to": 50 }, { "from": 50 } ] }, "aggs": { "avg_salary": { "avg": { "field": "salary" } }, "gender_distribution": { "terms": { "field": "gender.keyword" } } } } } }

计算字段空值率需求:统计某个字段的空值率

json
{ "size": 0, "aggs": { "all_documents": { "terms": { "script": "return 'all_documents';" }, "aggs": { "total_count": { "value_count": { "field": "_id" } }, "non_empty_count": { "value_count": { "script": "if (doc['my_field'].size() != 0 && doc['my_field'].value != '') return 1" } }, "empty_percentage": { "bucket_script": { "buckets_path": { "total": "total_count", "nonEmpty": "non_empty_count" }, "script": "(1 - params.nonEmpty / params.total) * 100" } } } } } }

聚合性能优化

理解 doc_values 和 fielddata

Elasticsearch 聚合操作的性能很大程度上依赖于底层数据结构:

Doc Values:列式存储,适用于精确值字段,默认启用,推荐使用

Fielddata:基于内存的数据结构,用于 text 字段,谨慎使用(因为可能消耗大量堆内存,在处理大数据集时容易引发内存溢出(OOM)问题)

最佳实践

使用 keyword 类型进行聚合

对于文本字段,使用 .keyword 子字段而非原始 text 字段:

json
// 推荐 ✅ "terms": { "field": "brand.keyword" } // 避免 ❌ "terms": { "field": "brand" }

合理设置聚合大小

json
"terms": { "field": "category.keyword", "size": 10 // 限制返回的桶数量 }

结合查询条件使用聚合

json
{ "query": { "range": { "price": { "gte": 100 } } }, "aggs": { // 只在价格>=100的商品上进行聚合 } }

避免在分片字段上使用深度分页聚合

在分片数较多的索引上,使用 terms 聚合获取大量桶时可能不准确。

Java 代码示例

java
// 使用 Java High Level REST Client 进行聚合查询 SearchRequest searchRequest = new SearchRequest("products"); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); // 构建品牌分析聚合 TermsAggregationBuilder brandAggregation = AggregationBuilders.terms("brand_analysis") .field("brand.keyword") .size(10); // 添加子聚合 - 平均价格 AvgAggregationBuilder avgPriceAggregation = AggregationBuilders.avg("avg_price") .field("price"); brandAggregation.subAggregation(avgPriceAggregation); // 添加子聚合 - 价格统计 StatsAggregationBuilder priceStatsAggregation = AggregationBuilders.stats("price_stats") .field("price"); brandAggregation.subAggregation(priceStatsAggregation); sourceBuilder.aggregation(brandAggregation); sourceBuilder.size(0); // 只返回聚合结果 searchRequest.source(sourceBuilder); SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT); // 处理聚合结果 Terms brandTerms = response.getAggregations().get("brand_analysis"); for (Terms.Bucket bucket : brandTerms.getBuckets()) { String brand = bucket.getKeyAsString(); long docCount = bucket.getDocCount(); Avg avgPrice = bucket.getAggregations().get("avg_price"); double averagePrice = avgPrice.getValue(); Stats priceStats = bucket.getAggregations().get("price_stats"); double minPrice = priceStats.getMin(); double maxPrice = priceStats.getMax(); System.out.println(String.format( "品牌: %s, 商品数: %d, 平均价格: %.2f, 价格范围: %.2f-%.2f", brand, docCount, averagePrice, minPrice, maxPrice )); }

总结

通过本文,我们深入学习了:

  • 聚合核心概念:桶(Buckets)和指标(Metrics)

  • 三种聚合类型:指标聚合、桶聚合、管道聚合

  • 实战应用场景:电商分析、员工统计、空值率计算等

  • 性能优化技巧:使用 keyword 字段、合理设置聚合大小等

聚合分析的价值:

  • 业务洞察:从海量数据中提取有价值的商业信息

  • 实时分析:支持大规模数据的实时统计分析

  • 多维度分析:支持嵌套聚合,实现复杂的数据分析需求

聚合分析是 Elasticsearch 最强大的功能之一,掌握它能够让你从数据中发现隐藏的价值,为业务决策提供有力支持。

本文作者:柳始恭

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!