MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

存储桶聚合:ElasticSearch数据分组基础

2024-10-066.3k 阅读

存储桶聚合基础概念

在 Elasticsearch 中,存储桶聚合(Bucket Aggregations)是一种强大的数据分组方式。它能够按照特定的条件对文档进行分组,将相似的文档划分到同一个 “桶” 中。就如同我们在日常生活中整理物品,将同一类别的物品放在同一个容器里。存储桶聚合在数据分析场景中极为重要,它能帮助我们从海量数据中提取有价值的信息,进行分类统计等操作。

常见的存储桶聚合类型

  1. Terms 聚合

    • 原理:Terms 聚合根据文档中某个字段的值进行分组。它会对该字段的每个唯一值创建一个桶,每个桶包含具有该特定值的所有文档。例如,对于一个包含 “product_type” 字段的商品数据集,我们可以使用 Terms 聚合按产品类型对商品进行分组,这样就能知道每种产品类型有多少个商品。
    • 代码示例
    {
        "aggs": {
            "product_type_buckets": {
                "terms": {
                    "field": "product_type"
                }
            }
        }
    }
    

    在上述示例中,“aggs” 是聚合的根节点,“product_type_buckets” 是我们给这个聚合起的名字,可以自定义。“terms” 表示使用 Terms 聚合类型,“field” 指明了要根据 “product_type” 字段进行分组。

  2. Date Histogram 聚合

    • 原理:Date Histogram 聚合主要用于日期类型的字段。它会根据指定的时间间隔对日期进行分组,将落在同一时间间隔内的文档划分到同一个桶中。比如,我们有一个包含 “order_date” 字段的订单数据集,我们可以按天、周、月等时间间隔对订单进行分组,统计每天、每周或每月的订单数量。
    • 代码示例
    {
        "aggs": {
            "order_date_buckets": {
                "date_histogram": {
                    "field": "order_date",
                    "calendar_interval": "day"
                }
            }
        }
    }
    

    这里 “order_date_buckets” 是聚合名称,“date_histogram” 表明使用日期直方图聚合,“field” 为 “order_date” 日期字段,“calendar_interval” 设置为 “day” 表示按天进行分组。如果要按周分组,可以将 “calendar_interval” 设置为 “week”。

  3. Range 聚合

    • 原理:Range 聚合用于根据数值范围对文档进行分组。我们可以定义多个数值范围,文档会根据其某个数值字段的值被划分到相应的范围桶中。例如,对于一个包含 “price” 字段的商品数据集,我们可以设置不同的价格范围,如 0 - 100,101 - 200 等,统计每个价格范围内的商品数量。
    • 代码示例
    {
        "aggs": {
            "price_ranges": {
                "range": {
                    "field": "price",
                    "ranges": [
                        {
                            "from": 0,
                            "to": 100
                        },
                        {
                            "from": 101,
                            "to": 200
                        }
                    ]
                }
            }
        }
    }
    

    “price_ranges” 是聚合名称,“range” 表示使用 Range 聚合,“field” 为 “price” 价格字段,“ranges” 数组中定义了具体的价格范围。

存储桶聚合的嵌套与组合

嵌套聚合

在 Elasticsearch 中,我们可以在一个聚合中嵌套另一个聚合。这使得我们能够进行更复杂的数据分析。例如,我们先按 “product_type” 进行 Terms 聚合,然后在每个产品类型的桶内再按 “brand” 进行 Terms 聚合,这样就能了解每个产品类型下不同品牌的分布情况。

  • 代码示例
{
    "aggs": {
        "product_type_buckets": {
            "terms": {
                "field": "product_type"
            },
            "aggs": {
                "brand_buckets": {
                    "terms": {
                        "field": "brand"
                    }
                }
            }
        }
    }
}

在这个例子中,“product_type_buckets” 是外层的 Terms 聚合,根据 “product_type” 字段分组。“aggs” 节点下的 “brand_buckets” 是内层的 Terms 聚合,在每个 “product_type” 桶内根据 “brand” 字段进一步分组。

