Elasticsearch(二)

Elasticsearch(二)

Elasticsearch搜索引擎的黑马学习笔记

原文:‍‬⁠⁠⁠‬‌‌‍‬‌‬⁠‬‬‌⁠‌day08-Elasticsearch - 飞书云文档 (feishu.cn)

一、DSL查询

前面我们只根据id查询结果。那么如何根据一些别的字段来查询呢? 这里就引入了DSL(Domain Specific Language)领域特定语言

Elasticsearch的查询可以分为两大类:

  • 叶子查询(Leaf query clauses):一般是在特定的字段里查询特定值,属于简单查询,很少单独使用。

  • 复合查询(Compound query clauses):以逻辑方式组合多个叶子查询或者更改叶子查询的行为方式。

通过match_all简单查询:

1
2
3
4
5
6
7
8
GET /items/_search
{
"query": {
"match_all": {

}
}
}

常见查询方式:

  • 全文检索查询(Full Text Queries):利用分词器对用户输入搜索条件先分词,得到词条,然后再利用倒排索引搜索词条。例如:

    • match
    • multi_match
  • 精确查询(Term-level queries):不对用户输入搜索条件分词,根据字段内容精确值匹配。但只能查找keyword、数值、日期、boolean类型的字段。例如:

    • ids
    • term
    • range
  • **地理坐标查询:**用于搜索地理位置,搜索方式很多,例如:

    • geo_bounding_box:按矩形搜索
    • geo_distance:按点和半径搜索

二、叶子查询

1、全文检索查询

match 匹配一个字段

1
2
3
4
5
6
7
8
GET /items/_search
{
"query": {
"match": {
"name": "华为荣耀"
}
}
}

multi_match 匹配多个字段

1
2
3
4
5
6
7
8
9
GET /items/_search
{
"query": {
"multi_match": {
"query": "华为荣耀",
"fields": ["name", "brand"]
}
}
}

2、精确查询

**Term-level query ** 精确匹配,适合查找keyword、数值、日期、boolean类型的字段

term 查询一个

1
2
3
4
5
6
7
8
9
10
GET /items/_search
{
"query": {
"term": {
"brand": {
"value": "格力"
}
}
}
}

range 查询范围

1
2
3
4
5
6
7
8
9
10
11
GET /items/_search
{
"query": {
"range": {
"price": {
"gte": 0,
"lte": 100
}
}
}
}

三、复合查询

1、算分函数

function_query结构包含四个部分:

“query”:上面学的查询条件,基于BM25算法给文档打分,得到原始算分(query score)

“filter”: 过滤条件,基于该条件的文档才会重新算分。

“functions”:算分函数,基于filter过滤后的文档,根据这个函数进行算分,得到函数算分(function score),可以选四种函数

​ weight :函数结果是常量

​ field_value_factor :以文档中某个字段值作为函数结果

​ random_score :随机数作为函数结果

​ script_score :自定义算分算法

“boost_mode”: 算分模式, 将算分函数得到的function scorequery score 不同方式进行运算。

​ multiply: 相乘

​ replace: 用function score代替query score

​ 其他: sum、avg、max、min

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /hotel/_search
{
"query": {
"function_score": {
"query": { .... }, // 原始查询,可以是任意条件
"functions": [ // 算分函数
{
"filter": { // 满足的条件,品牌必须是Iphone
"term": {
"brand": "Iphone"
}
},
"weight": 10 // 算分权重为2
}
],
"boost_mode": "multipy" // 加权模式,求乘积
}
}
}

2、bool查询

利用逻辑运算组合一个或多个查询子句的组合

must :必须匹配每个子查询,&&

should:选择性匹配每个子查询, ||

must_not: 必须不匹配,不参与算分。 !

filter:必须匹配,不参与算分

实际上就是叶子查询前面套了一些逻辑运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
GET /items/_search
{
"query": {
"bool": {
"must": [
{"match": {"name": "手机"}}
],
"should": [
{"term": {"brand": { "value": "vivo" }}},
{"term": {"brand": { "value": "小米" }}}
],
"must_not": [
{"range": {"price": {"gte": 2500}}}
],
"filter": [
{"range": {"price": {"lte": 1000}}}
]
}
}
}

