微服务入门:elasticsearch与RestClient(2)

微服务入门:elasticsearch与RestClient(2)

一、前言

在前面入门了elasticsearch的索引与文档的操作,以及如何使用RestClient来对索引的CRUD与文档CRUD

微服务入门:elasticsearch与RestClient(1):https://blog.csdn.net/weixin_51146329/article/details/125901581?spm=1001.2014.3001.5501

这些操作实现了elasticsearch的数据存储功能,但是对于elasticsearch来说,数据存储并不是其主要的目的,elasticsearch的主要功能是搜索和数据分析


二、使用DSL查询文档

在elasticsearch查询分为五种,分别是:

  • 查询所有:查询出所有数据,一般测试用。例如:match_all
  • 全文检索查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:
    • match_query
    • multi_match_query
  • 精准查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:
    • ids
    • range
    • term
  • 地理查询:根据经纬度查询。例如:
    • geo_distance
    • geo_bounding_box
  • 复合查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:
    • bool
    • function_score

1. 查询所有

#查询所有
GET /hotel/_search
{
  "query": {
    "match_all": {}
  }
}

字段解析:

  • query:表示为查询
  • match_all:表示查询所有,无查询条件

2. 全文检索查询

全文检索查询有两种

  • match:表示单字段查询
  • multi_match:多字段查询,任意一个字段符合即可
# match查询所有
GET /hotel/_search
{
  "query": {
    "match": {
      "all":"外滩如家"
    }
  }
}
# multi_match查询所有
GET /hotel/_search
{
  "query": {
    "multi_match": {
      "query":"如家",
      "fields":["brand", "name","business"]
    }
  }
}

注意:

使用multi_match查询会降低查询效率,一般来讲推荐使用match

在索引新增一个字段,然后将其他字段的信息,使用“copy_to”拷贝到那个字段,然后查询那个字段,也能实现多字段的查询,并且查询效率比使用multi_match要高


3. 精准查询

精准查询主要有两种:

  • range:根据词条精确值查询
  • term:根据值的范围查询

这里的根据词条和全文检索是有一定区别的,就像某东一样,选择商品的时候可以通过选择不同的筛选条件来得到筛选的功能,这里就是使用了根据词条查询

在这里插入图片描述

而根据值范围查询,则是选择最小值和最大值这种

在这里插入图片描述

# term查询所有
GET /hotel/_search
{
  "query": {
    "term": {
      "FIELD": {
        "value": "value"
      }
    }
  }
}
# range查询所有
GET /hotel/_search
{
  "query": {
    "range": {
      "FIELD": {
        "gte": 100,
        "lte": 300
      }
    }
  }
}

注意:

  • gte:大于等于
  • lte:小于等于
  • city:是需要筛选的字段

4. 地理坐标查询

地理坐标查询有两种

  • geo_bounding_box:矩形范围查询,根据指定矩形的左上右下两个点的坐标,然后画出一个矩形,落在该矩形内的都是符合条件的点

在这里插入图片描述

  • geo_distance:附近查询,在地图上找一个点作为圆心,以指定距离为半径,画一个圆,落在圆内的坐标都算符合条件

在这里插入图片描述

#distance查询
GET /hotel/_search
{
  "query": {
    "geo_distance":{
    	//半径
      "distance":"3km",
      //圆心
      "location":"31.21,121.5"
    }
  }
}
// geo_bounding_box查询
GET /indexName/_search
{
  "query": {
    "geo_bounding_box": {
      "FIELD": {
        "top_left": { // 左上点
          "lat": 31.1,
          "lon": 121.5
        },
        "bottom_right": { // 右下点
          "lat": 30.9,
          "lon": 121.7
        }
      }
    }
  }
}

5. 复合查询

复合查询有两个用处

  • fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名,有点类似百度的排名
  • bool query:布尔查询,利用逻辑关系组合多个其它的查询,实现复杂搜索
#function score查询
GET /hotel/_search
{
  "query": {
    "function_score": {
      "query": {
        "match": {
          "all": "外滩"
        }
      },
      "functions": [
        {
          "filter": {
            "term": {
              "brand": "如家"
            }
          },
          "weight": 10
        }
      ],
      "boost_mode": "multiply"
    }
  }
}

当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列

在这里插入图片描述