组合聚合

除了嵌套聚合,我们还可以将不同类型的聚合组合使用。比如,我们可以先使用 Date Histogram 聚合按月份对订单进行分组,然后在每个月的桶内使用 Terms 聚合按 “customer_type” 对订单进行分组,这样就能了解每个月不同客户类型的订单分布情况。

  • 代码示例
{
    "aggs": {
        "monthly_orders": {
            "date_histogram": {
                "field": "order_date",
                "calendar_interval": "month"
            },
            "aggs": {
                "customer_type_buckets": {
                    "terms": {
                        "field": "customer_type"
                    }
                }
            }
        }
    }
}

这里 “monthly_orders” 是外层的 Date Histogram 聚合,按月份对 “order_date” 进行分组。内层的 “customer_type_buckets” 是 Terms 聚合,在每个月的桶内按 “customer_type” 分组。

存储桶聚合与文档过滤

基于文档过滤的聚合

在进行存储桶聚合时,我们常常需要先对文档进行过滤,只对符合特定条件的文档进行聚合操作。例如,我们只想统计价格大于 100 的商品按 “product_type” 的分组情况。这就需要使用过滤(Filter)与聚合结合的方式。

  • 代码示例
{
    "query": {
        "range": {
            "price": {
                "gt": 100
            }
        }
    },
    "aggs": {
        "product_type_buckets": {
            "terms": {
                "field": "product_type"
            }
        }
    }
}

在这个例子中,“query” 部分使用 “range” 查询过滤出价格大于 100 的文档。然后 “aggs” 部分对这些过滤后的文档按 “product_type” 进行 Terms 聚合。

聚合内的过滤

除了在全局层面过滤文档,我们还可以在聚合内部进行过滤。例如,我们在按 “product_type” 进行 Terms 聚合时,只想统计 “product_type” 为 “electronics” 且价格大于 100 的文档数量。这可以通过 “filter” 子聚合来实现。

  • 代码示例
{
    "aggs": {
        "product_type_buckets": {
            "terms": {
                "field": "product_type"
            },
            "aggs": {
                "electronics_over_100": {
                    "filter": {
                        "bool": {
                            "must": [
                                {
                                    "term": {
                                        "product_type": "electronics"
                                    }
                                },
                                {
                                    "range": {
                                        "price": {
                                            "gt": 100
                                        }
                                    }
                                }
                            ]
                        }
                    }
                }
            }
        }
    }
}

这里 “product_type_buckets” 是按 “product_type” 进行的 Terms 聚合。在其内部的 “aggs” 中,“electronics_over_100” 是一个 “filter” 子聚合,它使用 “bool” 查询过滤出 “product_type” 为 “electronics” 且价格大于 100 的文档。

存储桶聚合的排序与限制

排序

在存储桶聚合的结果中,默认情况下,桶是按照文档数量降序排列的。但我们可以根据自己的需求进行排序。例如,对于 Terms 聚合,我们可以按桶的键(即字段的唯一值)升序排列,或者按某个指标(如文档数量)升序或降序排列。

  • 按桶的键升序排列的代码示例
{
    "aggs": {
        "product_type_buckets": {
            "terms": {
                "field": "product_type",
                "order": {
                    "_key": "asc"
                }
            }
        }
    }
}

这里 “order” 部分指定了按 “_key”(即桶的键)升序排列。如果要降序排列,可以将 “asc” 改为 “desc”。

  • 按文档数量升序排列的代码示例
{
    "aggs": {
        "product_type_buckets": {
            "terms": {
                "field": "product_type",
                "order": {
                    "_count": "asc"
                }
            }
        }
    }
}

这里 “_count” 表示文档数量,按文档数量升序排列。

限制

