1. 文档概述
本文聚焦 ElasticSearch(简称 ES)核心能力------分桶聚合(Bucket Aggregation) ,从核心原理、聚合分类、核心参数详解、语法详解、实战案例、嵌套组合、踩坑问题及性能优化全方位讲解。分桶聚合是 ES 数据分析的核心,核心作用是将海量文档按照指定规则划分为若干数据桶,实现数据分组统计,对标 MySQL 中 GROUP BY 语法,广泛应用于日志分析、业务统计、出行数据分析、数据看板、多维数据分析等场景。
本文所有案例基于 ES 7.x/8.x 通用语法,适配主流生产环境,所有 DSL 语句可直接复制测试。全文统一采用**骑行出行数据集**作为测试样本,贴合真实时序业务场景,同时重点讲解 size、order、shard_size 等生产必备聚合参数,适合开发、运维、数据分析人员落地实操。
2. 聚合核心基础
2.1 ES 分桶聚合最大桶数限制
作用定位
全局限制一次查询中所有聚合允许生成的最大桶总数,防止高基数聚合(如大量站点 ID、用户 ID 分组)产生过多桶导致 OOM(内存溢出)。
默认值
65535
两个级别(非常重要)
1)集群级别(全局生效)
控制整个 ES 集群所有聚合请求的最大桶数。
PUT _cluster/settings
{
"persistent": {
"search.max_buckets": 100000
}
}
2)索引级别(仅当前索引生效)
只对指定索引的聚合生效,不会影响其他业务。
PUT bike_ride/_settings
{
"index.search.max_buckets": 100000
}
触发后果
超过该值会直接报错:Too many buckets, maximum value is: 65535
这个数量只有在特定的业务下可以尝试着自定义,但是一定要注意过大的数量会导致OOM.
2.2 核心语法说明
ES 聚合查询统一通过 aggs(可简写为 aggregations)关键字定义,基础语法结构如下:
GET /索引名/_search
{
"size": 0, // 关闭原始文档返回,只展示聚合结果,提升查询性能
"aggs": {
"自定义聚合名称": { // 唯一标识,用于区分多个聚合
"聚合类型": { // 分桶/指标聚合类型
"field": "字段名", // 聚合字段
"size": 20, // 聚合桶返回数量
"shard_size": 100, // 分片预聚合数量
"order": { "_count": "desc" } // 聚合排序规则
// 对应聚合类型的专属参数
},
"aggs": {} // 嵌套子聚合(实现分组后二次统计)
}
}
}
关键参数说明 :size:0 是聚合查询必备优化参数,默认 ES 会返回匹配的原始文档,关闭后仅返回聚合统计结果,大幅减少数据传输开销。
2.3 分桶聚合核心通用参数详解(重点)
分桶聚合生产落地中,size、order、shard_size 是解决「数据不全、排序不准、聚合失真」的核心参数,也是面试与生产高频重难点,本节统一详细解析并区分易混参数。
2.3.1 size 参数(聚合桶结果条数)
作用定位:控制当前聚合最终返回多少个桶结果,仅影响客户端展示数据,不影响分片预聚合过程。
默认值:10
业务问题 :生产中高基数字段(站点ID、小众车型、细分用户标签)分组数量远超10个,不手动配置 size 会导致数据丢失、统计不全。
使用场景:需要展示全部分组、展示 TopN 热门分组、批量统计维度数据。
示例:统计所有车型骑行数据,返回前20个分组
"terms": { "field": "rideable_type.keyword", "size": 20 }
重要区分 :外层 size:0代表不返回原始文档;聚合内部 size 代表返回聚合桶数量,二者作用域完全不同。
2.3.2 order 参数(聚合桶排序规则)
作用定位:对聚合产出的 bucket 桶进行排序,支持按文档数、按自定义指标、按 key 名称排序。
默认值 :按 _count desc(文档数量倒序)
支持排序规则:
-
_count:按桶内文档数量排序 -
_key:按分组字段 key 字典序/数值排序 -
自定义指标名:嵌套聚合后可按平均值、最大值等指标排序
实战示例:按骑行订单量倒序,订单量一致则按车型名称正序
"terms": { "field": "rideable_type.keyword", "order": [ {"_count": "desc"}, {"_key": "asc"} ] }
2.3.3 shard_size 参数(分片预聚合条数,生产核心)
作用定位 :ES 聚合是「分片预聚合 + 节点合并聚合」两步执行,shard_size 用于设置每个分片单独预聚合时返回的桶数量,用于解决分布式聚合数据不准问题。
默认规则:shard_size = size * 1.5 + 10
核心问题场景 :分布式多分片环境下,部分小众分组在单个分片排名靠后被截断,最终合并后TopN 数据不准、统计数值偏小。
参数价值:加大 shard_size,让每个分片多返回候选桶,合并后保证最终 size 条数据绝对准确。
最佳实践 :需要精准 TopN 统计时,shard_size 设置为 size 的 3~5倍。
实战示例:精准获取骑行订单 Top10 车型
"terms": { "field": "rideable_type.keyword", "size": 10, "shard_size": 50 }
2.3.4 min_doc_count 参数(最小文档数过滤)
作用定位:限定聚合桶的最小文档数量阈值,仅返回桶内文档数大于等于该值的结果,用于过滤空桶、无效小众桶、低频次数据,精简聚合结果。
默认值:1
参数特性 :通用型参数,支持 terms、histogram、date_histogram、range 所有分桶聚合类型。
核心取值场景:
-
min_doc_count: 1(默认):只展示有数据的桶,过滤空桶,日常统计、报表展示首选。 -
min_doc_count: 0:展示所有预设分桶,包含文档数为0的空桶,用于时序报表补全日期、区间全覆盖场景。 -
min_doc_count: N(N>1):自定义阈值,过滤订单量/数据量过少的无效维度,只保留高频有效数据。
2.3.5 shard_min_doc_count 参数(每个分片最小文档数过滤)
shard_min_doc_count 用于在分片预聚合阶段 ,提前过滤掉单个分片内文档数量过少 的桶。它的作用是:在分片层面就剔除无效小桶,减少分片间数据传输,提升聚合性能。
它和 min_doc_count 功能类似,但执行时机完全不同:
- min_doc_count :在所有分片结果合并后过滤最终桶
- shard_min_doc_count :在每个分片内部 提前过滤桶,属于性能优化参数
2.3.5三大参数核心区别总结
| 参数 | 作用域 | 核心用途 | 是否影响数据准确性 |
| min_doc_count | 当前聚合 | 返会数据大于当前数值的分桶数据 | 不影响聚合结果 |
|---|---|---|---|
| 外层 size:0 | 查询全局 | 关闭原始文档返回,提升性能 | 不影响聚合结果 |
| 聚合内 size | 当前聚合 | 控制最终展示桶数量 | 控制结果展示范围 |
| order | 当前聚合 | 控制桶排序规则 | 不影响数值,只影响顺序 |
| shard_size | 分片预聚合 | 解决分布式聚合数据失真 | 直接影响数据准确性 |
3. 测试数据集说明
本文所有实战案例统一基于 bike_ride 骑行数据集,单条完整样本数据如下,后续所有聚合场景均围绕该数据集字段设计,贴合共享单车出行统计真实业务场景:
{
"ride_id": "63AF72AB3CD47753",
"rideable_type": "classic_bike",
"started_at": "2022-01-13 21:36:47.689",
"ended_at": "2022-01-13 21:46:02.024",
"start_station_name": "5 Ave & E 63 St",
"start_station_id": "6904.06",
"end_station_name": "Broadway & W 51 St",
"end_station_id": "6779.04",
"start_location": {
"lat": 40.766368,
"lon": -73.971518
},
"end_location": {
"lat": 40.76228826,
"lon": -73.98336183
},
"member_casual": "member"
}
核心业务字段释义:
-
rideable_type:骑行车辆类型(分类维度,用于词条分桶) -
member_casual:用户类型(会员/普通用户,分类维度) -
started_at/ended_at:骑行起止时间(时序分桶核心字段) -
start_station_id/end_station_id:起止站点ID(数值、分类分桶)
4. 常用分桶聚合类型与实战案例
4.1 Terms 词条分桶(最常用)
4.1.1 原理
Terms 聚合对标 MySQL GROUP BY,根据词条精准匹配 分桶,适用于分词字段、业务分类字段,本文用于统计车辆类型、用户类型等固定维度分组数据,字段统一使用.keyword 精准聚合。
4.1.2 实战场景:结合 size/order/shard_size 精准统计车型订单Top10
业务需求:统计骑行订单量 Top10 车型,保证分布式分片聚合数据精准,按订单量倒序排序。
GET /bike_ride/_search
{
"size": 0,
"aggs": {
"ride_type_group": {
"terms": {
"field": "rideable_type.keyword",
"size": 10,
"shard_size": 50,
"order": { "_count": "desc" }
}
}
}
}
4.1.3 拓展实战:多条件排序聚合
业务需求:按用户类型分组,优先按订单量倒序,订单量相同时按用户类型字典序正序。
GET /bike_ride/_search
{
"size": 0,
"aggs": {
"user_type_group": {
"terms": {
"field": "member_casual.keyword",
"size": 5,
"order": [
{"_count": "desc"},
{"_key": "asc"}
],
"min_doc_count": 1
}
}
}
}
4.1.4 结果解析
-
buckets:分桶结果集合,每个桶对应一种车型/用户类型 -
key:桶唯一标识(车型名称、用户类型) -
doc_count:当前分组下的骑行订单总数
4.1.5 核心注意点
文本类型字段 rideable_type、member_casual 禁止直接聚合,会出现分词错乱、统计数据不准,必须使用 .keyword 子字段做精准分桶;高基数分组统计必须配置 shard_size 保证数据精准。
4.1.6:terms多级嵌套案例展示:
GET citibike/_search
{
"size": 0,
"aggs": {
"terms_start_station_name": {
"terms": {
"field": "start_station_name.keyword",
"size": 100,
"order": {
"_count": "desc"
},
"min_doc_count": 261
},
"aggs": {
"termsmember_casual": {
"terms": {
"field": "member_casual.keyword",
"size": 2
}
}
}
},
"sum_":{
"cardinality": {
"field": "start_station_name.keyword"
}
}
}
}
4.1.7:Terms + Script 脚本聚合核心原理
放弃 field 固定字段分组,使用脚本动态计算出分组值,实现灵活分组。
简单理解:不再用数据库存好的字段分组,而是现场通过代码算出一个虚拟值用来分组。
拓展案例:字符串截取分组(手机号段)
针对手机号字段,截取前3位号段分组,无需新增字段。
{
"size": 0,
"aggs": {
"phone_type": {
"terms": {
"script": {
"source": "doc['phone'].value.substring(0,3)"
}
}
}
}
}
4.1.8:Terms + runtime_mapping 运行时字段
GET citibike/_search
{
"runtime_mappings": {
"test_field": {
"type": "keyword",
"script": {
"source": """
emit( doc['started_at.keyword'].value)
"""
}
}
},
"size":0,
"aggs": {
"NAME": {
"terms": {
"field": "test_field",
"size": 10
}
}
}
}
4.1.9:collect_mode策略选择:
详细的介绍请看文章:Elasticsearch 多级嵌套 Terms 分桶:深度优先与广度优先遍历技术选型文档-CSDN博客
下面为大家展示了一下实际的案例:
- ES 默认聚合遍历策略:深度优先 depth_first。
- 在size分桶数量较少的时候使用广度优先,可以先进性剪枝操作
- 这个参数在使用的时候还是推荐显示设置一下
4.1.10 execution_hint:
execution_hint 是 ES Terms 聚合专属优化参数,用于告诉聚合引擎:当前字段聚合应该采用「全局序数映射」还是「原生值映射」方式执行,直接影响字段读取效率、桶构建速度与内存开销。
该参数不改变聚合结果,仅优化底层执行性能。
| 参数值 | 核心作用 | 底层执行逻辑 | 默认触发场景 | 优缺点 |
|---|---|---|---|---|
| global_ordinals | 基于全局序数映射执行分桶,是结构化字段主流优化方案 | 将字段所有唯一值映射为连续整型ID,通过整型对比分桶、统计、排序 | 常规固定字段聚合(keyword、int、long 等索引结构化字段) | 优点:高基数、大数据量聚合性能极致,内存占用低;缺点:首次查询需构建序数映射表,有微小初始化开销 |
| global_ordinals_hash | 兼容序数映射,适配高稀疏、超高基数字段 | 基于全局序数哈希映射分桶,不强制连续序数适配零散唯一值 | 超高基数、字段值稀疏分散的结构化字段 | 优点:解决极稀疏字段序数映射冗余问题,比原生map性能高;缺点:普通字段使用无收益,略有哈希计算开销 |
| map | 原生值哈希分桶,无序数映射转换 | 直接读取文档原始字段值,通过HashMap存储键值对完成分桶统计 | 脚本聚合、自定义动态字段、无序数字段聚合 | 优点:无需构建序数表,小数据量、小结果集查询更快;缺点:大数据量字符串对比耗时高,性能差 |
| bytes_hash | 针对字节数组类型字段专属优化分桶 | 将字节类型字段做哈希运算后分桶,适配二进制、特殊字节字段 | bytes 类型字段、未分词二进制索引字段聚合 | 优点:专属适配字节字段,规避字节值直接对比的性能缺陷;缺点:仅支持bytes类型,通用性极差 |
map:不使用全局序数映射,直接读取文档的原始字段值,通过 HashMap 结构直接完成值匹配、分桶统计。全程无序数翻译、无映射表构建,直接原生值运算。
简单理解:不做翻译,直接用原始值对比分桶,省去映射开销,但对比速度慢。
global_ordinals:
重点总结 :日常开发 99% 场景仅用到 global_ordinals 和 map;global_ordinals_hash 为超高稀疏基数字段专属优化;bytes_hash 为极少用的字节类型字段专属参数。
ES 会对 keyword、integer 等常规结构化字段,预先构建全局序数映射表:将字段所有唯一字符串/数值,映射为连续的整型数字(0、1、2、3...)。
聚合时,引擎不再直接对比原始字符串或大数值,而是通过整型序数匹配完成分桶、统计、排序,极大降低计算开销与对比成本。
简单理解:把复杂的原始字段值,翻译成简单数字,用数字分桶,速度极快。
使用推荐:
-
数据量小、结果集少、高频小查询 → 用 map不用构建序数映射表,省去初始化开销,启动更快、更轻量。
-
数据量大、高基数、海量文档聚合 → 用 global_ordinals用数字代替字符串分桶,对比速度极快,节省内存、性能碾压 map
使用异步客户端查询:
POST citibike/_async_search?wait_for_completion_timeout=1ms
{
"size": 0,
"aggs": {
"terms_start_station_name": {
"terms": {
"field": "start_station_name.keyword",
"size": 1260,
"order": {
"_count": "desc"
},
"min_doc_count": 0,
"collect_mode": "depth_first",
"include": ".*",
"execution_hint": "map"
},
"aggs": {
"termsmember_casual": {
"terms": {
"field": "member_casual.keyword",
"size": 2,
"include": "member",
"execution_hint": "global_ordinals"
}
}
}
},
"sum_":{
"cardinality": {
"field": "start_station_name.keyword"
}
}
}
}
GET _async_search/Flc3Z3hfOThoVEVpNzJuVFZFMjJfSncdbmZWM1YwaEJSUVNSenVIeUxyazBGUToxODcxOTU
4.1.11使用text类型字段进行分桶聚合实战:
我们在使用text类型进行聚合时,会显示错误:
"reason": "Fielddata is disabled on [start_station_name] in [citibike-202201]. Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead. Alternatively, set fielddata=true on [start_station_name] in order to load field data by uninverting the inverted index. Note that this can use significant memory."
查看错误信息之后我们可以发现,我们需要将Fielddata设置成true就行了。
通过修改字段mapping中的属性之后就可以聚合了:
PUT citibike/_mapping
{
"properties": {
"start_station_name": {
"fielddata": true,
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
4.2 Range 范围分桶
4.2.1 原理
根据数值区间自定义分桶规则,适用于站点ID、经纬度、骑行时长等数值维度的区间分组,灵活适配业务分级统计场景。Range 聚合同样支持 size、order 通用参数。
4.2.2 实战场景:按起始站点ID区间分桶统计订单量
java
GET kibana_sample_data_flights/_search
{
"size":0,
"aggs": {
"range_test": {
"range": {
"field": "FlightTimeMin",
"ranges": [
{
"from": 50,
"to": 100
}
]
}
}
}
}
演示脚本scrtpt结合range使用:
java
GET kibana_sample_data_flights/_search
{
"size":0,
"aggs": {
"range_test": {
"range": {
"script": {
"source": """
doc['FlightTimeMin'].value*0.1;
"""
},
"ranges": [
{
"from": 50,
"to": 100
}
]
}
}
}
}
GET kibana_sample_data_flights/_search
{
"size":0,
"aggs": {
"range_test": {
"range": {
"field": "FlightTimeMin",
"ranges": [
{
"from": 500,
"to": 1000
}
]
}
}
}
}
4.3 Date Range 时间范围分桶
4.3.1 原理
专属时间字段的范围分桶,支持时间格式化、快捷时间表达式,适用于自定义时间段骑行订单统计,可精准筛选指定时段的出行数据。
java
GET kibana_sample_data_flights/_search
{
"size":0,
"aggs": {
"range_test": {
"date_range": {
"field": "timestamp",
"ranges": [
{
"from": "now-10d/d",
"to": "now"
},
{
"from": "now-20d/d",
"to": "now-10d/d"
}
]
}
}
}
}
4.4 Histogram 数值直方图分桶
4.4.1 原理
固定间隔自动分桶,无需手动定义区间,适配均匀数值分布数据,本文用于站点ID、坐标数值的均匀分段统计。
4.4.2 实战场景:
java
// 1. 请求方式 + 索引 + API:GET 请求,查询 kibana_sample_data_flights 索引
GET kibana_sample_data_flights/_search
{
// 2. size: 0 → 不返回任何原始文档数据(只返回聚合结果,节省带宽)
"size":0,
// 3. aggs = aggregations:开启聚合计算(分组/统计/直方图等)
"aggs": {
// 4. 自定义聚合名称:你可以随便改,比如 flight_time_group
"FlightTimeMin_histogram": {
// 5. 聚合类型:histogram = 直方图聚合(按区间分组统计)
"histogram": {
// 6. 要分组的字段:飞行时间(分钟)
"field": "FlightTimeMin",
// 7. 分组区间:每 60 分钟分一组(0-60, 60-120, 120-180...)
"interval": 60
}
}
}
}
返回的数据:
java
"buckets" : [
{ "key" : 0, "doc_count" : 1234 }, // 0~60分钟:1234个航班
{ "key" : 60, "doc_count" : 567 }, // 60~120分钟:567个航班
{ "key" : 120, "doc_count" : 89 }, // 120~180分钟:89个航班
...
]
extended_bounds参数:
extended_bounds 是 histogram 直方图聚合 的专属参数 ,作用只有一句话: 强制指定直方图的「最小 / 最大区间范围」,哪怕这个区间里没有数据,也会返回 0 条,不会丢失区间。
java
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"FlightTimeMin_histogram": {
"histogram": {
"field": "FlightTimeMin",
"interval": 60,
"extended_bounds": {
"min": 0, // 强制从 0 分钟开始
"max": 300 // 强制到 300 分钟结束
}
}
}
}
}
hard_bounds:
一句话:硬边界,强制截断直方图范围,超出 min/max 的数据直接扔掉、不生成桶
java
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"FlightTimeMin_histogram": {
"histogram": {
"field": "FlightTimeMin",
"interval": 60,
"hard_bounds": {
"min": 0,
"max": 300
}
}
}
}
}
想控制范围 = extended_bounds(补 0) + hard_bounds(截断)一起用!
- extended_bounds:保证显示到 300,空桶补 0
- hard_bounds :保证不出现 360/420,截断溢出数据
总结使用展示:
java
先统计目的地国家去重总数,再按目的地国家分组,每个国家内部再按飞行时间每 50 分钟做直方图区间统计。
GET kibana_sample_data_flights/_search
{
"size":0,
"aggs": {
"count_country": {
"cardinality": {
"field": "DestCountry"
}
},
"DestCountry_terms": {
"terms": {
"field": "DestCountry",
"size": 32
},
"aggs": {
"FlightTimeMin_histogram": {
"histogram": {
"field": "FlightTimeMin",
"interval": 50
}
}
}
}
}
}
4.5 Date Histogram 时间直方图分桶
4.5.1 原理
ES 时序统计核心聚合,按固定时间间隔(小时/天/月)自动分桶,是骑行日志、出行时序数据分析的核心能力,适配日订单、小时订单统计场景。
java
GET kibana_sample_data_flights/_search
{
"size":0,
"aggs": {
"time_stamp_date_histogram": {
"date_histogram": {
"field": "timestamp",
"calendar_interval":"week",
"missing": "1000-01-01",
"time_zone": "+08:00"
}
}
}
}
4.5.2 时间格式高级属性:auto_date_histogram
uto_date_histogram:你只告诉它 "我要多少个桶",它自己选最合适的时间间隔
实战案例:
java
GET kibana_sample_data_flights/_search
{
"size":0,
"aggs": {
"time_stamp_date_histogram": {
"auto_date_histogram": {
"field": "timestamp",
"buckets":2
}
}
}
}
返回数据:
java
"aggregations": {
"time_stamp_date_histogram": {
"buckets": [
{
"key_as_string": "2026-04-01T00:00:00.000Z",
"key": 1775001600000,
"doc_count": 3495
},
{
"key_as_string": "2026-05-01T00:00:00.000Z",
"key": 1777593600000,
"doc_count": 9564
}
],
"interval": "1M"
}
}
}
composite组合分桶
composite 是一种多维度组合桶聚合 。 它可以将 多个不同的聚合 (terms、date_histogram、histogram、range)组合在一起,形成多字段分组 ,并且支持分页。
他最大的优势我认为就是可以解决分页问题:
在下次查询各种使用上次数据的after_key字段中的数据就可以进行分页的查询了。
java
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs": {
"composite_study": {
"composite": {
"size": 20,
"sources": [
{
"term_county": {
"terms": {
"field": "DestCountry",
"order": "asc"
}
}
},
{
"Origin_term": {
"terms": {
"field": "Origin"
}
}
}
],
"after": {
"term_county": "AR",
"Origin_term": "Bradley International Airport"
}
}
}
}
}
global 全局聚合
global 聚合 是 Elasticsearch 中一类特殊的作用域容器型聚合 ,其核心语义为:解除当前查询上下文的所有过滤约束,以索引内的全部有效文档作为数据统计范围,独立执行内部嵌套聚合逻辑。
该聚合本身不具备数值计算、分组统计能力,仅作为作用域修改容器,用于重置聚合的数据来源范围。
在 Elasticsearch 标准查询结构中,query / filter 过滤条件会全局作用于所有普通聚合,普通聚合仅能统计「过滤后的子集文档」。
global 聚合拥有独立的隔离作用域:
无论外层查询是否配置 query、bool、filter 等任意过滤规则,global 聚合会完全忽略所有外层过滤条件 ,强制以索引全量文档作为统计数据源,与查询过滤后的结果子集完全无关。
java
GET 索引名/_search
{
"size": 0,
"query": {
// 自定义任意过滤条件(对global无效)
},
"aggs": {
// 局部聚合:受query过滤
"局部统计别名": {
// 普通聚合逻辑
},
// 全局聚合:无视query过滤
"全局统计别名": {
"global": {},
"aggs": {
// 全量数据统计逻辑
}
}
}
}
5. 嵌套聚合实战(多维出行数据分析)
分桶聚合核心优势:支持无限嵌套,实现「一级维度分组+二级维度细分+数值统计」的多维分析,对标 MySQL 多字段分组聚合,完美适配复杂出行报表场景。
5.1 业务场景需求
统计各用户类型(会员/普通用户)下,不同车型的骑行订单数量,同时统计每组数据的平均骑行起止站点ID、最大站点ID,实现多维细分统计。
geo_distance聚合
geo_distance 是 Elasticsearch 中专用的地理空间范围查询 ,属于空间检索核心语法。其核心语义为:以指定的经纬度坐标为圆心,设定固定半径画圆形区域,筛选出索引中地理位置落在该圆形范围内的所有地理文档。
该查询专为 LBS(基于位置的服务)场景设计,可精准过滤点位地理数据,是距离筛选、附近检索、周边推荐的核心底层语法。
java
GET kibana_sample_data_flights/_search
{
"size": 0, // 不需要返回原始文档,只需要聚合统计结果
"aggs": {
"geo_dis": { // 自定义聚合名称,可随意修改
"geo_distance": {
"field": "DestLocation", // 【必填】索引中的地理点位字段,必须是 geo_point 类型
"origin": { // 【必填】中心点坐标(参考坐标)
"lat": 52.376, // 中心点:纬度
"lon": 4.894 // 中心点:经度
},
"ranges": [ // 【必填】自定义距离区间分组(支持多区间)
{
"from": 100, // 距离起始值(包含)
"to": 300 // 距离结束值(不包含)
}
],
"unit": "km", // 【可选】距离单位,默认 km(千米)
"distance_type": "arc" // 【可选】距离计算模式
}
}
}
}
-
field :参与距离计算的地理字段,必须为 geo_point 类型,否则直接报错
-
origin:基准中心点坐标,所有文档距离都以此点计算
-
lat:中心点纬度,隶属于 origin
-
lon:中心点经度,隶属于 origin
-
ranges:距离区间数组,用于自定义分组区间,实现「按距离范围统计数量」
-
from:区间起始距离,包含当前值
-
to:区间结束距离,不包含当前值
-
可选默认属性(常用)
-
unit :距离单位,默认
km,支持 m/mi/ft,适配 ranges 数值 -
distance_type:距离计算方式
-
arc(默认):球面精准计算,生产推荐
-
plane:平面快速计算,小范围可用、有误差
-
5.2 嵌套聚合完整 DSL(含参数优化)
GET /bike_ride/_search { "size": 0, "aggs": { // 一级分桶:按用户类型分组 "user_type_group": { "terms": { "field": "member_casual.keyword", "size": 10, "shard_size": 30, "order": {"_count":"desc"} }, "aggs": { // 二级分桶:用户类型下按车型细分 "bike_type_group": { "terms": { "field": "rideable_type.keyword", "size": 10 }, "aggs": { // 指标聚合:统计站点ID数值指标 "station_stats": { "stats": { "field": "start_station_id" } } } } } } } }
5.3 结果说明
该语句实现三层多维分析:全量骑行数据→按用户类型分桶→同用户类型下按车型二次分桶→每组数据统计站点ID的平均值、最大值、最小值,可直接用于出行用户画像、车型偏好分析。同时通过 shard_size 保证顶层用户类型聚合数据精准,size 控制展示维度数量。
6. 过滤后聚合实战
实际业务中常需要先过滤有效数据,再聚合统计,本文结合骑行业务场景,演示两种主流过滤聚合方案。
6.1 query+aggs:全局过滤聚合
先通过 query 筛选指定条件数据,再全局聚合,适用于固定条件的统计场景。
6.2 filter 单桶过滤聚合
不全局过滤,在聚合内部单独过滤数据,可实现一次查询、多条件细分统计,适配复杂多维对比场景。
7. 常见踩坑问题与解决方案
7.1 文本分类字段聚合数据错乱
问题:直接对 rideable_type、member_casual 等 text 字段聚合,会按分词结果拆分出大量无效小桶,统计数据失真。
解决方案 :所有分类文本字段统一使用 字段名.keyword 做精准分桶,禁止直接聚合 text 原字段。
7.2 Terms 聚合结果数据不全、TopN 不准
问题:默认 size=10 导致分组展示不全;分布式分片场景下小众分组被截断,TopN 统计数值错误。
解决方案 :根据业务维度数量调大聚合内 size;精准统计场景必须配置 shard_size(建议 size 的3~5倍),规避分片预聚合截断问题。
7.3 时间聚合空数据、时区偏移
问题:started_at 时间字段聚合出现空桶、时间区间匹配错误、数据对不上业务时段。
解决方案:统一指定时间格式化参数,服务器时区配置为 Asia/Shanghai,严格匹配数据时间格式。
7.4 直方图聚合出现大量空桶
问题:站点ID、时间直方图聚合出现大量无数据空桶,结果杂乱。
解决方案 :日常统计展示有效数据时设置 min_doc_count:1;需要补全时段报表时设置为0,并通过 extended_bounds 限定统计范围。
7.5 shard_size 使用常见误区
误区1:只改 size 不改 shard_size,认为调大 size 就能保证数据准确。
误区2:shard_size 设置过小,分片预聚合阶段丢失数据,最终合并结果失真。
正确规范 :生产 TopN 统计,必须配套使用 size + shard_size,shard_size 始终大于 size。
8. 性能优化实战
8.1 基础优化
-
强制开启
size:0,关闭骑行原始文档返回,只保留聚合结果,减少IO开销。 -
聚合字段优先使用 keyword、数值、时间类型,禁止对大文本、未优化字段聚合。
-
提前通过 query 过滤非目标时段、非目标用户数据,减少聚合计算的数据体量。
8.2 高阶优化
-
高基数维度(站点ID、骑行ID)聚合开启全局序数优化,配合shard_size 参数,大幅提升 Terms 聚合准确度与速度。
-
时序骑行数据优先使用 date_histogram 聚合,比手动 date_range 效率更高、适配性更强。
-
大数量级骑行日志统计,合理配置 size、shard_size,避免过度预聚合导致内存溢出。
9. 总结
-
分桶聚合是 ES 出行数据分析的核心,可实现骑行订单的维度分组、时序统计、区间拆分,完全替代传统 SQL 的 GROUP BY 统计能力。
-
五大核心分桶场景完美适配骑行业务:词条分桶(车型、用户类型分组)、范围分桶(站点区间统计)、时间分桶(自定义时段统计)、数值直方图(站点均匀分段)、时间直方图(出行时序报表)。
-
size、order、shard_size 是生产必备核心参数:size 控制展示桶数、order 控制排序规则、shard_size 解决分布式聚合数据失真,三者配合可实现精准、可控的聚合统计。
-
嵌套聚合可实现用户、车型、时段、站点的多维组合分析,满足共享单车出行统计、用户行为分析、时段流量监控等核心业务需求。生产落地需规避参数使用误区,配合优化策略保障海量骑行数据的聚合查询性能与准确度。
10. 拓展业务场景
基于本文语法,可快速延伸实现:城市骑行高峰时段分析、热门起止站点统计、不同车型用户出行偏好、节假日骑行数据对比、骑行时长分布分析等生产级数据看板功能。