ElasticSearch实现GroupBy多字段分组统计

需求描述

对clientip,request,vhost 三个字段进行group by分组统计count,之后根据传入的时间进行区间筛选。
转换成对应的sql应该是

select
	MAX(@timestamp) as accessTimeStamp, 
	clientip as clientIp, request, 
	vhost, count(*) AS accessCount 
from  nginxweb-2022-08-17 
where  @timestamp  between '2021-08-17T00:24:41.000Z' 
and '2023-08-17T00:25:00.000Z' 
GROUP BY clientip, request, vhost

part1(@timestamp格式)

请看下我们的查询语句是根据@timestamp的时间区间进行数据的筛选的,这里的@timestamp字段需要特别注意:
@timestamp 字段需要注意这字段在Es中默认存储的是UTC时区、UTC、UTC是UTC呀,它长得样子为:2021-08-17T00:24:41.000Z 可以使用以下代码获取到UTC时间:

// 获取UTC 时间
DateTimeFormatter utcFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
LocalDateTime now = LocalDateTime.now(Clock.systemUTC());
String utcNow = now.format(utcFormatter);

特别需要注意@timestamp字段的时间格式,一定要确保和es中的时间存储格式相同,还有一点between和and之间的时间是自动生成的千万别用东八区的时间去查es中的UTC时间,那样打死都查不出来,别问我是怎么知道的!!需要特别注意!
如果都没有问题还是没有查出来数据注意排查下:

1. 通过es查询该字段的mapping的type类型是text还是Date
2. 如果是Date那么就认真检查自己传入的日期是否是标准的UTC时间

part2(分组的核心代码)

分组我们使用es的两个类:TermsValuesSourceBuilder、CompositeAggregationBuilder
使用CompositeAggregationBuilder我们可以很轻松的进行分组,貌似网上介绍的比较少哈,我这里也只是贴出核心代码,因为用着也比较简单哈。

// 分组
// 简单的说下,当你分组查数据的时候肯定是需要的是分组后的聚合数据,而不需要符合要求的数据
// 可以使用:searchSourceBuilder.size(0);
TermsValuesSourceBuilder clientIpGrep = new TermsValuesSourceBuilder("clientipGrep")
.field("clientip.keyword").missingBucket(true);
List sources = new ArrayList<>();
sources.add(clientIpGrep);
CompositeAggregationBuilder composite = new CompositeAggregationBuilder("groupby", sources);
// 设置每次分页数据的大小
composite.size(5000);

这样其实分组就已经完成了,之后我们只需要稍微的处理下分页就行了。
这里我们使用afterKey(在每次的返回体中)这个对象,这个对象标记本次查询的最后一条,如果这个为空,则证明数据已经查询完毕了。所有我们通过该字段进行滚动查询(使用递归)
下面的代码是把afterKey这个对象原封不动的放入请求体中,再次请求es就ok了。

if ( MapUtils.isNotEmpty(afterKey)) {
    	composite.aggregateAfter(afterKey);
    }

大概就这么多吧,总体上挺简单的,当初的时候竟然没有注意东八区和UTC的时差造成了bug,哈哈哈!还是需要努力呀!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