在某些情况下,我们可能只需要获取聚合结果中的前几个桶。例如,我们只想知道销量最高的前 5 种产品类型。这可以通过设置 “size” 参数来实现。

  • 代码示例
{
    "aggs": {
        "product_type_buckets": {
            "terms": {
                "field": "product_type",
                "size": 5
            }
        }
    }
}

上述示例中,“size” 设置为 5,这意味着只返回按默认排序(文档数量降序)的前 5 个产品类型的桶。

存储桶聚合的指标计算

在每个存储桶聚合生成的桶内,我们可以进一步计算各种指标,如平均值、总和、最大值、最小值等。这些指标能帮助我们更深入地了解每个分组的数据特征。

平均值指标

例如,我们按 “product_type” 进行 Terms 聚合后,想知道每个产品类型的平均价格。这可以通过在 Terms 聚合内添加 “avg” 指标聚合来实现。

  • 代码示例
{
    "aggs": {
        "product_type_buckets": {
            "terms": {
                "field": "product_type"
            },
            "aggs": {
                "average_price": {
                    "avg": {
                        "field": "price"
                    }
                }
            }
        }
    }
}

这里 “product_type_buckets” 是按 “product_type” 进行的 Terms 聚合,“average_price” 是在每个产品类型桶内计算平均价格的 “avg” 指标聚合,“field” 为 “price” 价格字段。

总和指标

如果我们想知道每个产品类型的总销售额(假设数据集中有 “price” 和 “quantity” 字段,销售额 = 价格 * 数量),可以使用 “sum” 指标聚合。

  • 代码示例
{
    "aggs": {
        "product_type_buckets": {
            "terms": {
                "field": "product_type"
            },
            "aggs": {
                "total_sales": {
                    "sum": {
                        "script": {
                            "source": "doc['price'].value * doc['quantity'].value"
                        }
                    }
                }
            }
        }
    }
}

在这个例子中,“total_sales” 是计算总销售额的 “sum” 指标聚合,通过 “script” 脚本计算每个文档的销售额(价格乘以数量)并求和。

最大值与最小值指标

我们还可以获取每个产品类型中的最高价格和最低价格。

  • 获取最大值的代码示例
{
    "aggs": {
        "product_type_buckets": {
            "terms": {
                "field": "product_type"
            },
            "aggs": {
                "max_price": {
                    "max": {
                        "field": "price"
                    }
                }
            }
        }
    }
}
  • 获取最小值的代码示例
{
    "aggs": {
        "product_type_buckets": {
            "terms": {
                "field": "product_type"
            },
            "aggs": {
                "min_price": {
                    "min": {
                        "field": "price"
                    }
                }
            }
        }
    }
}

上述两个示例分别通过 “max” 和 “min” 指标聚合获取每个产品类型桶内的最大和最小价格。

存储桶聚合的深度分页

在处理大量数据的聚合时,可能会遇到需要获取聚合结果的多页数据的情况。这就涉及到深度分页。Elasticsearch 提供了两种主要的方式来处理深度分页:“from” 和 “size” 参数以及 “search_after”。

“from” 和 “size” 参数

这是最基本的分页方式。“from” 表示从结果集的第几项开始返回,“size” 表示返回的结果数量。例如,我们想获取按 “product_type” 进行 Terms 聚合结果的第 11 - 20 项。

  • 代码示例
{
    "aggs": {
        "product_type_buckets": {
            "terms": {
                "field": "product_type",
                "from": 10,
                "size": 10
            }
        }
    }
}

这里 “from” 设置为 10(从第 11 项开始,因为索引从 0 开始),“size” 设置为 10,表示返回 10 项。然而,这种方式在处理深度分页时性能较差,因为 Elasticsearch 需要在每个分片上计算完整的排序结果,然后再合并。

“search_after”

