当前位置: 代码网 > it编程>编程语言>Javascript > 【微服务】第29节: ElasticSearch的 RestClient查询&数据耦合

【微服务】第29节: ElasticSearch的 RestClient查询&数据耦合

2024年07月28日 Javascript 我要评论
Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎,能够解决不断涌现出的各种用例。作为 Elastic Stack 的核心,Elasticsearch 会集中存储您的数据,让您飞快完成搜索,微调相关性,进行强大的分析,并轻松缩放规模。

目录

1.restclient查询

1.1.快速入门

1.1.1.发送请求

1.1.2.解析响应结果

1.1.3.总结

1.2.叶子查询

1.3.复合查询

1.4.排序和分页

1.5.高亮

2.数据聚合

2.1.dsl实现聚合

2.1.1.bucket聚合

2.1.2.带条件聚合

2.1.3.metric聚合

2.1.4.总结

2.2.restclient实现聚合


1.restclient查询

文档的查询依然使用学习的 resthighlevelclient对象,查询的基本步骤如下⁉️ :

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

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

  • 3)发起请求

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

1.1.快速入门

之前说过,由于elasticsearch对外暴露的接口都是restful风格的接口,因此javaapi调用就是在发送http请求。而我们核心要做的就是利用利用java代码组织请求参数解析响应结果

这个参数的格式完全参考dsl查询语句的json结构,因此我们在学习的过程中,会不断的把javaapi与dsl语句对比。大家在学习记忆的过程中,也应该这样对比学习。

1.1.1.发送请求

首先以match_all查询为例,其dsl和javaapi的对比如图:

代码解读:

  • ☑️第一步,创建searchrequest对象,指定索引库名

  • ☑️第二步,利用request.source()构建dsl,dsl中可以包含查询、分页、排序、高亮等

    • query():代表查询条件,利用querybuilders.matchallquery()构建一个match_all查询的dsl

  • ☑️第三步,利用client.search()发送请求,得到响应

这里关键的api有两个,一个是request.source(),它构建的就是dsl中的完整json参数。其中包含了querysortfromsizehighlight等所有功能:

另一个是querybuilders,其中包含了我们学习过的各种叶子查询复合查询等:

1.1.2.解析响应结果

在发送请求以后,得到了响应结果searchresponse,这个类的结构与我们在kibana中看到的响应结果json结构完全一致:

{
    "took" : 0,
    "timed_out" : false,
    "hits" : {
        "total" : {
            "value" : 2,
            "relation" : "eq"
        },
        "max_score" : 1.0,
        "hits" : [
            {
                "_index" : "heima",
                "_type" : "_doc",
                "_id" : "1",
                "_score" : 1.0,
                "_source" : {
                "info" : "java讲师",
                "name" : "赵云"
                }
            }
        ]
    }
}

因此,我们解析searchresponse的代码就是在解析这个json结果,对比如下:

代码解读

elasticsearch返回的结果是一个json字符串,结构包含:

  • hits:命中的结果

    • total:总条数,其中的value是具体的总条数值

    • max_score:所有结果中得分最高的文档的相关性算分

    • hits:搜索结果的文档数组,其中的每个文档都是一个json对象

      • _source:文档中的原始数据,也是json对象

因此,我们解析响应结果,就是逐层解析json字符串,流程如下:

  • searchhits:通过response.gethits()获取,就是json中的最外层的hits,代表命中的结果

    • searchhits#gettotalhits().value:获取总条数信息

    • searchhits#gethits():获取searchhit数组,也就是文档数组

      • searchhit#getsourceasstring():获取文档结果中的_source,也就是原始的json文档数据

1.1.3.总结

🔍文档搜索的基本步骤是:

完整代码如下

1.2.叶子查询

所有的查询条件都是由querybuilders来构建的,叶子查询也不例外。因此整套代码中变化的部分仅仅是query条件构造的方式,其它不动。

例如match查询:

@test
void testmatch() throws ioexception {
    // 1.创建request
    searchrequest request = new searchrequest("items");
    // 2.组织请求参数
    request.source().query(querybuilders.matchquery("name", "脱脂牛奶"));
    // 3.发送请求
    searchresponse response = client.search(request, requestoptions.default);
    // 4.解析响应
    handleresponse(response);
}

再比如multi_match查询:

@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.解析响应
    handleresponse(response);
}

还有range查询:

@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.解析响应
    handleresponse(response);
}

还有term查询:

@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);
}

1.3.复合查询

复合查询也是由querybuilders来构建,我们以bool查询为例,dsl和javaapi的对比如图:

完整代码如下:

@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.解析响应
    handleresponse(response);
}

1.4.排序和分页

之前说过,requeset.source()就是整个请求json参数,所以排序、分页都是基于这个来设置,其dsl和javaapi的对比如下:

完整示例代码:

@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);
}

1.5.高亮

高亮查询与前面的查询有两点不同:

  • 条件同样是在request.source()中指定,只不过高亮条件要基于highlightbuilder来构造

  • 高亮响应结果与搜索的文档结果不在一起,需要单独解析

首先来看高亮条件构造,其dsl和javaapi的对比如图:

示例代码如下:

@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);
}

再来看结果解析,文档解析的部分不变,主要是高亮内容需要单独解析出来,其dsl和javaapi的对比如图:

代码解读:

  • 3、4步:从结果中获取_sourcehit.getsourceasstring(),这部分是非高亮结果,json字符串。还需要反序列为itemdoc对象

  • 5步:获取高亮结果。hit.gethighlightfields(),返回值是一个map,key是高亮字段名称,值是highlightfield对象,代表高亮值

  • 5.1步:从map中根据高亮字段名称,获取高亮字段值对象highlightfield

  • 5.2步:从highlightfield中获取fragments,并且转为字符串。这部分就是真正的高亮字符串了

  • 最后:用高亮的结果替换itemdoc中的非高亮结果

完整代码如下:

private void handleresponse(searchresponse response) {
    searchhits searchhits = response.gethits();
    // 1.获取总条数
    long total = searchhits.gettotalhits().value;
    system.out.println("共搜索到" + total + "条数据");
    // 2.遍历结果数组
    searchhit[] hits = searchhits.gethits();
    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);
    }
}

2.数据聚合

聚合(aggregations)可以让我们极其方便的实现对数据的统计、分析、运算。例如:

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

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

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

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

官方文档:

https://www.elastic.co/guide/en/elasticsearch/reference/7.12/search-aggregations.html

聚合常见的有三类:

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

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

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

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

    • avg:求平均值

    • max:求最大值

    • min:求最小值

    • stats:同时求maxminavgsum

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

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

2.1.dsl实现聚合

与之前的搜索功能类似,我们依然先学习dsl的语法,再学习javaapi.

2.1.1.bucket聚合

例如我们要统计所有商品中共有哪些商品分类,其实就是以分类(category)字段对数据分组。category值一样的放在同一组,属于bucket聚合中的term聚合。

基本语法如下:

get /items/_search
{
  "size": 0, 
  "aggs": {
    "category_agg": {
      "terms": {
        "field": "category",
        "size": 20
      }
    }
  }
}

语法说明:

来看下查询的结果:

2.1.2.带条件聚合

默认情况下,bucket聚合是对索引库的所有文档做聚合,例如我们统计商品中所有的品牌,结果如下:

可以看到统计出的品牌非常多。

但真实场景下,用户会输入搜索条件,因此聚合必须是对搜索结果聚合。那么聚合必须添加限定条件。

例如,我想知道价格高于3000元的手机品牌有哪些,该怎么统计呢?

我们需要从需求中分析出搜索查询的条件和聚合的目标:

  • 搜索查询条件:

    • 价格高于3000

    • 必须是手机

  • 聚合目标:统计的是品牌,肯定是对brand字段做term聚合

语法如下:

get /items/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "category": "手机"
          }
        },
        {
          "range": {
            "price": {
              "gte": 300000
            }
          }
        }
      ]
    }
  }, 
  "size": 0, 
  "aggs": {
    "brand_agg": {
      "terms": {
        "field": "brand",
        "size": 20
      }
    }
  }
}

聚合结果如下:

{
  "took" : 2,
  "timed_out" : false,
  "hits" : {
    "total" : {
      "value" : 13,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "brand_agg" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "华为",
          "doc_count" : 7
        },
        {
          "key" : "apple",
          "doc_count" : 5
        },
        {
          "key" : "小米",
          "doc_count" : 1
        }
      ]
    }
  }
}

可以看到,结果中只剩下3个品牌了。

2.1.3.metric聚合

上节课,我们统计了价格高于3000的手机品牌,形成了一个个桶。现在我们需要对桶内的商品做运算,获取每个品牌价格的最小值、最大值、平均值。

这就要用到metric聚合了,例如stat聚合,就可以同时获取minmaxavg等结果。

语法如下:

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"
          }
        }
      }
    }
  }
}

query部分就不说了,我们重点解读聚合部分语法。

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

  • stats_meric:聚合名称

    • stats:聚合类型,stats是metric聚合的一种

      • field:聚合字段,这里选择price,统计价格

由于stats是对brand_agg形成的每个品牌桶内文档分别做统计,因此每个品牌都会统计出自己的价格最小、最大、平均值。

结果如下:

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

2.1.4.总结

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

  • 限定聚合的的文档范围

聚合必须的三要素:

  • 聚合名称

  • 聚合类型

  • 聚合字段

聚合可配置属性有:

  • size:指定聚合结果数量

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

  • field:指定聚合字段

2.2.restclient实现聚合

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

不过聚合条件的要利用aggregationbuilders这个工具类来构造。dsl与javaapi的语法对比如下:

聚合结果与搜索文档同一级别,因此需要单独获取和解析。具体解析语法如下:

完整代码如下:

@test
void testagg() throws ioexception {
    // 1.创建request
    searchrequest request = new searchrequest("items");
    // 2.准备请求参数
    boolquerybuilder bool = querybuilders.boolquery()
            .filter(querybuilders.termquery("category", "手机"))
            .filter(querybuilders.rangequery("price").gte(300000));
    request.source().query(bool).size(0);
    // 3.聚合参数
    request.source().aggregation(
            aggregationbuilders.terms("brand_agg").field("brand").size(5)
    );
    // 4.发送请求
    searchresponse response = client.search(request, requestoptions.default);
    // 5.解析聚合结果
    aggregations aggregations = response.getaggregations();
    // 5.1.获取品牌聚合
    terms brandterms = aggregations.get("brand_agg");
    // 5.2.获取聚合中的桶
    list<? extends terms.bucket> buckets = brandterms.getbuckets();
    // 5.3.遍历桶内数据
    for (terms.bucket bucket : buckets) {
        // 5.4.获取桶内key
        string brand = bucket.getkeyasstring();
        system.out.print("brand = " + brand);
        long count = bucket.getdoccount();
        system.out.println("; count = " + count);
    }
}

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com