必须是name包含手机,品牌可以是vivo或者小米。价格必须小于2500。过滤只剩下价格小于1000的结果。

小细节:

搜索关键字有关的搜索字段可以采用must或者should。与关键字无关的采用must_not或者filter,可以避免参与相关性计算。

比如,我们要搜索手机,但品牌必须是华为,价格必须是900~1599,那么可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET /items/_search
{
"query": {
"bool": {
"must": [
{"match": {"name": "手机"}}
],
"filter": [
{"term": {"brand": { "value": "华为" }}},
{"range": {"price": {"gte": 90000, "lt": 159900}}}
]
}
}
}

3、排序

默认通过相关度算分_score来排序,也支持自定义方式排序。但是分词字段不能用来排序。可以排序的字段:keyword类型、数值类型、地理坐标类型、日期类型等。

1
2
3
4
5
6
7
8
9
10
11
12
13
GET /items/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"price": {
"order": "desc"
}
}
]
}

order可以选择desc或者是asc

4、分页

基础分页

ES默认只返回前十条,想要得到更多的数据只能修改分页参数了。

  • from:从第几个文档开始

  • size:总共查询几个文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /items/_search
{
"query": {
"match_all": {}
},
"from": 0, // 分页开始的位置,默认为0
"size": 10, // 每页文档数量,默认10
"sort": [
{
"price": {
"order": "desc"
}
}
]
}

深度分页

ES一般会将数据采用分片存储,但是这样会造成问题: 比如我们想要得到价格降序条件下, 每页10条 第99页的的数据。那么我们就要先得到前1000个数据进行排序。

又因为我们分片存储了,又得得到每一片的前1000个,把他们全部汇总再去排序得到排序后的前1000个。

这样如果汇总数据很多,就会对内存和CPU会产生非常大的压力。

针对深度分页,elasticsearch提供了两种解决方案:

  • search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。

  • scroll:原理将排序后的文档id形成快照,保存下来,基于快照做分页。官方已经不推荐使用。

5、高亮

8

前端高亮原理:

  • 高亮词条都被加了<em>标签

  • <em>标签都添加了红色样式

em可以自定义

前端并不知道什么时候需要高亮。词条的高亮标签肯定是由服务端提供数据的时候已经加上的

实现高亮的原理:

用户通过关键词搜索数据 -> 后端ES查询到数据后,将数据中包含关键词的词条加上高亮标签(要先和前端约定好高亮标签)

格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /{索引库名}/_search
{
"query": {
"match": {
"搜索字段": "搜索关键字"
}
},
"highlight": {
"fields": {
"高亮字段名称": {
"pre_tags": "<em>",
"post_tags": "</em>"
}
}
}
}

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /items/_search
{
"query": {
"match": {
"name": "脱脂牛奶"
}
},
"highlight": {
"fields": {
"name": {
"pre_tags": "<em>",
"post_tags": "</em>"
}
}
}
}

四、RestClient查询

通过Java代码操作ES来查询

  • 1)创建request对象,这次是搜索,所以是SearchRequest

  • 2)准备请求参数,也就是查询DSL对应的JSON参数

  • 3)发起请求

  • 4)解析响应,响应结果相对复杂,需要逐层解析

准备请求参数完全可以对应上面JSON查询代码来构建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Test
void testMatchAll() throws IOException {
//1、创建Request对象
SearchRequest request = new SearchRequest("items");


//2、配置Request参数
request.source()
.query(QueryBuilders.matchAllQuery());
//3、发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);


//解析结果
parseResponseResult(response);

}

private static void parseResponseResult(SearchResponse response){
//4、解析结果
SearchHits searchHits = response.getHits();

//4.1、总条数
long total = searchHits.getTotalHits().value;
System.out.println("total = " + total);
//4.2、命中的数据
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
//转为ItemDOc
ItemDoc doc = JSONUtil.toBean(json, ItemDoc.class);
//4.3 处理高亮结果
Map<String, HighlightField> hfs = hit.getHighlightFields();
if(hfs !=null && !hfs.isEmpty()){
//4.3.1、根据高亮字段名获取高亮结果
HighlightField hf = hfs.get("name");
//4.3.2、根据高亮结果,覆盖非高亮结果
String hfName = hf.getFragments()[0].string();
doc.setName(hfName);
}


System.out.println("doc = " + doc);

}
}