“search_after” 是一种更高效的深度分页方式。它通过使用上一页的最后一个文档的排序值来获取下一页的数据。例如,我们先获取第一页数据,假设最后一个文档的 “price” 字段值为 150,我们可以使用这个值来获取下一页数据。

  • 获取第一页数据的代码示例
{
    "sort": [
        {
            "price": {
                "order": "asc"
            }
        }
    ],
    "size": 10,
    "aggs": {
        "product_type_buckets": {
            "terms": {
                "field": "product_type"
            }
        }
    }
}
  • 获取第二页数据的代码示例
{
    "sort": [
        {
            "price": {
                "order": "asc"
            }
        }
    ],
    "search_after": [150],
    "size": 10,
    "aggs": {
        "product_type_buckets": {
            "terms": {
                "field": "product_type"
            }
        }
    }
}

在获取第二页数据时,“search_after” 设置为第一页最后一个文档的 “price” 值(这里假设为 150),这样 Elasticsearch 可以根据这个值更高效地获取下一页数据,避免了在每个分片上计算完整排序结果的开销。

存储桶聚合在实际场景中的应用

电商数据分析

在电商场景中,存储桶聚合有着广泛的应用。例如,按商品类别(“category” 字段)进行 Terms 聚合,统计每个类别下的商品数量,了解不同类别商品的受欢迎程度。同时,可以在每个类别桶内计算平均价格、总销售额等指标,分析不同类别商品的销售情况。

  • 代码示例
{
    "aggs": {
        "category_buckets": {
            "terms": {
                "field": "category"
            },
            "aggs": {
                "average_price": {
                    "avg": {
                        "field": "price"
                    }
                },
                "total_sales": {
                    "sum": {
                        "script": {
                            "source": "doc['price'].value * doc['quantity'].value"
                        }
                    }
                }
            }
        }
    }
}

通过这样的聚合操作,电商运营人员可以了解哪些商品类别更受欢迎,哪些类别虽然商品数量少但销售额高,从而制定更合理的商品策略。

日志分析

在日志分析场景中,假设我们有一个包含 “timestamp”(时间戳)和 “log_level”(日志级别)字段的日志数据集。我们可以使用 Date Histogram 聚合按小时对日志进行分组,然后在每个小时桶内使用 Terms 聚合按日志级别统计日志数量,了解不同时间段内不同日志级别的分布情况。

  • 代码示例
{
    "aggs": {
        "hourly_logs": {
            "date_histogram": {
                "field": "timestamp",
                "calendar_interval": "hour"
            },
            "aggs": {
                "log_level_buckets": {
                    "terms": {
                        "field": "log_level"
                    }
                }
            }
        }
    }
}

通过这种方式,运维人员可以快速定位哪些时间段内出现了较多的错误日志(假设 “log_level” 有 “error”、“warning” 等级别),以便及时排查问题。

社交媒体数据分析

在社交媒体场景中,假设我们有用户发布内容的数据集,包含 “user_type”(用户类型,如普通用户、认证用户)、“post_time”(发布时间)和 “engagement”(互动量,如点赞数、评论数之和)字段。我们可以先按用户类型进行 Terms 聚合,然后在每个用户类型桶内使用 Date Histogram 聚合按天对发布时间进行分组,并计算每天的平均互动量。

  • 代码示例
{
    "aggs": {
        "user_type_buckets": {
            "terms": {
                "field": "user_type"
            },
            "aggs": {
                "daily_engagement": {
                    "date_histogram": {
                        "field": "post_time",
                        "calendar_interval": "day"
                    },
                    "aggs": {
                        "average_engagement": {
                            "avg": {
                                "field": "engagement"
                            }
                        }
                    }
                }
            }
        }
    }
}

这样,社交媒体运营人员可以了解不同类型用户每天的内容互动情况,从而制定更有针对性的运营策略,提高用户参与度。

通过以上对 Elasticsearch 存储桶聚合的详细介绍,包括基础概念、各种聚合类型、嵌套与组合、过滤、排序、指标计算、深度分页以及实际应用场景等方面,相信读者对存储桶聚合有了全面而深入的理解,能够在实际的数据分析工作中灵活运用这一强大的功能。