ElasticSearch学习笔记(五)Bucket聚合、Metric聚合

news2024/9/22 4:21:53

文章目录

  • 前言
  • 9 项目实战
    • 9.3 我周边的酒店
    • 9.4 酒店竞价排名
  • 10 数据聚合
    • 10.1 聚合的分类
    • 10.2 DSL实现聚合
      • 10.2.1 Bucket聚合
      • 10.2.2 聚合结果排序
      • 10.2.3 限定聚合范围
      • 10.2.4 Metric聚合
    • 10.3 RestAPI实现聚合
      • 10.3.1 API语法
      • 10.3.2 业务需求
      • 10.3.3 业务实现
      • 10.3.4 功能测试

前言

ElasticSearch学习笔记(一)倒排索引、ES和Kibana安装、索引操作
ElasticSearch学习笔记(二)文档操作、RestHighLevelClient的使用
ElasticSearch学习笔记(三)RestClient操作文档、DSL查询文档、搜索结果排序
ElasticSearch学习笔记(四)分页、高亮、RestClient查询文档

9 项目实战

9.3 我周边的酒店

  • 1)需求分析

点击页面右侧的地图组件的定位按钮,将位置信息发送给后台,后台基于位置坐标,按照距离远近对附近的酒店进行排序。

  • 2)修改RequestParams参数,接收location字段
// cn.hsgx.hotel.pojo.RequestParams

@Data
public class RequestParams {
    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
    private String brand;
    private String city;
    private String starName;
    private Integer minPrice;
    private Integer maxPrice;
    // 位置信息
    private String location;
}
  • 3)在HotelServiceImpl实现类的handleQueryParams()方法中,添加根据地理位置排序功能
// cn.hsgx.hotel.service.impl.HotelServiceImpl

private void handleQueryParams(RequestParams params, SearchRequest request) {
    
    // ......
    
    // 1.6 根据地理位置排序
    String location = params.getLocation();
    if(StringUtils.isNotBlank(location)) {
        request.source().sort(SortBuilders
                .geoDistanceSort("location", new GeoPoint(location))
                .order(SortOrder.ASC)
                .unit(DistanceUnit.KILOMETERS)
        );
    }
    // 2.设置查询条件
    request.source().query(functionScoreQuery);
}
  • 4)功能测试

查阅日志中打印的DSL语句及其返回信息:

  • 5)获取附近每个酒店距离当前位置的具体距离值

根据距离进行排序时,具体的距离值也会一起返回,只是不在source部分:

因此在结果解析时,除了解析source部分,还要得到sort部分,也就是排序的距离,然后放到响应结果中。

修改HotelServiceImpl类中的handleResponse方法,添加对sort值的获取:

// cn.hsgx.hotel.service.impl.HotelServiceImpl

private PageResult handleResponse(SearchResponse response) {
    SearchHits searchHits = response.getHits();
    // 4.1.总条数
    long total = searchHits.getTotalHits().value;
    // 4.2.获取文档数组
    SearchHit[] hits = searchHits.getHits();
    // 4.3.遍历
    List<HotelDoc> hotels = new ArrayList<>(hits.length);
    for (SearchHit hit : hits) {
        // 4.4.获取source
        String json = hit.getSourceAsString();
        // 4.5.反序列化,非高亮的
        HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
        // 4.6.排序信息
        Object[] sortValues = hit.getSortValues();
        if (sortValues.length > 0) {
            hotelDoc.setDistance(sortValues[0]);
        }
        // 4.7.放入集合
        hotels.add(hotelDoc);
    }
    return new PageResult(total, hotels);
}

9.4 酒店竞价排名

  • 1)需求分析

让指定的酒店在搜索结果中排名置顶。 要实现这个功能,其实就是使用function_score查询改变查询结果的算分,算分高了,排名就会靠前。

function_score查询包含3个要素:

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