request.source()包含DSL所需要的功能

9

QueryBuilders.matchAllQuery()包含很多查询方式:

10

1、叶子查询

match

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
void testMatchAll() throws IOException {
//1、创建Request对象
SearchRequest request = new SearchRequest("items");



//2、配置Request参数
request.source()
.query(QueryBuilders.matchAllQuery());
//3、发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);

parseResponseResult(response);

}

multiple_match

1
2
3
4
5
6
7
8
9
10
11
@Test
void testMultiMatch() throws IOException {
// 1.创建Request
SearchRequest request = new SearchRequest("items");
// 2.组织请求参数
request.source().query(QueryBuilders.multiMatchQuery("脱脂牛奶", "name", "category"));
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
parseResponseResult(response);
}

range

1
2
3
4
5
6
7
8
9
10
11
@Test
void testRange() throws IOException {
// 1.创建Request
SearchRequest request = new SearchRequest("items");
// 2.组织请求参数
request.source().query(QueryBuilders.rangeQuery("price").gte(10000).lte(30000));
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
parseResponseResult(response);
}

term

1
2
3
4
5
6
7
8
9
10
11
@Test
void testTerm() throws IOException {
// 1.创建Request
SearchRequest request = new SearchRequest("items");
// 2.组织请求参数
request.source().query(QueryBuilders.termQuery("brand", "华为"));
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
handleResponse(response);
}

2、复合查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
void testBool() throws IOException {
// 1.创建Request
SearchRequest request = new SearchRequest("items");
// 2.组织请求参数
// 2.1.准备bool查询
BoolQueryBuilder bool = QueryBuilders.boolQuery();
// 2.2.关键字搜索
bool.must(QueryBuilders.matchQuery("name", "脱脂牛奶"));
// 2.3.品牌过滤
bool.filter(QueryBuilders.termQuery("brand", "德亚"));
// 2.4.价格过滤
bool.filter(QueryBuilders.rangeQuery("price").lte(30000));
request.source().query(bool);
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
parseResponseResult(response);
}

实际上就是支持嵌套查询,我们在BoolQueryBuilder中又添加了叶子查询的参数

3、排序和分页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
void testPageAndSort() throws IOException {
int pageNo = 1, pageSize = 5;

// 1.创建Request
SearchRequest request = new SearchRequest("items");
// 2.组织请求参数
// 2.1.搜索条件参数
request.source().query(QueryBuilders.matchQuery("name", "脱脂牛奶"));
// 2.2.排序参数
request.source().sort("price", SortOrder.ASC);
// 2.3.分页参数
request.source().from((pageNo - 1) * pageSize).size(pageSize);
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
handleResponse(response);
}

在不同的维度。之前都是添加在query中。现在选择source的不同方法中

4、高亮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
void testHighlight() throws IOException {
// 1.创建Request
SearchRequest request = new SearchRequest("items");
// 2.组织请求参数
// 2.1.query条件
request.source().query(QueryBuilders.matchQuery("name", "脱脂牛奶"));
// 2.2.高亮条件
request.source().highlighter(
SearchSourceBuilder.highlight()
.field("name")
.preTags("<em>")
.postTags("</em>")
);
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
handleResponse(response);
}

处理高亮结果完整方法在上面。 由于高亮查询结果并不会自动添加到_source内,会放在highlight中,所以我们需要手动替换。

11

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
for (SearchHit hit : hits) {
// 3.得到_source,也就是原始json文档
String source = hit.getSourceAsString();
// 4.反序列化
ItemDoc item = JSONUtil.toBean(source, ItemDoc.class);
// 5.获取高亮结果
Map<String, HighlightField> hfs = hit.getHighlightFields();
if (CollUtils.isNotEmpty(hfs)) {
// 5.1.有高亮结果,获取name的高亮结果
HighlightField hf = hfs.get("name");
if (hf != null) {
// 5.2.获取第一个高亮结果片段,就是商品名称的高亮值
String hfName = hf.getFragments()[0].string();
item.setName(hfName);
}
}
System.out.println(item);
}

只能说人家函数这么设计的。我们只能这么取

五、数据聚合

聚合(aggregations)可以非常方便的实现对数据的统计、分析、运算。例如:

  • 什么品牌的手机最受欢迎?

  • 这些手机的平均价格、最高价格、最低价格?

  • 这些手机每月的销售情况如何?