function score 查询中包含四部分内容:

  • 原始查询条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分(query score)
  • 过滤条件:filter部分,符合该条件的文档才会重新算分
  • 算分函数:符合filter条件的文档要根据这个函数做运算,得到的函数算分(function score),有四种函数
    • weight:函数结果是常量
    • field_value_factor:以文档中的某个字段值作为函数结果
    • random_score:以随机数作为函数结果
    • script_score:自定义算分函数算法
  • 运算模式:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:
    • multiply:相乘(默认)
    • replace:用function score替换query score
    • 其它,例如:sum、avg、max、min
# bool 查询
GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "如家"
          }
        }
      ],
      "must_not": [
        {
          "range": {
            "price": {
              "gte": 400
            }
          }
        }
      ],
      "filter": [
        {
          "geo_distance": {
            "distance": "10km",
            "location": {
              "lat": 31.21,
              "lon": 121.5
            }
          }
        }
      ]
    }
  }
}

布尔查询是一个或多个查询子句的组合,每一个子句就是一个子查询。子查询的组合方式有:

  • must:必须匹配每个子查询,类似“与”
  • should:选择性匹配子查询,类似“或”
  • must_not:必须不匹配,不参与算分,类似“非”
  • filter:必须匹配,不参与算分

需要注意的是,搜索时,参与打分的字段越多,查询的性能也越差。因此这种多条件查询时,建议这样做:

  • 搜索框的关键字搜索,是全文检索查询,使用must查询,参与算分
  • 其它过滤条件,采用filter查询。不参与算分

三、处理查询结果

对于查询结果的处理,常见有以下处理方法

  • 排序
  • 分页
  • 高亮

1. 排序

elasticsearch默认是根据相关度算分(_score)来排序,但是也支持自定义方式对搜索结果排序。可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。

#sort排序
GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "score": "desc"
    },
    {
      "price": "asc"
    }
  ]
}

这里查询的意思是

  • 查询所有
  • 按照score排序
  • 如果score相同,那么按照price排序
# 找到121.6122282,31.034661周围的酒店,距离升序排序
GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "_geo_distance": {
        "location": {
          "lat": 31.034661,
          "lon": 121.6122282
        },
        "order": "asc",
        "unit": "km"
      }
    }
  ]
}
  • 指定一个坐标,作为目标点
  • 计算每一个文档中,指定字段(必须是geo_point类型)的坐标 到目标点的距离是多少
  • 根据距离排序

2. 分页

elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch中通过修改from、size参数来控制要返回的分页结果:

#分页查询
GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "price": {
        "order": "asc"
      }
    }
  ],
  "from": 10,// 分页开始的位置,默认为0
  "size": 10// 期望获取的文档总数
}

但是elasticsearch内部分页不允许from+ size 超过10000的请求

并且当es为集群部署的时候,需要将所有集群的数据集中在一起才是一个完整的数据,因此这种方法就不再适用了

在这里插入图片描述

针对深度分页,ES提供了两种解决方案,官方文档

  • search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
  • scroll:原理将排序后的文档id形成快照,保存在内存。官方已经不推荐使用。

这三种分页的优缺点:

  • from + size

    • 优点:支持随机翻页
    • 缺点:深度分页问题,默认查询上限(from + size)是10000
    • 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
  • after search

    • 优点:没有查询上限(单次查询的size不超过10000)
    • 缺点:只能向后逐页查询,不支持随机翻页
    • 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页
  • scroll

    • 优点:没有查询上限(单次查询的size不超过10000)
    • 缺点:会有额外内存消耗,并且搜索结果是非实时的
    • 场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用 after search方案。

3. 高亮

对于高亮,我们经常看见,当我们在在百度上查找某一样东西的时候,就出现了高亮了

在这里插入图片描述

高亮显示的实现分为两步:

  • 1)给文档中的所有关键字都添加一个标签,例如<em>标签
  • 2)页面给<em>标签编写CSS样式
#高亮查询,默认情况下,ES搜索字段必须与高亮字段一致,否则不高亮
GET /hotel/_search
{
  "query": {
    "match": {
      "all": "如家"
    }
  },
  "highlight": {
    "fields": {
      "name": {
        "require_field_match": "false"
      }
    }
  }
}

如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false


四、RestClient查询文档

用Java构建DSL语句相对而已比较简单,需要我们熟悉如何编写DSL语句,然后根据DSL的JSON格式的结构编写出即可

