• 【分布式搜索引擎es】



    elasticsearch最擅长的是 搜索数据分析

    数据搜索

    DSL实现

    查询文档

    常见的查询类型包括:

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

    全文检索查询
    使用场景

    全文检索查询的基本流程如下:

    • 对用户搜索的内容做分词,得到词条
    • 根据词条去倒排索引库中匹配,得到文档id
    • 根据文档id找到文档,返回给用户

    比较常用的场景包括:

    • 商城的输入框搜索
    • 百度输入框搜索

    在这里插入图片描述

    在这里插入图片描述
    可以看到,两种查询结果是一样的,为什么?

    因为我们将brand、name、business值都利用copy_to复制到了all字段中。因此你根据三个字段搜索,和根据all字段搜索效果当然一样了。

    但是,搜索字段越多,对查询性能影响越大,因此建议采用copy_to,然后单字段查询的方式。

    精准查询
    精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。常见的有:

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

    在这里插入图片描述
    在这里插入图片描述
    范围查询,一般应用在对数值类型做范围过滤的时候。比如做价格范围过滤。

    在这里插入图片描述
    精确查询常见的有哪些?

    • term查询:根据词条精确匹配,一般搜索keyword类型、数值类型、布尔类型、日期类型字段
    • range查询:根据数值范围查询,可以是数值、日期的范围

    地理坐标查询
    附近查询,也叫做距离查询(geo_distance):查询到指定中心点小于某个距离值的所有文档
    在这里插入图片描述
    复合查询
    复合(compound)查询:复合查询可以将其它简单查询组合起来,实现更复杂的搜索逻辑。常见的有两种:

    • fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名

    • bool query:布尔查询,利用逻辑关系组合多个其它的查询,实现复杂搜索
      在这里插入图片描述
      function score的运行流程如下:

    • 1)根据原始条件查询搜索文档,并且计算相关性算分,称为原始算分(query score)

    • 2)根据过滤条件,过滤文档

    • 3)符合过滤条件的文档,基于算分函数运算,得到函数算分(function score)

    • 4)将原始算分(query score)和函数算分(function score)基于运算模式做运算,得到最终结果,作为相关性算分。

    在这里插入图片描述

    function score query定义的三要素是什么?

    • 过滤条件:哪些文档要加分
    • 算分函数:如何计算function score
    • 加权方式:function score 与 query score如何运算

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

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

    比如在搜索酒店时,除了关键字搜索外,我们还可能根据品牌、价格、城市等字段做过滤:
    在这里插入图片描述
    每一个不同的字段,其查询的条件、方式都不一样,必须是多个不同的查询,而要组合这些查询,就必须用bool查询了。

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

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

    示例:

    需求:搜索名字包含“如家”,价格不高于400,在坐标31.21,121.5周围10km范围内的酒店。

    分析:

    • 名称搜索,属于全文检索查询,应该参与算分。放到must中
    • 价格不高于400,用range查询,属于过滤条件,不参与算分。放到must_not中
    • 周围10km范围内,用geo_distance查询,属于过滤条件,不参与算分。放到filter中

    在这里插入图片描述

    搜索结果处理

    排序
    普通字段排序:
    在这里插入图片描述
    地理坐标排序:
    在这里插入图片描述
    分页

    • from:从第几个文档开始
    • size:总共查询几个文档

    在这里插入图片描述
    分页查询的常见实现方案以及优缺点:

    • from + size
      • 优点:支持随机翻页
      • 缺点:深度分页问题,默认查询上限(from + size)是10000
      • 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
    • after search
      • 优点:没有查询上限(单次查询的size不超过10000)
      • 缺点:只能向后逐页查询,不支持随机翻页
      • 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页

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

    • 1)给文档中的所有关键字都添加一个标签,例如标签

    • 2)页面给标签编写CSS样式
      在这里插入图片描述
      在这里插入图片描述
      注意:

    • 高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。

    • 默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮

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

    总结:

    查询的DSL是一个大的JSON对象,包含下列属性:

    • query:查询条件
    • from和size:分页条件
    • sort:排序条件
    • highlight:高亮条件
      在这里插入图片描述

    RestClient实现

    简单实现:

    @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);
    
        // 4.解析响应
        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();
        // 4.3.遍历
        for (SearchHit hit : hits) {
            // 获取文档source
            String json = hit.getSourceAsString();
            // 反序列化
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
            System.out.println("hotelDoc = " + hotelDoc);
        }
    }
    
    • 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

    查询的基本步骤是:

    1. 创建SearchRequest对象

    2. 准备Request.source(),也就是DSL。

      ① QueryBuilders来构建查询条件

      ② 传入Request.source() 的 query() 方法

    3. 发送请求,得到结果

    4. 解析结果(参考JSON结果,从外到内,逐层解析)

    精确查询
    在这里插入图片描述

    布尔查询
    在这里插入图片描述
    排序、分页
    搜索结果的排序和分页是与query同级的参数,因此同样是使用request.source()来设置。
    在这里插入图片描述

    高亮
    在这里插入图片描述
    高亮的结果与查询的文档结果默认是分离的,并不在一起。

    因此解析高亮的代码需要额外处理:
    在这里插入图片描述

    旅游案例

    我们实现四部分功能:

    • 酒店搜索和分页
    • 酒店结果过滤
    • 我周边的酒店
    • 酒店竞价排名

    酒店搜索和分页

    在这里插入图片描述

    在这里插入图片描述
    前端请求的json结构如下:
    在这里插入图片描述
    定义一个实体类:
    在这里插入图片描述
    返回值

    分页查询,需要返回分页结果PageResult,包含两个属性:

    • total:总条数
    • List:当前页的数据

    在这里插入图片描述
    定义controller:
    在这里插入图片描述

    在这里插入图片描述
    实现业务搜索:
    我们在controller调用了IHotelService,并没有实现该方法,因此下面我们就在IHotelService中定义方法,并且去实现业务逻辑。
    在这里插入图片描述
    实现搜索业务,肯定离不开RestHighLevelClient,我们需要把它注册到Spring中作为一个Bean。在cn.itcast.hotel中的HotelDemoApplication中声明这个Bean
    在这里插入图片描述
    cn.itcast.hotel.service.impl中的HotelService中实现search方法:

    @Override
    public PageResult search(RequestParams params) {
        try {
            // 1.准备Request
            SearchRequest request = new SearchRequest("hotel");
            // 2.准备DSL
            // 2.1.query
            String key = params.getKey();
            if (key == null || "".equals(key)) {
                boolQuery.must(QueryBuilders.matchAllQuery());
            } else {
                boolQuery.must(QueryBuilders.matchQuery("all", key));
            }
    
            // 2.2.分页
            int page = params.getPage();
            int size = params.getSize();
            request.source().from((page - 1) * size).size(size);
    
            // 3.发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            // 4.解析响应
            return handleResponse(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    // 结果解析
    private PageResult handleResponse(SearchResponse response) {
        // 4.解析响应
        SearchHits searchHits = response.getHits();
        // 4.1.获取总条数
        long total = searchHits.getTotalHits().value;
        // 4.2.文档数组
        SearchHit[] hits = searchHits.getHits();
        // 4.3.遍历
        List<HotelDoc> hotels = new ArrayList<>();
        for (SearchHit hit : hits) {
            // 获取文档source
            String json = hit.getSourceAsString();
            // 反序列化
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
    		// 放入集合
            hotels.add(hotelDoc);
        }
        // 4.4.封装返回
        return new PageResult(total, hotels);
    }
    
    • 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

    酒店结果过滤

    需求:添加品牌、城市、星级、价格等过滤功能
    在这里插入图片描述
    在这里插入图片描述

    修改实体类
    ![(https://img-blog.csdnimg.cn/5fceab59828a4f59bce319484cce0c79.png)

    修改搜索业务

    在HotelService的search方法中,只有一个地方需要修改:requet.source().query( … )其中的查询条件。

    在之前的业务中,只有match查询,根据关键字搜索,现在要添加条件过滤,包括:

    • 品牌过滤:是keyword类型,用term查询
    • 星级过滤:是keyword类型,用term查询
    • 价格过滤:是数值类型,用range查询
    • 城市过滤:是keyword类型,用term查询

    多个查询条件组合,肯定是boolean查询来组合:

    • 关键字搜索放到must中,参与算分
    • 其它过滤条件放到filter中,不参与算分

    在这里插入图片描述
    buildBasicQuery的代码如下:

    private void buildBasicQuery(RequestParams params, SearchRequest request) {
        // 1.构建BooleanQuery
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 2.关键字搜索
        String key = params.getKey();
        if (key == null || "".equals(key)) {
            boolQuery.must(QueryBuilders.matchAllQuery());
        } else {
            boolQuery.must(QueryBuilders.matchQuery("all", key));
        }
        // 3.城市条件
        if (params.getCity() != null && !params.getCity().equals("")) {
            boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
        }
        // 4.品牌条件
        if (params.getBrand() != null && !params.getBrand().equals("")) {
            boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        // 5.星级条件
        if (params.getStarName() != null && !params.getStarName().equals("")) {
            boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        }
    	// 6.价格
        if (params.getMinPrice() != null && params.getMaxPrice() != null) {
            boolQuery.filter(QueryBuilders
                             .rangeQuery("price")
                             .gte(params.getMinPrice())
                             .lte(params.getMaxPrice())
                            );
        }
    	// 7.放入source
        request.source().query(boolQuery);
    }
    
    • 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

    我周边的酒店

    需求:我附近的酒店
    在这里插入图片描述
    我们要做的事情就是基于这个location坐标,然后按照距离对周围酒店排序。实现思路如下:

    • 修改RequestParams参数,接收location字段
    • 修改search方法业务逻辑,如果location有值,添加根据geo_distance排序的功能

    修改实体类:
    在这里插入图片描述
    添加距离排序:
    在这里插入图片描述
    在这里插入图片描述
    发现确实可以实现对我附近酒店的排序,不过并没有看到酒店到底距离我多远,这该怎么办?
    在这里插入图片描述
    因此,我们在结果解析阶段,除了解析source部分以外,还要得到sort部分,也就是排序的距离,然后放到响应结果中。

    我们要做两件事:

    • 修改HotelDoc,添加排序距离字段,用于页面显示
    • 修改HotelService类中的handleResponse方法,添加对sort值的获取

    在这里插入图片描述

    酒店竞价排名

    需求:让指定的酒店在搜索结果中排名置顶
    在这里插入图片描述
    这里的需求是:让指定酒店排名靠前。因此我们需要给这些酒店添加一个标记,这样在过滤条件中就可以根据这个标记来判断,是否要提高算分

    比如,我们给酒店添加一个字段:isAD,Boolean类型:

    • true:是广告
    • false:不是广告

    这样function_score包含3个要素就很好确定了:

    • 过滤条件:判断isAD 是否为true
    • 算分函数:我们可以用最简单暴力的weight,固定加权值
    • 加权方式:可以用默认的相乘,大大提高算分

    因此,业务的实现步骤包括:

    1. 给HotelDoc类添加isAD字段,Boolean类型
    2. 挑选几个你喜欢的酒店,给它的文档数据添加isAD字段,值为true
    3. 修改search方法,添加function score功能,给isAD值为true的酒店增加权重

    在这里插入图片描述
    在这里插入图片描述

    private void buildBasicQuery(RequestParams params, SearchRequest request) {
        // 1.构建BooleanQuery
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 关键字搜索
        String key = params.getKey();
        if (key == null || "".equals(key)) {
            boolQuery.must(QueryBuilders.matchAllQuery());
        } else {
            boolQuery.must(QueryBuilders.matchQuery("all", key));
        }
        // 城市条件
        if (params.getCity() != null && !params.getCity().equals("")) {
            boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
        }
        // 品牌条件
        if (params.getBrand() != null && !params.getBrand().equals("")) {
            boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        // 星级条件
        if (params.getStarName() != null && !params.getStarName().equals("")) {
            boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        }
        // 价格
        if (params.getMinPrice() != null && params.getMaxPrice() != null) {
            boolQuery.filter(QueryBuilders
                             .rangeQuery("price")
                             .gte(params.getMinPrice())
                             .lte(params.getMaxPrice())
                            );
        }
    
        // 2.算分控制
        FunctionScoreQueryBuilder functionScoreQuery =
            QueryBuilders.functionScoreQuery(
            // 原始查询,相关性算分的查询
            boolQuery,// 原始查询
            // function score的数组
            new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                // 其中的一个function score 元素
                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                    // 过滤条件
                    QueryBuilders.termQuery("isAD", true),
                    // 算分函数
                    ScoreFunctionBuilders.weightFactorFunction(10)
                )
            });
        request.source().query(functionScoreQuery);
    }
    
    • 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
  • 相关阅读:
    使用小程序实现AI动漫脸特效
    Ubuntu2204安装JDK环境
    Spring5源码3-BeanDefinition
    这份Java面试全栈手册竟让我反败为胜
    Linux——操作指令(ls,cd,touch,mkdir,tree,pwd,rm,man,cp)
    错误: 找不到或无法加载主类 Main
    嵌入式 ADC使用手册完整版 (188977万字)(附源码详细篇)
    四、nginx静态文件的配置
    从java代码到网络编程
    日常SRC中xss小tips
  • 原文地址:https://blog.csdn.net/qq_51240148/article/details/132631519