半年时间聚合桶数量指定都忘了 半年时间聚合桶数量指定都忘了怎么办
bigegpt 2024-10-12 06:07 8 浏览
一、版本
- elasticsearch 8.13
二、背景
最近接到一个需求,将 MySQL 中的数据迁移到 Elasticsearch 中,并且相关的业务接口全部切换为使用 Elasticsearch 实现。
其中有个统计的功能,先根据 group 进行分组,然后对每个组内对象的 type 值进行分组统计。
举个例子:对学生进行统计,相当于先按照班级分组,在统计每个班级里面男女生的人数。
我一想切换到 Elasticsearch 中,相当于嵌套子聚合,一个聚合查询就出来结果,半小时搞定这个接口。
三、问题来了
3.1、初始化数据
创建索引
PUT zuiyu_index
{
"settings": {
"number_of_replicas": 1,
"number_of_shards": 1
},
"mappings": {
"properties": {
"group": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"type": {
"type": "long"
}
}
}
}
插入测试数据
POST _bulk
{ "index" : { "_index" : "zuiyu_index", "_id" : "1" } }
{ "group" : "1","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "2" } }
{ "group" : "1","type":2 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "3" } }
{ "group" : "1","type":3 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "4" } }
{ "group" : "2","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "5" } }
{ "group" : "2","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "6" } }
{ "group" : "2","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "7" } }
{ "group" : "3","type":2 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "8" } }
{ "group" : "4","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "9" } }
{ "group" : "5","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "10" } }
{ "group" : "6","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "11" } }
{ "group" : "7","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "12" } }
{ "group" : "8","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "13" } }
{ "group" : "9","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "14" } }
{ "group" : "10","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "15" } }
{ "group" : "10","type":2 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "16" } }
{ "group" : "11","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "17" } }
{ "group" : "11","type":3 ,"sort":1}
像这种简单的一条 sql 就出来结果的业务,我一般都是先写个 sql 语句,然后根据 sql 再写代码。所以这里我就先写了个DSL语句。
3.2、聚合查询
GET zuiyu_index/_search
{
"aggregations": {
"agg_group": {
"aggregations": {
"agg_type": {
"terms": {
"field": "type"
}
}
},
"terms": {
"field": "group.keyword"
}
}
},
"query": {
"bool": {
"must": [
{
"terms": {
"group.keyword": ["1","2","3","4","5","6","7","8","9","10","11"]
}
}
]
}
},
"size":0
}
同学们可以看一下上面的语句有问题吗,如果你能发现问题,那么这篇文章也希望你能看下去,也许会有意想不到的收获。
提示一下:就像标题所说,可以关心一下聚合桶的数量。
四、发现问题
上述 DSL 语句执行之后,大眼一看 ,结果 OK,是我想要的,那就按这个逻辑直接写 Java 代码。上述 DSL 语句中聚合操作对应的 Java 代码如下:
Aggregation aggType = Aggregation.of(agg -> agg.terms(t -> t.field("type")));
Aggregation aggGroup = Aggregation.of(agg -> agg.terms(t -> t.field("group"))
.aggregations("agg_type", aggType));
做接口数据层的迁移,最简单的就是修改完业务代码之后直接对比返回结果,保持返回结果的一致,这样的修改对于前端来说没有影响。
所以,修改完代码之后直接拿接口的返回值与修改之前的版本进行比对,验证业务逻辑是否一致。直接 F12 控制台,找到该接口的返回值,复制,粘贴到对比工具中,进行对比。
此处使用的对比工具是 Beyond Compare 。
通过对比返回结果发现,聚合桶的数量少了一个。
上面的例子中,我们的预期结果是,最外层 group 的分组最少11个,排除 group 不存在的情况。这里 terms 中条件 group 在索引中都已存在。
然后我就赶紧再去执行了一遍上面 DSL 语句,一个一个的验证聚合桶,发现返回结果中竟然没有 group=9 的桶存在。
DSL 返回结果如下
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 17,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"agg_group": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 1,
"buckets": [
{
"key": "1",
"doc_count": 3,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 1
},
{
"key": 2,
"doc_count": 1
},
{
"key": 3,
"doc_count": 1
}
]
}
},
{
"key": "2",
"doc_count": 3,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 3
}
]
}
},
{
"key": "10",
"doc_count": 2,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 1
},
{
"key": 2,
"doc_count": 1
}
]
}
},
{
"key": "11",
"doc_count": 2,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 1
},
{
"key": 3,
"doc_count": 1
}
]
}
},
{
"key": "3",
"doc_count": 1,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 2,
"doc_count": 1
}
]
}
},
{
"key": "4",
"doc_count": 1,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 1
}
]
}
},
{
"key": "5",
"doc_count": 1,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 1
}
]
}
},
{
"key": "6",
"doc_count": 1,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 1
}
]
}
},
{
"key": "7",
"doc_count": 1,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 1
}
]
}
},
{
"key": "8",
"doc_count": 1,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 1
}
]
}
}
]
}
}
}
到了这,其实我还没想到是什么原因造成的,然后看了好几遍的聚合语句,都没有发现问题。一度的自我怀疑,聚合不是这样用的吗,嵌套的聚合难道还有花样?
- 查阅官方文档
官方文档肯定是最权威的,所以去官方文档看看吧,是不是可以给自己点灵感,找到解决方案。
首先去的是如下地址:
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html#run-sub-aggs
官方给的代码示例好简单,确实没毛病,对我没啥启发,告辞转下个网页。
还是这个网页,回到页面顶端,有一个 Bucket 字样的地方,点进去。
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket.html
在这个页面,发现可以通过 search.max_buckets 设置请求返回聚合桶的总数,然后我脑海中那丢失的记忆回来了。
想起了在 Elasticsearch 聚合时,聚合桶的返回数量是可以指定的,但是怎么指定,参数是什么,我又忘了。
但是大方向肯定是这个了,我就开始找相关的资料,翻阅官网关于聚合的文档,终于在官方文档的嵌套聚合中找到了相关的说明。
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-composite-aggregation.html#_size
大体意思就是size参数可以控制返回聚合桶的数量,默认 10。
但是这里也没有给出示例,我还是不会用啊。
毕竟咱也是有点 ES 基础的,内心其实已经有了想法,大概知道怎么用了,只是还得需要确认下。
最后想起来之前写过关于聚合的文章,抱着试试看的态度,在回顾一下吧。
聚合在Elasticsearch中的使用及示例验证
发文日期,2023年8月2日,真是老了,才半年多的时间都忘了,好了回到主题。
在这篇文档中,发现了 size 参数的使用,在这里终于确认,聚合桶数量需要指定,并且根据自己的查询条件进行设置,或直接设置一个最大值兼容自己所有的聚合请求。
所以修改之后的 DSL 语句如下:
GET zuiyu_index/_search
{
"aggregations": {
"agg_group": {
"aggregations": {
"agg_type": {
"terms": {
"field": "type"
}
}
},
"terms": {
"field": "group.keyword",
"size": 11
}
}
},
"query": {
"bool": {
"must": [
{
"terms": {
"group.keyword": ["1","2","3","4","5","6","7","8","9","10","11"]
}
}
]
}
},
"size":0
}
相对应的 Java 代码也修改:
Aggregation aggType = Aggregation.of(agg -> agg.terms(t -> t.field("type")));
Aggregation aggGroup = Aggregation.of(agg -> agg.terms(t -> t.field("group").size(groupList.size())
.aggregations("agg_type", aggType));
- groupList.size() 为 terms 查询条件值的数量。
五、search.max_buckets
可以通过 _cluster 的 API 设置此参数。
PUT _cluster/settings
{
"transient": {
"search.max_buckets":100
}
}
此处使用的是 transient ,还可以使用 persistent,他俩的区别就是transient 的配置会在集群重启之后失效,persistent会持久化保存。
其中这个参数在之前的索引分片分配策略一文中讲过了,还没看过的可以跳过去看一下,链接我放下面。
Elasticsearch Index Shard Allocation 索引分片分配策略
六、总结
本文通过 demo 示例,从发现聚合桶数量丢失,到排查产生丢失的问题,最后通过 size 参数解决聚合桶数量丢失问题的过程。
意外收获的是一次请求返回聚合桶数量的总数也是可以通过 search.max_buckets设置的。
日常的积累固然重要,熟悉官方文档中相关 API 的位置也是必不可少的。
工作中你遇到问题是如何排查的呢,会不会查阅官网文档呢?欢迎在评论区沟通交流,大家一起学习进步!
参考链接
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-composite-aggregation.html
https://mp.weixin.q?q.com/s/18bz77nwgC_tF8l_jYD3CA
https://mp.weixin.qq.com/s/Rlu-GDNwnAUR3tzJYk?m5Aw
相关推荐
- Go语言泛型-泛型约束与实践(go1.7泛型)
-
来源:械说在Go语言中,Go泛型-泛型约束与实践部分主要探讨如何定义和使用泛型约束(Constraints),以及如何在实际开发中利用泛型进行更灵活的编程。以下是详细内容:一、什么是泛型约束?**泛型...
- golang总结(golang实战教程)
-
基础部分Go语言有哪些优势?1简单易学:语法简洁,减少了代码的冗余。高效并发:内置强大的goroutine和channel,使并发编程更加高效且易于管理。内存管理:拥有自动垃圾回收机制,减少内...
- Go 官宣:新版 Protobuf API(go pro版本)
-
原文作者:JoeTsai,DamienNeil和HerbieOng原文链接:https://blog.golang.org/a-new-go-api-for-protocol-buffer...
- Golang开发的一些注意事项(一)(golang入门项目)
-
1.channel关闭后读的问题当channel关闭之后再去读取它,虽然不会引发panic,但会直接得到零值,而且ok的值为false。packagemainimport"...
- golang 托盘菜单应用及打开系统默认浏览器
-
之前看到一个应用,用go语言编写,说是某某程序的windows图形化客户端,体验一下发现只是一个托盘,然后托盘菜单的控制面板功能直接打开本地浏览器访问程序启动的webserver网页完成gui相关功...
- golang标准库每日一库之 io/ioutil
-
一、核心函数概览函数作用描述替代方案(Go1.16+)ioutil.ReadFile(filename)一次性读取整个文件内容(返回[]byte)os.ReadFileioutil.WriteFi...
- 文件类型更改器——GoLang 中的 CLI 工具
-
我是如何为一项琐碎的工作任务创建一个简单的工具的,你也可以上周我开始玩GoLang,它是一种由Google制作的类C编译语言,非常轻量和快速,事实上它经常在Techempower的基准测...
- Go (Golang) 中的 Channels 简介(golang channel长度和容量)
-
这篇文章重点介绍Channels(通道)在Go中的工作方式,以及如何在代码中使用它们。在Go中,Channels是一种编程结构,它允许我们在代码的不同部分之间移动数据,通常来自不同的goro...
- Golang引入泛型:Go将Interface「」替换为“Any”
-
现在Go将拥有泛型:Go将Interface{}替换为“Any”,这是一个类型别名:typeany=interface{}这会引入了泛型作好准备,实际上,带有泛型的Go1.18Beta...
- 一文带你看懂Golang最新特性(golang2.0特性)
-
作者:腾讯PCG代码委员会经过十余年的迭代,Go语言逐渐成为云计算时代主流的编程语言。下到云计算基础设施,上到微服务,越来越多的流行产品使用Go语言编写。可见其影响力已经非常强大。一、Go语言发展历史...
- Go 每日一库之 java 转 go 遇到 Apollo?让 agollo 来平滑迁移
-
以下文章来源于GoOfficialBlog,作者GoOfficialBlogIntroductionagollo是Apollo的Golang客户端Apollo(阿波罗)是携程框架部门研...
- Golang使用grpc详解(golang gcc)
-
gRPC是Google开源的一种高性能、跨语言的远程过程调用(RPC)框架,它使用ProtocolBuffers作为序列化工具,支持多种编程语言,如C++,Java,Python,Go等。gR...
- Etcd服务注册与发现封装实现--golang
-
服务注册register.gopackageregisterimport("fmt""time"etcd3"github.com/cor...
- Golang:将日志以Json格式输出到Kafka
-
在上一篇文章中我实现了一个支持Debug、Info、Error等多个级别的日志库,并将日志写到了磁盘文件中,代码比较简单,适合练手。有兴趣的可以通过这个链接前往:https://github.com/...
- 如何从 PHP 过渡到 Golang?(php转golang)
-
我是PHP开发者,转Go两个月了吧,记录一下使用Golang怎么一步步开发新项目。本着有坑填坑,有错改错的宗旨,从零开始,开始学习。因为我司没有专门的Golang大牛,所以我也只能一步步自己去...
- 一周热门
- 最近发表
- 标签列表
-
- mybatiscollection (79)
- mqtt服务器 (88)
- keyerror (78)
- c#map (65)
- xftp6 (83)
- bt搜索 (75)
- c#var (76)
- xcode-select (66)
- mysql授权 (74)
- 下载测试 (70)
- linuxlink (65)
- pythonwget (67)
- androidinclude (65)
- libcrypto.so (74)
- linux安装minio (74)
- ubuntuunzip (67)
- vscode使用技巧 (83)
- secure-file-priv (67)
- vue阻止冒泡 (67)
- jquery跨域 (68)
- php写入文件 (73)
- kafkatools (66)
- mysql导出数据库 (66)
- jquery鼠标移入移出 (71)
- 取小数点后两位的函数 (73)