文章二十九:ElasticSearch分桶聚合

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_typemember_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_ordinalsmapglobal_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_boundshistogram 直方图聚合专属参数 ,作用只有一句话: 强制指定直方图的「最小 / 最大区间范围」,哪怕这个区间里没有数据,也会返回 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 聚合拥有独立的隔离作用域

无论外层查询是否配置 queryboolfilter 等任意过滤规则,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. 总结

  1. 分桶聚合是 ES 出行数据分析的核心,可实现骑行订单的维度分组、时序统计、区间拆分,完全替代传统 SQL 的 GROUP BY 统计能力。

  2. 五大核心分桶场景完美适配骑行业务:词条分桶(车型、用户类型分组)、范围分桶(站点区间统计)、时间分桶(自定义时段统计)、数值直方图(站点均匀分段)、时间直方图(出行时序报表)。

  3. size、order、shard_size 是生产必备核心参数:size 控制展示桶数、order 控制排序规则、shard_size 解决分布式聚合数据失真,三者配合可实现精准、可控的聚合统计。

  4. 嵌套聚合可实现用户、车型、时段、站点的多维组合分析,满足共享单车出行统计、用户行为分析、时段流量监控等核心业务需求。生产落地需规避参数使用误区,配合优化策略保障海量骑行数据的聚合查询性能与准确度。

10. 拓展业务场景

基于本文语法,可快速延伸实现:城市骑行高峰时段分析、热门起止站点统计、不同车型用户出行偏好、节假日骑行数据对比、骑行时长分布分析等生产级数据看板功能。

相关推荐
AI极客菌9 分钟前
AI绘画工具中,为什么专业玩家爱用Stable Diffusion,普通玩家却喜欢Midjourney?
大数据·人工智能·ai·ai作画·stable diffusion·aigc·midjourney
腾视科技AI12 分钟前
腾视科技大模型一体机解决方案:低成本私有化落地,重塑行业智能应用新格局
大数据·人工智能·科技·ai·边缘计算·算力·ai算力
金融支付架构实战指南1 小时前
支付系统 ES 实战案例:从索引创建到真实业务查询
大数据·elasticsearch·搜索引擎·支付
alexhilton2 小时前
AppFunctions:让你的Android应用更容易被AI智能体发现
android·kotlin·android jetpack
qq3621967052 小时前
APK文件签名校验教程:验证APK真伪的完整方法
android·智能手机
赏金术士2 小时前
Android 组件化概念和特征
android·kotlin·组件化
百胜软件@百胜软件3 小时前
从“数据孤岛”到“智利标杆”:百胜E3全渠道中台助力“名创优品”Newtree实现一体化智变
大数据·人工智能·零售数字化·数智中台·珠宝行业
lizhihai_993 小时前
股市学习心得-A股服务器/算力服务器龙头
大数据·运维·服务器·人工智能·科技·学习
AllData公司负责人4 小时前
大模型赋能AllData数据中台,系列升级|通过联合智谱大模型与BiSheng开源项目,建设企业大模型应用开发平台,支持知识库向量检索!
大数据·数据结构·数据库·算法·大模型·向量数据库·智谱ai