1. 查询所有

@Test
void testMatchAll() throws IOException {
    //1. 准备Request
    SearchRequest request = new SearchRequest("hotel");
    //2. 准备DSL
    request.source().query(QueryBuilders.matchAllQuery());
    //3. 发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    handleResponse(response);
}

在这里插入图片描述

处理响应结果

    private void handleResponse(SearchResponse response) {
        //4. 解析结果
        SearchHits searchHits = response.getHits();
        //4.1 查询总条数
        long total = searchHits.getTotalHits().value;
        System.out.println("查询结果总条数: " + total);
        //4.2 查询总条数
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            //4.3 得到source
            String json = hit.getSourceAsString();
            //4.4 反序列化
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
            //4.5 获取高亮部分
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (!CollectionUtils.isEmpty(highlightFields)){
                //获取高亮字段结果
                HighlightField highlightField = highlightFields.get("name");
                if (highlightField != null){
                    //取出高亮结果数组中的第一个,就是酒店名称
                    String name = highlightField.getFragments()[0].string();
                    hotelDoc.setName(name);
                }
            }
            //4.6 打印
            System.out.println(hotelDoc);
        }
    }

2. match查询

在这里插入图片描述

@Test
void testMath() throws IOException {
    //1. 准备请求
    SearchRequest request = new SearchRequest("hotel");
    //2. 准备DSL
    request.source().query(QueryBuilders.matchQuery("all", "如家"));
    //3. 发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    //4. 解析结果
    handleResponse(response);
}

3. 多字段查询

在这里插入图片描述

@Test
void testMultiMatch() throws IOException {
    //1. 准备请求
    SearchRequest request = new SearchRequest("hotel");
    //2. 准备DSL
    request.source().query(QueryBuilders.multiMatchQuery("如家", "brand", "name"));
    //3. 发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    handleResponse(response);
}

4. 精确查询

在这里插入图片描述

@Test
void testTermQuery() throws IOException {
    //1. 准备请求
    SearchRequest request = new SearchRequest("hotel");
    //2. 准备DSL
    request.source().query(QueryBuilders.termQuery("city", "上海"));
    //3. 发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    handleResponse(response);
}

@Test
void testRangeQuery() throws IOException {
    // 1. 准备请求
    SearchRequest request = new SearchRequest("hotel");
    // 2. 准备DSL
    request.source().query(QueryBuilders.rangeQuery("price").gte(100).lte(150));
    //3. 发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    //4. 处理请求
    handleResponse(response);
}

5. 布尔查询

在这里插入图片描述

@Test
void testBoolQuery() throws IOException {
    // 1.准备请求
    SearchRequest request = new SearchRequest("hotel");
    // 2.创建布尔查询
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    // 3.添加must条件
    BoolQueryBuilder queryBuilder = boolQuery.must(QueryBuilders.termQuery("city", "杭州"));
    queryBuilder.filter(QueryBuilders.rangeQuery("price").lte(250));
    // 4.准备DSL
    request.source().query(queryBuilder);
    // 5.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    handleResponse(response);
}

6. 排序

@Test
void testSort() throws IOException {
    // 1.准备请求
    SearchRequest request = new SearchRequest("hotel");
    // 2.准备DSL
    request.source().query(QueryBuilders.matchAllQuery());
    // 分页
    request.source().from(0).size(5);
    //排序
    request.source().sort("price", SortOrder.ASC);
    // 3.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    handleResponse(response);
}

在这里插入图片描述


7. 分页

在这里插入图片描述

    @Test
    void testHighLight() throws IOException {
        // 1.准备请求
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备DSL
        request.source().query(QueryBuilders.matchQuery("all", "如家"));
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        request.source().highlighter(highlightBuilder.field("name").requireFieldMatch(false));
        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        handleResponse(response);
    }

8. 总结

总而言之,对比着DSL来编写Java代码,其他都是有规律的,几乎都是通过QueryBuilders来构建一些条件,然后通过RestHighLevelClient发送请求获得响应结果,再从响应结果中获取到需要的参数即可

并且熟练掌握编写DSL是能写出的Java代码的关键,脑子能随时想出要编写的DSL语句样子,编写JAVA代码的时候才不会觉得懵逼或者乱


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

起名方面没有灵感

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值