为此,则需要给这些酒店添加一个标记,这样在过滤条件中就可以根据这个标记来判断是否要提高算分。比如,给酒店添加一个字段:isAD,Boolean类型,为true时表示是广告,为false时表示不是广告。

这样function_score查询的3个要素也就确定了:

  • 过滤条件:判断isAD是否为true

  • 算分函数:可以用最简单的weight,固定加权值

  • 加权方式:可以用默认的相乘,大大提高算分

  • 2)随意挑选几个酒店,添加isAD字段,设置为true

  • 3)添加算分函数查询

之前是用的boolean查询,现在要改成function_socre查询。查询结构如下:

修改HotelServiceImpl类的handleQueryParams()方法:

// cn.hsgx.hotel.service.impl.HotelServiceImpl

private void handleQueryParams(RequestParams params, SearchRequest request) {
    // 1.准备boolean查询
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    
    // ......
    
    // 2.算分函数查询
    FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
            boolQuery, // 原始查询,boolQuery
            new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{ // function数组
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                            QueryBuilders.termQuery("isAD", true), // 过滤条件
                            ScoreFunctionBuilders.weightFactorFunction(10) // 算分函数
                    )
            }
    );
    // 3.设置查询条件
    request.source().query(functionScoreQuery);
}
  • 4)功能测试

添加前:

添加后:

10 数据聚合

ES的数据聚合(aggregations)功能可以极其方便地实现对数据的统计、分析、运算。 例如可以统计出什么品牌的酒店最受欢迎?这些酒店的平均价格、最高价格、最低价格是多少?这些酒店每月的订单情况如何?

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

10.1 聚合的分类

常见的聚合有三类:

  • 桶(Bucket)聚合:用来对文档做分组
    • TermAggregation:按照文档字段值分组,例如按照品牌分组、按照城市分组
    • DateHistogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
    • 参加聚合的字段必须是keyword、日期、数值、布尔类型
  • 度量(Metric)聚合:用以计算一些值,例如最大值、最小值、平均值等
    • Avg:求平均值
    • Max:求最大值
    • Min:求最小值
    • Stats:同时求Max、Min、Avg、Sum等
  • 管道(pipeline)聚合:以其它聚合的结果为基础做聚合

10.2 DSL实现聚合

10.2.1 Bucket聚合

例如,现在要统计所有数据中的酒店品牌有几种,也就是按照品牌对数据进行分组。此时可以根据酒店品牌的名称做聚合,也就是Bucket聚合。其语法如下:

GET /hotel/_search
{
  "size": 0, //设置size为0,则结果中不包含文档,只包含聚合结果
  "aggs": { //定义聚合
    "brandAgg": { //给聚合取个名字,可自定义
      "terms": { //聚合的类型,按照文档字段值聚合,所以选择term
        "field": "brand", //参与聚合的字段
        "size": 20 //希望获取的聚合结果数量
      }
    }
  }
}

10.2.2 聚合结果排序

默认情况下,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照_count降序排序。

我们可以指定order属性,自定义聚合的排序方式:

GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "size": 20,
        "order": {
          "_count": "asc" //按照_count升序排列
        }
      }
    }
  }
}

10.2.3 限定聚合范围

默认情况下,Bucket聚合是对索引库的所有文档做聚合,但真实场景下,用户会输入搜索条件,因此聚合必须是对搜索结果聚合。

要限定聚合的文档范围,只要添加query条件即可:

GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "size": 20,
        "order": {
          "_count": "asc" 
        }
      }
    }
  },
  "query": { //查询条件,只对价格在200元一下的文档进行聚合
    "range": {
      "price": {
        "lte": 200
      }
    }
  }
}

10.2.4 Metric聚合

前面是对酒店按照品牌进行分组,形成了一个个桶。现在需要对桶内的酒店做运算,获取每个品牌酒店的用户评分的Min、Max、Avg等值。

这就要用到Metric聚合了,例如stat聚合:就可以获取Min、Max、Avg等结果。其语法如下:

GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "size": 20,
        "order": {
          "scoreAgg.avg": "desc" //按照平均分进行降序排序 
        }
      },
      "aggs": { //Metric是品牌聚合的子聚合,也就是分组后对每组分别计算
        "scoreAgg": { //聚合名称
          "stats": { //聚合类型,这里stats可以计算Min、Max、Avg等
            "field": "score" //聚合字段,这里是score
          }
        }
      }
    }
  }
}

10.3 RestAPI实现聚合

10.3.1 API语法

聚合条件与query条件同级别,因此需要使用request.source()来指定聚合条件。其API如下:

聚合查询的结果也与query查询的结果不一样,其解析API如下:

10.3.2 业务需求

在搜索页面,可以根据城市、星级、品牌进行过滤,但这些信息不应该在页面写死,而是要通过聚合查询酒店数据得到,进行动态展示。

例如,用户搜索“广州塔”,那搜索得到的酒店肯定是在广州塔附近的,因此城市只能是广州,此时城市列表中就不应该显示北京、深圳、杭州这些了。

也就是说,搜索结果中包含哪些城市,页面就应该列出哪些城市;搜索结果中包含哪些品牌,页面就应该列出哪些品牌。

在原有查询的基础上,利用Bucket聚合,对搜索结果中的文档基于品牌分组、基于城市分组,就能得知包含哪些品牌、哪些城市了。

10.3.3 业务实现

cn.hsgx.hotel.web.HotelController类中添加一个方法,遵循下面的要求:

  • 请求方式:POST
  • 请求路径:/hotel/filters
  • 请求参数:RequestParams
  • 返回值类型:Map<String, List<String>>
// cn.hsgx.hotel.web.HotelController

@PostMapping("/filters")
public Map<String, List<String>> filters(@RequestBody RequestParams params) {
    return hotelService.getFilters(params);
}

IHotelService接口及其实现类HotelServiceImpl中实现该getFilters()方法:

// cn.hsgx.hotel.service.impl.HotelServiceImpl