实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现近实时搜索效果。

聚合常见的有三类:

  • 桶(Bucket)聚合:用来对文档做分组

  • TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组

  • Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组

  • 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等

  • Avg:求平均值

  • Max:求最大值

  • Min:求最小值

  • Stats:同时求maxminavgsum

  • 管道(pipeline)聚合:其它聚合的结果为基础做进一步运算

**注意:**参加聚合的字段必须是keyword、日期、数值、布尔类型

1、Bucket聚合

1
2
3
4
5
6
7
8
9
10
11
12
GET /items/_search
{
"size": 0,
"aggs": {
"category_agg": {
"terms": {
"field": "category",
"size": 20
}
}
}
}

size: 设置为0条,表示只做文档查询,不需要查看文档。

aggs: 定义聚合。

category_agg: 自定义聚合名称,不可重复。

terms: 聚合类型,按分类聚合,用terms

field:参与聚合字段名称

size: 希望返回的聚合结果最大值。

得到查询结果中分类排名,以及分类的数量。安装分类数量排名

12

2、带条件的聚合

bucket集合bool查询一起使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
GET /items/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"category": "手机"
}
},
{
"range": {
"price": {
"gte": 300000
}
}
}
]
}
},
"size": 0,
"aggs": {
"brand_agg": {
"terms": {
"field": "brand",
"size": 20
}
}
}
}

13

3、Metric聚合(度量聚合)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
GET /items/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"category": "手机"
}
},
{
"range": {
"price": {
"gte": 300000
}
}
}
]
}
},
"size": 0,
"aggs": {
"brand_agg": {
"terms": {
"field": "brand",
"size": 20
},
"aggs": {
"stats_meric": {
"stats": {
"field": "price"
}
}
}
}
}
}

可以看到我们在brand_agg聚合的内部,我们新加了一个aggs参数。这个聚合就是brand_agg的子聚合,会对brand_agg形成的每个桶中的文档分别统计。

  • stats_meric:聚合名称

    • stats:聚合类型,stats是metric聚合的一种
      • field:聚合字段,这里选择price,统计价格

14

对分组结果进行metric聚合。

另外,我们还可以让聚合按照每个品牌的价格平均值排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
GET /items/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"category": "手机"
}
},
{
"range": {
"price": {
"gte": 300000
}
}
}
]
}
},
"size": 0,
"aggs": {
"brand_agg": {
"terms": {
"field": "brand",
"size": 20,
"order": {
"stats_meric.avg": "desc"
}
},
"aggs": {
"stats_meric": {
"stats": {
"field": "price"
}
}
}
}
}
}

15

aggs代表聚合,与query同级,此时query的作用是?

  • 限定聚合的的文档范围

聚合必须的三要素:

  • 聚合名称

  • 聚合类型

  • 聚合字段

聚合可配置属性有:

  • size:指定聚合结果数量

  • order:指定聚合结果排序方式

  • field:指定聚合字段

4、RestClient实现聚合

aggs聚合条件与query条件是同一级别,都属于查询JSON参数。因此依然是利用request.source()方法来设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Test
void testAgg() throws IOException {

//1、创建Request对象
SearchRequest request = new SearchRequest("items");

//2.组织DSL参数

//2.1、分页
request.source().size(0);

//2.2、聚合条件
String brandAggName = "brandAgg";
request.source().aggregation(
AggregationBuilders.terms(brandAggName)
.field("brand").size(10));
//3、发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);

//4、解析结果
Aggregations aggregations = response.getAggregations();
//4.1、根据聚合名称获取对应的聚合
Terms brandTerms = aggregations.get(brandAggName);
//4.2、获取buckets
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();

//4.3、遍历获取每一个桶
for (Terms.Bucket bucket : buckets) {
System.out.println("bucket = " + bucket.getKeyAsString());
System.out.println("count = " + bucket.getDocCount());
}
}

16

六、复杂的例子

17

对应上述搜索的查询语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
   public PageDTO<ItemDTO> detailSearch(ItemPageQuery itemPageQuery) throws IOException {

RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.101.128:9200")
));

//1、创建Request对象
SearchRequest request = new SearchRequest("items");


