前言:最近遇到了一些需求,需要统计分组后的结果数据再分组的数据,查遍资料绞尽脑汁的想解决方案。可也没有一个很好地解决方案,但最后也还是找到一个不算太好但能解决问题的办法。分享给大家。
需求背景:有一批设备可以播放广告,es存了设备的播放记录,播一次一条记录。
大概需求是这样:
1,统计每个设备播了多少次。(这个so easy啦,直接terms分组不就可以啦,但是有个问题,es默认terms的结果只展示value最多的10000条。)
2,统计每种播放次数下有多少设备,也就是播放1次的有多少设备,2次的有多少。(这个需求我想了很久,是否能通过一次语句实现,但最后还是没想出来。)
第一个需求的解决方案:利用composite + after实现(类似于es导出原数据的scroll)。
GET advertisement-2019.11.11/_search
{
"aggs": {
"groupcid": {
"composite": {
"size": 10,
"sources": [
{
"id": {
"terms": {
"field": "device_uuid"
}
}
}
]
}
}
},
"size": 0
}
首次导不需要指定after
结果:
{
"took" : 1989,
"timed_out" : false,
"_shards" : {
"total" : 3,
"successful" : 3,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 10000,
"relation" : "gte"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"groupcid" : {
"after_key" : {
"id" : "110100303038473932000c4f6b18a0f7"
},
"buckets" : [
{
"key" : {
"id" : "002b0020504d530b363531340100267d"
},
"doc_count" : 7
},
{
"key" : {
"id" : "003d0020504d53111b3236370100267d"
},
"doc_count" : 20
},
{
"key" : {
"id" : "004800204d415717463437330100267d"
},
"doc_count" : 3
},
{
"key" : {
"id" : "010004393438470f803737370150267d"
},
"doc_count" : 18
},
{
"key" : {
"id" : "0a0004373138470b003932370100267d"
},
"doc_count" : 15
},
{
"key" : {
"id" : "0a0011373138470b003932370100267d"
},
"doc_count" : 22
},
{
"key" : {
"id" : "10000a393138470f003534360003267d"
},
"doc_count" : 2
},
{
"key" : {
"id" : "1101003030384739320007590717a0a7"
},
"doc_count" : 20
},
{
"key" : {
"id" : "110100303038473932000bcd53cf311d"
},
"doc_count" : 6
},
{
"key" : {
"id" : "110100303038473932000c4f6b18a0f7"
},
"doc_count" : 18
}
]
}
}
}
可以看到,es按照分组字段进行了排序,且带有了一个after_key,这个key就是下次请求要带上的开始的key。
第二次查询:
GET advertisement-2019.11.11/_search
{
"aggs": {
"groupcid": {
"composite": {
"size": 10,
"sources": [
{
"id": {
"terms": {
"field": "device_uuid"
}
}
}
],
"after": {
"id" : "110100303038473932000c4f6b18a0f7"
}
}
}
},
"size": 0
}
看到这大家应该明白了吧,但是想要批量拉,手动是不可能的,肯定是需要代码来做的。
第二个需求的解决方案:其实这个需求很明确也很简单,无非就是把第一个需求的结果再分组就ok。但我查了很多es的api,都没发现能一步到位的方法(如果有人知道,欢迎分享)。无奈只能想办法把第一个需求的结果集存起来再分组,这就是解决方案。
那么写一下第一个需求的java实现吧,实现批量拉并写到map集合。
public void test() throws Exception{
long total = 0L;
TreeMap hashMap = new TreeMap<>();
CompositeAggregation compositeAgg = deal(null);
Map afterKey = compositeAgg.afterKey();
for (int i = 0; i < compositeAgg.getBuckets().size(); i++) {
long docCount = compositeAgg.getBuckets().get(i).getDocCount();
total += docCount;
if (hashMap.containsKey(docCount)){
hashMap.put(docCount,hashMap.get(docCount)+1);
}else {
hashMap.put(docCount,1L);
}
}
// 一直循环处理,直到没有数据
while (true){
CompositeAggregation compositeAggregation = deal(afterKey);
afterKey = compositeAggregation.afterKey();
System.out.println(compositeAggregation.getBuckets().size());
for (int i = 0; i < compositeAggregation.getBuckets().size(); i++) {
long docCount = compositeAggregation.getBuckets().get(i).getDocCount();
total += docCount;
if (hashMap.containsKey(docCount)){
hashMap.put(docCount,hashMap.get(docCount)+1);
}else {
hashMap.put(docCount,1L);
}
}
if (null == afterKey || null == afterKey.get("id")){
break;
}
}
System.out.println(hashMap.size());
System.out.println(total);
Iterator> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry next = iterator.next();
System.out.println(next.getKey()+"-"+next.getValue());
}
}
private CompositeAggregation deal(Map afterKey) throws Exception{
SearchRequest searchRequest = new SearchRequest("advertisement-2019.11.11");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
List> sources
= Collections.singletonList(new TermsValuesSourceBuilder("id").field("device_uuid").missingBucket(true).order("asc"));
CompositeAggregationBuilder composite = null;
// 每次处理7000条数据
if(afterKey == null){
composite = AggregationBuilders.composite("composite", sources).size(7000);
}else{
composite = AggregationBuilders.composite("composite", sources).aggregateAfter(afterKey).size(7000);
}
searchSourceBuilder.aggregation(composite);
searchRequest.source(searchSourceBuilder);
SearchResponse search = esFactory.getClient().search(searchRequest,RequestOptions.DEFAULT);
CompositeAggregation compositeAgg = search.getAggregations().get("composite");
return compositeAgg;
}