
👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕Java中间件这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获!
文章目录
- Java 中间件:Elasticsearch 聚合查询(Terms/Max/Min 聚合) 📊
-
- 什么是 Elasticsearch 聚合?🔍
- 准备工作:环境搭建与依赖配置 ⚙️
-
- 1. Elasticsearch 服务
- 2. Java 项目依赖
-
- 方案一:使用 High Level REST Client(适用于 7.x)
- 方案二:使用新版 Java API Client(推荐用于 8.x)
- 数据模型设计:模拟电商订单 🛒
-
- 创建索引(Java API Client 示例)
- Terms 聚合:按类别分组统计 📦
-
- 聚合原理
- Java 实现(Java API Client)
- 输出结果
- 高级用法:排序与大小控制
- Max/Min 聚合:求极值 📈
-
- 场景示例
- Java 实现(单独使用)
- 组合聚合:Terms + Max/Min(子聚合)🔗
-
- 聚合结构图解
- Java 实现(子聚合)
- 输出结果
- 使用 High Level REST Client 的实现(兼容 7.x)🔄
- 性能优化与注意事项 ⚡
-
- 1. 避免高基数字段的 Terms 聚合
- 2. 合理设置 `size`
- 3. 使用 `docvalue_fields` 提升性能
- 4. 避免在 Text 字段上聚合
- 5. 监控聚合耗时
- 实际应用场景举例 🌍
-
- 场景一:用户行为分析
- 场景二:IoT 设备监控
- 场景三:金融风控
- 错误排查与常见问题 ❌
-
- Q1: 聚合返回空结果?
- Q2: Max/Min 返回 null?
- Q3: Terms 聚合结果不全?
- Q4: 客户端报 `CoercionException`?
- 扩展:与其他聚合组合使用 🧩
- 总结与展望 🎯
Java 中间件:Elasticsearch 聚合查询(Terms/Max/Min 聚合) 📊
在现代分布式系统和大数据应用中,Elasticsearch 已经成为不可或缺的搜索与分析引擎。它不仅提供强大的全文检索能力,还通过其灵活而高效的聚合(Aggregation)功能,支持对海量数据进行多维度统计、分组、指标计算等操作。对于 Java 开发者而言,掌握 Elasticsearch 的聚合查询机制,尤其是 Terms 聚合、Max 聚合 和 Min 聚合,是构建高性能数据分析平台的关键技能。
本文将深入探讨 Elasticsearch 聚合查询的核心概念,并结合 Java 客户端(High Level REST Client 及新版 Java API Client)提供完整的代码示例,帮助你从零开始理解并实现这些聚合操作。无论你是初学者还是有一定经验的开发者,都能从中获得实用的知识和最佳实践。
什么是 Elasticsearch 聚合?🔍
Elasticsearch 聚合是一种对搜索结果进行分组、统计和分析的机制。它类似于 SQL 中的 GROUP BY、MAX()、MIN() 等操作,但功能更强大、性能更高,尤其适合处理非结构化或半结构化数据。
聚合分为三大类:
本文重点聚焦于 Terms(桶聚合) 与 Max/Min(指标聚合) 的组合使用,这是实际业务中最常见的场景之一。
💡 小知识:聚合操作是在查询阶段之后执行的,因此你可以先通过 query 过滤出感兴趣的文档,再对这些文档进行聚合分析。
准备工作:环境搭建与依赖配置 ⚙️
在开始编码前,我们需要确保开发环境已正确配置。
1. Elasticsearch 服务
首先,确保本地或远程已运行 Elasticsearch 服务(建议 7.x 或 8.x 版本)。你可以通过以下命令检查服务是否正常:
curl http://localhost:9200
应返回类似如下 JSON 响应:
{
"name" : "node-1",
"cluster_name" : "elasticsearch",
"version" : {
"number" : "8.9.0",
…
}
}
2. Java 项目依赖
我们以 Maven 项目为例。根据你使用的 Elasticsearch 版本,选择对应的客户端:
方案一:使用 High Level REST Client(适用于 7.x)
⚠️ 注意:Elasticsearch 官方已在 8.0 后弃用 High Level REST Client,推荐新项目使用 Java API Client。
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.17.10</version>
</dependency>
方案二:使用新版 Java API Client(推荐用于 8.x)
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
🔗 官方文档参考:Elasticsearch Java API Client
本文将同时提供两种客户端的代码示例,但重点讲解新版 Java API Client,因其更现代、类型安全且面向未来。
数据模型设计:模拟电商订单 🛒
为了演示聚合查询,我们设计一个简单的电商订单索引 orders,包含以下字段:
| orderId | keyword | 订单 ID |
| customerId | keyword | 客户 ID |
| productName | text / keyword | 商品名称 |
| category | keyword | 商品类别(如 “Electronics”, “Books”) |
| price | double | 商品价格 |
| orderDate | date | 下单时间 |
我们将插入几条测试数据:
{ "orderId": "O001", "customerId": "C100", "productName": "iPhone 14", "category": "Electronics", "price": 999.0, "orderDate": "2023-10-01T10:00:00Z" }
{ "orderId": "O002", "customerId": "C101", "productName": "MacBook Pro", "category": "Electronics", "price": 2499.0, "orderDate": "2023-10-02T11:30:00Z" }
{ "orderId": "O003", "customerId": "C100", "productName": "Effective Java", "category": "Books", "price": 45.0, "orderDate": "2023-10-03T09:15:00Z" }
{ "orderId": "O004", "customerId": "C102", "productName": "iPad Air", "category": "Electronics", "price": 599.0, "orderDate": "2023-10-04T14:20:00Z" }
{ "orderId": "O005", "customerId": "C101", "productName": "Clean Code", "category": "Books", "price": 40.0, "orderDate": "2023-10-05T16:45:00Z" }
创建索引(Java API Client 示例)
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.indices.CreateIndexRequest;
import co.elastic.clients.transport.endpoints.BooleanResponse;
public void createOrdersIndex(ElasticsearchClient client) throws IOException {
BooleanResponse response = client.indices().create(
CreateIndexRequest.of(c -> c
.index("orders")
.mappings(m -> m
.properties("orderId", p -> p.keyword(k -> k))
.properties("customerId", p -> p.keyword(k -> k))
.properties("productName", p -> p.text(t -> t.fields("keyword", f -> f.keyword(k -> k))))
.properties("category", p -> p.keyword(k -> k))
.properties("price", p -> p.double_(d -> d))
.properties("orderDate", p -> p.date(d -> d))
)
)
);
System.out.println("Index created: " + response.acknowledged());
}
Terms 聚合:按类别分组统计 📦
Terms 聚合是最常用的桶聚合,它根据字段的唯一值对文档进行分组。例如,我们可以按 category 字段将订单分为 “Electronics” 和 “Books” 两类。
聚合原理
Terms 聚合会遍历所有文档,提取指定字段的值,然后将相同值的文档归入同一个“桶”。每个桶包含:
- key:字段值(如 “Electronics”)
- doc_count:该值出现的文档数量
Java 实现(Java API Client)
import co.elastic.clients.elasticsearch._types.aggregations.TermsAggregation;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket;
public void termsAggregationByCategory(ElasticsearchClient client) throws IOException {
SearchRequest request = SearchRequest.of(s -> s
.index("orders")
.size(0) // 不返回具体文档,只返回聚合结果
.aggregations("categories", a -> a
.terms(t -> t
.field("category")
.size(10) // 最多返回10个桶
)
)
);
SearchResponse<Void> response = client.search(request, Void.class);
var categories = response.aggregations().get("categories").sterms();
for (StringTermsBucket bucket : categories.buckets().array()) {
System.out.println("Category: " + bucket.key() + ", Count: " + bucket.docCount());
}
}
输出结果
Category: Electronics, Count: 3
Category: Books, Count: 2
高级用法:排序与大小控制
默认情况下,Terms 聚合按 doc_count 降序排列。你也可以按字母顺序排序:
.terms(t -> t
.field("category")
.order(o -> o.key(SortOrder.Asc)) // 按 key 升序
)
📌 注意:size 参数控制返回的桶数量,默认为 10。若需返回更多,可增大该值,但会影响性能。
Max/Min 聚合:求极值 📈
Max 聚合和 Min 聚合属于指标聚合,用于计算数值字段的最大值和最小值。
场景示例
- 找出最贵的商品(Max price)
- 找出最便宜的商品(Min price)
Java 实现(单独使用)
public void maxMinPriceAggregation(ElasticsearchClient client) throws IOException {
SearchRequest request = SearchRequest.of(s -> s
.index("orders")
.size(0)
.aggregations("max_price", a -> a.max(m -> m.field("price")))
.aggregations("min_price", a -> a.min(m -> m.field("price")))
);
SearchResponse<Void> response = client.search(request, Void.class);
Double maxPrice = response.aggregations().get("max_price").max().value();
Double minPrice = response.aggregations().get("min_price").min().value();
System.out.println("Max Price: " + maxPrice); // 2499.0
System.out.println("Min Price: " + minPrice); // 40.0
}
组合聚合:Terms + Max/Min(子聚合)🔗
真实业务中,我们往往需要在分组的基础上计算每组的极值。例如:
“每个商品类别的最高价格和最低价格是多少?”
这就需要用到 子聚合(Sub-Aggregation):在 Terms 聚合的每个桶内,再嵌套 Max 和 Min 聚合。
聚合结构图解
#mermaid-svg-koIivI0Ga7JlZ3pK{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-koIivI0Ga7JlZ3pK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-koIivI0Ga7JlZ3pK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-koIivI0Ga7JlZ3pK .error-icon{fill:#552222;}#mermaid-svg-koIivI0Ga7JlZ3pK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-koIivI0Ga7JlZ3pK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-koIivI0Ga7JlZ3pK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-koIivI0Ga7JlZ3pK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-koIivI0Ga7JlZ3pK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-koIivI0Ga7JlZ3pK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-koIivI0Ga7JlZ3pK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-koIivI0Ga7JlZ3pK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-koIivI0Ga7JlZ3pK .marker.cross{stroke:#333333;}#mermaid-svg-koIivI0Ga7JlZ3pK svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-koIivI0Ga7JlZ3pK p{margin:0;}#mermaid-svg-koIivI0Ga7JlZ3pK .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-koIivI0Ga7JlZ3pK .cluster-label text{fill:#333;}#mermaid-svg-koIivI0Ga7JlZ3pK .cluster-label span{color:#333;}#mermaid-svg-koIivI0Ga7JlZ3pK .cluster-label span p{background-color:transparent;}#mermaid-svg-koIivI0Ga7JlZ3pK .label text,#mermaid-svg-koIivI0Ga7JlZ3pK span{fill:#333;color:#333;}#mermaid-svg-koIivI0Ga7JlZ3pK .node rect,#mermaid-svg-koIivI0Ga7JlZ3pK .node circle,#mermaid-svg-koIivI0Ga7JlZ3pK .node ellipse,#mermaid-svg-koIivI0Ga7JlZ3pK .node polygon,#mermaid-svg-koIivI0Ga7JlZ3pK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-koIivI0Ga7JlZ3pK .rough-node .label text,#mermaid-svg-koIivI0Ga7JlZ3pK .node .label text,#mermaid-svg-koIivI0Ga7JlZ3pK .image-shape .label,#mermaid-svg-koIivI0Ga7JlZ3pK .icon-shape .label{text-anchor:middle;}#mermaid-svg-koIivI0Ga7JlZ3pK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-koIivI0Ga7JlZ3pK .rough-node .label,#mermaid-svg-koIivI0Ga7JlZ3pK .node .label,#mermaid-svg-koIivI0Ga7JlZ3pK .image-shape .label,#mermaid-svg-koIivI0Ga7JlZ3pK .icon-shape .label{text-align:center;}#mermaid-svg-koIivI0Ga7JlZ3pK .node.clickable{cursor:pointer;}#mermaid-svg-koIivI0Ga7JlZ3pK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-koIivI0Ga7JlZ3pK .arrowheadPath{fill:#333333;}#mermaid-svg-koIivI0Ga7JlZ3pK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-koIivI0Ga7JlZ3pK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-koIivI0Ga7JlZ3pK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-koIivI0Ga7JlZ3pK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-koIivI0Ga7JlZ3pK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-koIivI0Ga7JlZ3pK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-koIivI0Ga7JlZ3pK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-koIivI0Ga7JlZ3pK .cluster text{fill:#333;}#mermaid-svg-koIivI0Ga7JlZ3pK .cluster span{color:#333;}#mermaid-svg-koIivI0Ga7JlZ3pK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-koIivI0Ga7JlZ3pK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-koIivI0Ga7JlZ3pK rect.text{fill:none;stroke-width:0;}#mermaid-svg-koIivI0Ga7JlZ3pK .icon-shape,#mermaid-svg-koIivI0Ga7JlZ3pK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-koIivI0Ga7JlZ3pK .icon-shape p,#mermaid-svg-koIivI0Ga7JlZ3pK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-koIivI0Ga7JlZ3pK .icon-shape rect,#mermaid-svg-koIivI0Ga7JlZ3pK .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-koIivI0Ga7JlZ3pK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-koIivI0Ga7JlZ3pK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-koIivI0Ga7JlZ3pK :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Search Query
Terms Aggregation on 'category'
Bucket: Electronics
Bucket: Books
Max Aggregation on 'price'
Min Aggregation on 'price'
Max Aggregation on 'price'
Min Aggregation on 'price'
Java 实现(子聚合)
public void termsWithMaxMinAggregation(ElasticsearchClient client) throws IOException {
SearchRequest request = SearchRequest.of(s -> s
.index("orders")
.size(0)
.aggregations("categories", a -> a
.terms(t -> t.field("category").size(10))
.aggregations("max_price", aa -> aa.max(m -> m.field("price")))
.aggregations("min_price", aa -> aa.min(m -> m.field("price")))
)
);
SearchResponse<Void> response = client.search(request, Void.class);
var categories = response.aggregations().get("categories").sterms();
for (StringTermsBucket bucket : categories.buckets().array()) {
String category = bucket.key();
Double maxPrice = bucket.aggregations().get("max_price").max().value();
Double minPrice = bucket.aggregations().get("min_price").min().value();
System.out.printf("Category: %s, Max Price: %.2f, Min Price: %.2f%n",
category, maxPrice, minPrice);
}
}
输出结果
Category: Electronics, Max Price: 2499.00, Min Price: 599.00
Category: Books, Max Price: 45.00, Min Price: 40.00
✅ 这正是我们想要的:每个类别内部的价格极值。
使用 High Level REST Client 的实现(兼容 7.x)🔄
虽然官方推荐使用新版客户端,但许多老项目仍在使用 High Level REST Client。以下是等效实现:
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.Max;
import org.elasticsearch.search.aggregations.metrics.Min;
public void termsWithMaxMin_HLRC(RestHighLevelClient client) throws IOException {
SearchRequest searchRequest = new SearchRequest("orders");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.size(0);
// 构建 Terms 聚合,并添加子聚合
sourceBuilder.aggregation(
AggregationBuilders.terms("categories")
.field("category")
.subAggregation(AggregationBuilders.max("max_price").field("price"))
.subAggregation(AggregationBuilders.min("min_price").field("price"))
);
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
Terms categories = response.getAggregations().get("categories");
for (Terms.Bucket bucket : categories.getBuckets()) {
String category = bucket.getKeyAsString();
Max maxPrice = bucket.getAggregations().get("max_price");
Min minPrice = bucket.getAggregations().get("min_price");
System.out.printf("Category: %s, Max: %.2f, Min: %.2f%n",
category, maxPrice.getValue(), minPrice.getValue());
}
}
🔗 更多关于 High Level REST Client 的信息,请参考 Elasticsearch 7.x Java REST Client 文档
性能优化与注意事项 ⚡
聚合查询虽然强大,但在大数据量下可能带来性能挑战。以下是一些最佳实践:
1. 避免高基数字段的 Terms 聚合
如果对 customerId(假设有百万级唯一值)做 Terms 聚合,会导致内存爆炸。此时应:
- 使用 composite 聚合进行分页
- 或限制 size 并配合 include/exclude 过滤
2. 合理设置 size
默认 size=10,若业务需要更多桶,逐步增加,避免一次性拉取过多。
3. 使用 docvalue_fields 提升性能
确保聚合字段启用了 doc_values(默认开启),这是列式存储,专为聚合优化。
4. 避免在 Text 字段上聚合
Text 字段用于全文检索,不可直接聚合。应使用 .keyword 子字段(如 productName.keyword)。
5. 监控聚合耗时
通过 Kibana 或 _nodes/stats API 监控聚合请求的 CPU 和内存使用。
实际应用场景举例 🌍
场景一:用户行为分析
- 按 userType(普通/会员)分组,统计每次会话的最长/最短停留时间(Max/Min sessionDuration)
场景二:IoT 设备监控
- 按 deviceId 分组,找出每台设备今日的最高/最低温度(Max/Min temperature)
场景三:金融风控
- 按 transactionType 分组,识别每类交易的最大/最小金额,用于异常检测
📊 这些场景都可通过 Terms + Max/Min 子聚合 优雅实现。
错误排查与常见问题 ❌
Q1: 聚合返回空结果?
- 检查字段是否存在且类型正确(如 price 是否为 double)
- 确认索引中有数据:GET /orders/_search
- 确保未被 query 过滤掉所有文档
Q2: Max/Min 返回 null?
- 字段值全为 null 或 missing
- 字段类型不匹配(如字符串无法求极值)
Q3: Terms 聚合结果不全?
- 默认 size=10,增加 size 或使用 composite 聚合分页
Q4: 客户端报 CoercionException?
- 字段映射与实际数据类型不一致,检查 mapping
扩展:与其他聚合组合使用 🧩
Terms 聚合可与多种聚合组合,形成复杂分析:
#mermaid-svg-wFfundElaBMd3XJ8{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wFfundElaBMd3XJ8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wFfundElaBMd3XJ8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wFfundElaBMd3XJ8 .error-icon{fill:#552222;}#mermaid-svg-wFfundElaBMd3XJ8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wFfundElaBMd3XJ8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wFfundElaBMd3XJ8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wFfundElaBMd3XJ8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wFfundElaBMd3XJ8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wFfundElaBMd3XJ8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wFfundElaBMd3XJ8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wFfundElaBMd3XJ8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wFfundElaBMd3XJ8 .marker.cross{stroke:#333333;}#mermaid-svg-wFfundElaBMd3XJ8 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wFfundElaBMd3XJ8 p{margin:0;}#mermaid-svg-wFfundElaBMd3XJ8 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-wFfundElaBMd3XJ8 .cluster-label text{fill:#333;}#mermaid-svg-wFfundElaBMd3XJ8 .cluster-label span{color:#333;}#mermaid-svg-wFfundElaBMd3XJ8 .cluster-label span p{background-color:transparent;}#mermaid-svg-wFfundElaBMd3XJ8 .label text,#mermaid-svg-wFfundElaBMd3XJ8 span{fill:#333;color:#333;}#mermaid-svg-wFfundElaBMd3XJ8 .node rect,#mermaid-svg-wFfundElaBMd3XJ8 .node circle,#mermaid-svg-wFfundElaBMd3XJ8 .node ellipse,#mermaid-svg-wFfundElaBMd3XJ8 .node polygon,#mermaid-svg-wFfundElaBMd3XJ8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wFfundElaBMd3XJ8 .rough-node .label text,#mermaid-svg-wFfundElaBMd3XJ8 .node .label text,#mermaid-svg-wFfundElaBMd3XJ8 .image-shape .label,#mermaid-svg-wFfundElaBMd3XJ8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-wFfundElaBMd3XJ8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wFfundElaBMd3XJ8 .rough-node .label,#mermaid-svg-wFfundElaBMd3XJ8 .node .label,#mermaid-svg-wFfundElaBMd3XJ8 .image-shape .label,#mermaid-svg-wFfundElaBMd3XJ8 .icon-shape .label{text-align:center;}#mermaid-svg-wFfundElaBMd3XJ8 .node.clickable{cursor:pointer;}#mermaid-svg-wFfundElaBMd3XJ8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wFfundElaBMd3XJ8 .arrowheadPath{fill:#333333;}#mermaid-svg-wFfundElaBMd3XJ8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wFfundElaBMd3XJ8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wFfundElaBMd3XJ8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wFfundElaBMd3XJ8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wFfundElaBMd3XJ8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wFfundElaBMd3XJ8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wFfundElaBMd3XJ8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wFfundElaBMd3XJ8 .cluster text{fill:#333;}#mermaid-svg-wFfundElaBMd3XJ8 .cluster span{color:#333;}#mermaid-svg-wFfundElaBMd3XJ8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wFfundElaBMd3XJ8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wFfundElaBMd3XJ8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-wFfundElaBMd3XJ8 .icon-shape,#mermaid-svg-wFfundElaBMd3XJ8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wFfundElaBMd3XJ8 .icon-shape p,#mermaid-svg-wFfundElaBMd3XJ8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wFfundElaBMd3XJ8 .icon-shape rect,#mermaid-svg-wFfundElaBMd3XJ8 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wFfundElaBMd3XJ8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wFfundElaBMd3XJ8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wFfundElaBMd3XJ8 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Terms on category
Avg price
Sum sales
Top Hits: 最贵商品详情
Cardinality: 唯一客户数
例如,获取每个类别中最贵商品的完整信息:
.aggregations("top_expensive", aa -> aa
.topHits(t -> t
.sort(s -> s.field(f -> f.field("price").order(SortOrder.Desc)))
.size(1)
)
)
总结与展望 🎯
通过本文,我们深入学习了 Elasticsearch 中 Terms 聚合、Max 聚合 和 Min 聚合 的核心概念与 Java 实现。关键要点包括:
- ✅ Terms 聚合用于分组,Max/Min 用于求极值
- ✅ 通过子聚合实现在分组内计算指标
- ✅ 掌握新版 Java API Client 与旧版 High Level REST Client 的写法
- ✅ 了解性能优化与常见陷阱
Elasticsearch 的聚合能力远不止于此。随着你对 Histogram、Date Histogram、Percentiles、Scripted Metric 等高级聚合的探索,你将能构建更强大的实时分析系统。
🌟 最后建议:在生产环境中,始终对聚合查询进行压测,并结合业务需求合理设计索引 mapping 和查询逻辑。
Happy aggregating! 🚀
🙌 感谢你读到这里! 🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。 💡 如果本文对你有帮助,不妨 👍 点赞、📌 收藏、📤 分享 给更多需要的朋友! 💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿 🔔 关注我,不错过下一篇干货!我们下期再见!✨
网硕互联帮助中心



评论前必须登录!
注册