@Override
public Map<String, List<String>> getFilters(RequestParams params) {
    try {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备DSL
        // 2.1.基础query
        handleQueryParams(params, request);
        // 2.2.设置size
        request.source().size(0);
        // 2.3.设置聚合条件
        buildAggregation(request);
        // 3.发出请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        // 4.解析结果
        Map<String, List<String>> result = new HashMap<>();
        Aggregations aggregations = response.getAggregations();
        // 4.1.根据品牌名称,获取品牌结果
        List<String> brandList = getAggByName(aggregations, "brandAgg");
        result.put("品牌", brandList);
        // 4.2.根据品牌名称,获取品牌结果
        List<String> cityList = getAggByName(aggregations, "cityAgg");
        result.put("城市", cityList);
        // 4.3.根据品牌名称,获取品牌结果
        List<String> starList = getAggByName(aggregations, "starAgg");
        result.put("星级", starList);
        return result;
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

private void buildAggregation(SearchRequest request) {
    request.source().aggregation(AggregationBuilders
            .terms("brandAgg")
            .field("brand")
            .size(100)
    );
    request.source().aggregation(AggregationBuilders
            .terms("cityAgg")
            .field("city")
            .size(100)
    );
    request.source().aggregation(AggregationBuilders
            .terms("starAgg")
            .field("starName")
            .size(100)
    );
}

private List<String> getAggByName(Aggregations aggregations, String aggName) {
    // 4.1.根据聚合名称获取聚合结果
    Terms brandTerms = aggregations.get(aggName);
    // 4.2.获取buckets
    List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
    // 4.3.遍历
    List<String> brandList = new ArrayList<>();
    for (Terms.Bucket bucket : buckets) {
        // 4.4.获取key
        String key = bucket.getKeyAsString();
        brandList.add(key);
    }
    return brandList;
}

10.3.4 功能测试

日志中打印的DSL语句如下:

本节完,更多内容请查阅分类专栏:微服务学习笔记

感兴趣的读者还可以查阅我的另外几个专栏:

  • SpringBoot源码解读与原理分析
  • MyBatis3源码深度解析
  • Redis从入门到精通
  • MyBatisPlus详解
  • SpringCloud学习笔记

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2102936.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

人工智能、机器学习和深度学习有什么区别?应用领域有哪些?

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发…

RLHF(带有人类反馈的强化学习)初探

我的目标是&#xff0c;在决策游戏上学习某人风格&#xff0c;可以让人对战“带有某人风格”的AI&#xff0c;比如你可以在这里对战“sky风格的AI”,这样的效果。 我最开始受到的启发来源于xbox的广告《爸爸的幽灵车》&#xff0c;已故人在游戏中留下的速度记录的固定轨迹。 …

传统CV算法——仿射变换原理及应用

可以理解一下常规的翻转和平移。“线性变换”“平移”空间变换中的仿射变换对应着五种变换&#xff0c;平移&#xff0c;缩放&#xff0c;旋转&#xff0c;翻转&#xff0c;错切。而这五种变化由原图像转变到变换图像的过程&#xff0c;可以用仿射变换矩阵进行描述。而这个变换…

异地多活架构计算设计

随着互联网的飞速发展,企业对业务连续性和高可用性的需求日益增加。异地多活架构作为一种高可靠性的系统设计方案,通过在地理上分散的多个数据中心部署应用和数据,有效降低了单一故障点对整个系统的影响,确保业务在灾难发生时能够持续运行。 架构设计策略 业务解耦:将系…

Servelet学习-24.9.3

文章目录 前言一、Servelet概述1.1 简单入门&#xff1a;2.2 生命周期 二、HttpServletRequest对象2.1 常用方法 三、HttpServeletResponse对象 前言 九月&#xff0c;加油 一、Servelet概述 Servelet&#xff1a; server applet servelet就是一个接口,定义了Java类被浏览器访…

《大道平渊》· 廿壹 —— 杀心篇:何谓 “杀心”?本质上,就是寻求杀心的一个过程。

《大道平渊》 "行有不得&#xff0c;反求诸己。" ——《论语 学而》 指的是遇事遭困&#xff0c;须在自身寻因&#xff0c;而非怨天尤人&#xff0c;一味地归咎于外因。 凡事向内求也&#xff0c;多多自省&#xff0c;提高自身的修养和能力&#xff0c;取得成功。…

基于yolov8的106种鲜花识别花朵检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的106种鲜花识别花朵检测系统是一项融合了先进深度学习技术的创新应用。该系统利用YOLOv8算法&#xff0c;这一目前最先进的目标检测模型之一&#xff0c;实现了对106种不同花卉的快速、准确识别。 YOLOv8以其速度快、准确性高和鲁棒性强的特点&#…

模拟算法专题——算法介绍算法讲解力扣实战应用

目录 1、模拟算法介绍 2、算法应用【leetcode】 2.1 替换所有的问号 2.1.1 算法思想 2.1.2 算法代码 2.2 提莫攻击 2.2.1 算法思想 2.2.2 算法代码 2.3 Z字形变换 2.3.1 算法思想 2.3.2 算法代码 2.4 外观数列 2.4.1 算法思想 2.4.2 算法代码 2.5 数青蛙 2.5.1 算…

复旦NLP团队新作:大规模语言模型从理论到实践PDF版

2022 年 11 月&#xff0c;Chat GPT 的问世展示了大语言模型的强大潜能&#xff0c;并迅速引起了广泛关注。Chat GPT 能够有效理解用户需求&#xff0c;并根据上下文提供恰当的回答。它不仅可以进行日常对话&#xff0c;还能够完成复杂任务&#xff0c;如撰写文章、回答问题等。…

测试工程师学历路径:从功能测试到测试开发

现在软件从业者越来越多&#xff0c;测试工程师的职位也几近饱和&#xff0c;想要获得竞争力还是要保持持续学习。基本学习路径可以从功能测试-自动化测试-测试开发工程师的路子来走。 功能测试工程师&#xff1a; 1、软件测试基本概念&#xff1a; 学习软件测试的定义、目的…

Cubase里如何使用效果器插件?

Cubase里如何使用效果器插件&#xff1f;具体操作步骤如下&#xff1a; 1、首先&#xff0c;在你的电脑上打开Cubase软件。进入页面后&#xff0c;单击菜单栏上的设备以进入插件管理器&#xff0c;如下所示&#xff0c;然后继续下一步。 2、接下来&#xff0c;弹出插件管理器窗…

银行业智能化转型:智能客服的崛起与挑战

更多内容个人网站&#xff1a;孔乙己大叔 在当今这个科技日新月异的时代&#xff0c;银行业作为传统金融业的支柱&#xff0c;正经历着一场前所未有的变革。智能客服的兴起&#xff0c;不仅重塑了银行的服务模式&#xff0c;也深刻影响着银行员工的职业生涯。这场由技术驱动的变…

遥控器显示分别对应的无人机状态详解!!

1. 电量显示 遥控器电量&#xff1a;遥控器上通常会显示自身的电池电量&#xff0c;以提醒用户及时充电。 无人机电量&#xff1a;部分高端遥控器还会显示无人机的电池电量&#xff0c;以进度条或百分比的形式表示&#xff0c;帮助用户了解无人机的续航能力。 2. 飞行模式与…

24并发设计模式——线程池模式

一、线程池模式介绍 线程池模式&#xff08;Thread Pool Pattern&#xff09;是一种并发设计模式&#xff0c;用于管理和循环使用线程资源以处理大量任务。它旨在提高系统性能和资源利用率&#xff0c;特别是在需要频繁创建和销毁线程的环境中。 1、线程池模式结构图 线程池管…

弱通联条件下的人机混合控制

弱通联条件下的人机混合控制指的是在通信连接不稳定或不可靠的情况下&#xff0c;如何有效地将人工控制与自动化/智能化系统结合起来进行操作。这种情况下&#xff0c;控制系统需要设计得既能在网络问题时维持基本功能&#xff0c;又能充分利用人工输入来补充自动系统的不足。下…

Win10提示输入网络凭据解决方法(Win10 Prompts for Entering Network Credentials Solution)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

什么?!新版 Node.js V22.5 自带 SQLite 模块啦

前言 2024年7月&#xff0c;Node.js V22.5.0 版本发布&#xff0c;自带了 SQLite 模块&#xff0c;意味着开发者可以直接在程序中使用 SQLite 数据库&#xff0c;而无需引入第三方库&#x1f44d;。 话不多说&#xff0c;感觉来体验一波✈。 安装/升级 我现在用的是21.4.0版…

xss.haozi.me

0x03 审查源码我们发现&#xff0c;括号, 方括号都被过滤了 这段代码是一个简单的 JavaScript 函数&#xff0c;名为 render。它接受一个字符串 input 作为参数&#xff0c;并返回一个新的字符串&#xff0c;其中所有圆括号 ( 和 ) 都被移除了。 函数内部定义了一个正则表达式…

三级_网络技术_58_应用题

一、 请根据下图所示网络结构回答下列问题。 1.填写RG的路由表项。 目的网络/掩码长度输出端口__________S0&#xff08;直接连接&#xff09;__________S1&#xff08;直接连接&#xff09;__________S0__________S1__________S0__________S1 (2)如果在不改变路由表项的前提…

notepad++将换行替换成空

将多行里的换行置为一行&#xff0c;例如将下面的6行置为3行 crrlH打开替换框&#xff0c; 替换目标为【,\r\n】&#xff0c;替换成空&#xff0c;勾选循环查找和 正则表达式&#xff0c;全部替换即可。 替换后的效果