//2、配置Request参数
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); //创建一个bool查询
if (StrUtil.isNotBlank(itemPageQuery.getKey())) {
queryBuilder.must(QueryBuilders.matchQuery("name", itemPageQuery.getKey())); //名称必须包含key
}
if (StrUtil.isNotBlank(itemPageQuery.getBrand())) {
queryBuilder.must(QueryBuilders.termQuery("brand", itemPageQuery.getBrand())); //品牌必须准确包含brand
}
if (StrUtil.isNotBlank(itemPageQuery.getCategory())) {
queryBuilder.must(QueryBuilders.termQuery("category", itemPageQuery.getCategory()));
}
if (itemPageQuery.getMaxPrice() != null) {
queryBuilder.filter(QueryBuilders.rangeQuery("price").lt(itemPageQuery.getMaxPrice())); //价格区间
}
if (itemPageQuery.getMinPrice() != null) {
queryBuilder.filter(QueryBuilders.rangeQuery("price").gt(itemPageQuery.getMinPrice()));
}
//竞价排名,引入算分函数,将上述bool查询条件包装其中
FunctionScoreQueryBuilder scoreQueryBuilder = QueryBuilders.functionScoreQuery(
queryBuilder,
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
QueryBuilders.termQuery("isAD", "true"),
ScoreFunctionBuilders.weightFactorFunction(100)
)
}
).boostMode(CombineFunction.MULTIPLY); //算分模式


request.source().query(scoreQueryBuilder);
//设置分页参数
request.source().from((itemPageQuery.getPageNo() - 1) * itemPageQuery.getPageSize()).size(itemPageQuery.getPageSize());

if (StrUtil.isNotBlank(itemPageQuery.getSortBy()))
request.source().sort(StrUtil.toCamelCase(itemPageQuery.getSortBy()), itemPageQuery.getIsAsc() ? SortOrder.ASC : SortOrder.DESC);


//3、发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);

//4、解析请求
long total = response.getHits().getTotalHits().value;

List<ItemDTO> itemDTOList = new ArrayList<>();

SearchHits hits = response.getHits();
for (SearchHit hit : hits) {
String source = hit.getSourceAsString();
//JSON转成itemDoc
ItemDoc itemDoc = JSONUtil.toBean(source, ItemDoc.class);
//包装成DTO
ItemDTO itemDTO = BeanUtil.copyProperties(itemDoc, ItemDTO.class);
itemDTOList.add(itemDTO);
}

if (client != null) {
client.close();
}
PageDTO<ItemDTO> pageDTO = new PageDTO<>();
pageDTO.setTotal(total);
pageDTO.setList(itemDTOList);
pageDTO.setPages(itemPageQuery.getPageNo().longValue());
return pageDTO;
}

得到聚合结果,用于搜索结果的分类,品牌的动态展示

不放在一起是因为聚合只需要根据key得到聚合结果就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public String filters(ItemPageQuery query) throws IOException {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.174.129:9200")
));

SearchRequest request = new SearchRequest("items");

SearchSourceBuilder sourceBuilder = request.source();
//构造搜索条件,以供聚合使用
sourceBuilder.query(QueryBuilders.matchQuery("name", query.getKey()));
//得到2个聚合结果,根据品牌分组,以根据分类分组
sourceBuilder.aggregation(AggregationBuilders.terms("brand_agg").field("brand"));
sourceBuilder.aggregation(AggregationBuilders.terms("category_agg").field("category"));


//2、发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);

//3、解析结果,结果的读取可以通过调试获得

Terms brandTerm = response.getAggregations().get("brand_agg");
Terms categoryAgg = response.getAggregations().get("category_agg");

List<String> brandList = new ArrayList<>();
List<String> categoryList = new ArrayList<>();

for (Terms.Bucket bucket : brandTerm.getBuckets()) {
String keyAsString = bucket.getKeyAsString();
brandList.add(keyAsString);
}

for (Terms.Bucket bucket : categoryAgg.getBuckets()) {
String keyAsString = bucket.getKeyAsString();
categoryList.add(keyAsString);
}


if (client != null) {
client.close();
}

Map<String,List<String>> map = new HashMap<>();
map.put("category",categoryList);
map.put("brand",brandList);
return JSONUtil.toJsonStr(map);
}