简介composite
composite是一个多桶(multi-bucket)聚合,它从不同的聚合源创建composite buckets聚合,与其他multi-bucket聚合不同,composite聚合可用于高效地对多级聚合中的所有 bucket 进行分页。这种聚合提供了一种方法来流特定聚合的所有 bucket,类似于 scroll 对文档所做的操作。
composite buckets 是由为每个document 数据提取/创建的值的组合构建的,每个组合被视为组合 bucket。如下为官方给的例子:
{"keyword": ["foo", "bar"],"number": [23, 65, 76]}
如果我们同时对keyword和number两个字段进行聚合会得出以下的结果:
{ "keyword": "foo", "number": 23 }{ "keyword": "foo", "number": 65 }{ "keyword": "foo", "number": 76 }{ "keyword": "bar", "number": 23 }{ "keyword": "bar", "number": 65 }{ "keyword": "bar", "number": 76 }
看到上面的例子是不是恍然大悟, 就像类似sql中的多group by 多字段,可以对多个字段进行聚合,这非常适用于对于多维度出报表的需求,我这里建议使用的版本为6.5+,因为6.5版本以下此功能还处于测试阶段,设计和代码没有正式的GA功能成熟,并且没有担保。当然我这里也会提供6.5版本以下如何进行多聚合字段的使用。
首先上官方文档地址:
https://www.elastic.co/guide/en/elasticsearch/reference/6.5/search-aggregations-bucket-composite-aggregation.html
其实官方文档已经把该功能说得很详细,如果你只是单纯写DSL实现的话看官方文档就可以,我接下来就介绍如何调用他的javaAPI来使用,当然如果阅读源码能力强的话也可以直接看官方在github的test,地址如下:
https://github.com/elastic/elasticsearch/tree/master/server/src/test/java/org/elasticsearch/search/aggregations/bucket/composite
验证多字段聚合可行性
上面的例子为官方例子,我们需要自己弄个例子检查可行性,我们现在创建一个index,索引名为composite_test,mapping如下
{"area" : {"type" : "keyword" },"userid" : {"type" : "keyword" },"sendtime" : {"type" : "date","format" : "yyyy-MM-dd HH:mm:ss" }}
我们创建好了index,index中一共有三个字段,area,userid,sendtime三个字段
数据库实现方式
为了方便比较,我们也使用MySQL数据建一个一模一样的表,方便对比聚合出来的数据是否正确,表名为composite_test。
我们首先对数据表composite_test插入5条记录,分别如下
使用group by对三个字段进行聚合,以下为在数据库中的实现:
SELECT COUNT(1),area,userid,sendtime FROM composite_test GROUP BY area,userid,sendtime
结果如下:
以上为数据库的聚合实现
ES实现方式
所以我们使用ES进行多字段聚合的时候如果结果和以上的一样则是正确的,我们也一样往ES composite_test索引中 插入相同的5条数据,在kibana上执行以下命令插入
POST composite_test/_bulk{ "index" : {"_type" :"_doc"}}{"area":"33","userid":"400015","sendtime":"2019-01-17 00:00:00"}{ "index" : {"_type" : "_doc"}}{"area":"33","userid":"400015","sendtime":"2019-01-17 00:00:00"}{ "index" : {"_type" : "_doc"}}{"area":"35","userid":"400016","sendtime":"2019-01-18 00:00:00"}{ "index" : { "_type" : "_doc"}}{"area":"35","userid":"400016","sendtime":"2019-01-18 00:00:00"}{ "index" : {"_type" : "_doc"}}{"area":"33","userid":"400017","sendtime":"2019-01-17 00:00:00"}
查询ES,我们发现已经存在了这5条数据了。
接下来我们先使用composite的DSL查询:
GET composite_test/_search{"size": 0, "aggs" : {"my_buckets": {"composite" : {"" : [ { "area": { "terms": {"field": "area" } } }, { "userid": { "terms": {"field": "userid" } } }, { "sendtime": { "date_histogram": { "field": "sendtime","interval": "1d","format": "yyyy-MM-dd"} } } ] } } }}
DSL中我们分别对area、userid、sendtime做多字段聚合
结果如下
[ {"key" : {"area" : "33","userid" : "400015","sendtime" : "2019-01-17" },"doc_count" : 2 }, {"key" : {"area" : "33","userid" : "400017","sendtime" : "2019-01-17" },"doc_count" : 1 }, {"key" : {"area" : "35","userid" : "400016","sendtime" : "2019-01-18" },"doc_count" : 2 }]
从以上响应的json数组中我们不难看出,该聚合聚合出来的数据是和数据库聚合出来的数据是一致的
所以 composite是可以使用在多字段聚合上的。论证完可行性,我们接下来使用java来实现,实话说这一块我是看源代码才会使用的,网上资料基本为0,而且官方java使用文档里也没用,的确是与遇到了不少坑,写出来方便以后使用能快速回忆。
java使用composite聚合
首先创建Maven项目,加入ElasticSearch依赖:
org.elasticsearch.clientelasticsearch-rest-high-level-client6.5.4
关键代码如下
SearchRequest searchRequest = new SearchRequest("composite_test"); searchRequest.types("_doc");SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.size(0);/********************以下组装聚合的三个字段****************************/List> sources = new ArrayList<>();DateHistogramValuesSourceBuilder sendtime = new DateHistogramValuesSourceBuilder("sendtime") .field("sendtime") .dateHistogramInterval(DateHistogramInterval.days(1)) .format("yyyy-MM-dd").order(SortOrder.DESC).missingBucket(false); sources.add(sendtime);TermsValuesSourceBuilder userid = new TermsValuesSourceBuilder("userid").field("userid").missingBucket(true); sources.add(userid); TermsValuesSourceBuilder dttype = new TermsValuesSourceBuilder("area").field("area").missingBucket(true); sources.add(dttype); CompositeAggregationBuilder composite =new CompositeAggregationBuilder("my_buckets", sources);composite.size(1000);/*********************执行查询******************************/searchSourceBuilder.aggregation(composite);searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = client.search(searchRequest,RequestOptions.DEFAULT);/********************取出数据*******************/Aggregations aggregations = searchResponse.getAggregations();ParsedComposite parsedComposite = aggregations.get("my_buckets");List list = parsedComposite.getBuckets();Map data = new HashMap<>();for(ParsedBucket parsedBucket:list){ data.clear(); for (Map.Entry m : parsedBucket.getKey().entrySet()) { data.put(m.getKey(),m.getValue()); } data.put("count",parsedBucket.getDocCount()); System.out.println(data);}/*************************************/
控制台打印如下
{area=35, count=2, sendtime=2019-01-18, userid=400016}{area=33, count=2, sendtime=2019-01-17, userid=400015}{area=33, count=1, sendtime=2019-01-17, userid=400017}
数据正确,方法可用,其实这个方法是RestHighLevelClient替我们封装了composite生成DSL。
这里注意一下missingBucket的设置,这个的意思是如果该字段没值,为true的时候会返回null,为false不返回整条数据,注意这里是整条数据,而不是单单这个字段而已。
低版本使用ES多字段聚合
以上就是composite的验证和在java中的使用方法,建议在6.5+版本使用,但这个时候小伙伴可能会问,如果我是6.5以下的版本呢,这里也有一个方法,可以使用ES的子聚合(sub-aggregation),直接上代码:
SearchRequest searchRequest = new SearchRequest("composite_test"); searchRequest.types("_doc");SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.size(0);/********************以下组装聚合的三个字段****************************/AggregationBuilder sendtime=AggregationBuilders.dateHistogram("sendtime").field("sendtime").format("yyyy-MM-dd").interval(86400000);AggregationBuilder area=AggregationBuilders.terms("area").field("area");AggregationBuilder userid=AggregationBuilders.terms("userid").field("userid");//实现功能关键点area.subAggregation(userid);sendtime.subAggregation(area);/*********************执行查询******************************/searchSourceBuilder.aggregation(sendtime);searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = client.search(searchRequest,RequestOptions.DEFAULT);/********************取出数据*******************/Aggregations aggregations = searchResponse.getAggregations();//取出数据aggHandle(aggregations);/*************************************/
运行响应的数据为
{area=33, count=2, sendtime=2019-01-17, userid=400015}, {area=33, count=1, sendtime=2019-01-17, userid=400017}, {area=35, count=2, sendtime=2019-01-18, userid=400016}
所以使用子聚合也能多值聚合数据,其中实现的关键点在于
area.subAggregation(userid);sendtime.subAggregation(area);
相当于把聚合之后的数据在做一次聚合