ElasticSearch学习笔记(六)自动补全、拼音分词器、RabbitMQ实现数据同步

news2024/9/22 7:40:22

文章目录

  • 前言
  • 11 自动补全
    • 11.1 拼音分词器
    • 11.2 自定义分词器
    • 11.3 自动补全查询
  • 12 数据同步
    • 12.1 实现方案
      • 12.1.1 同步调用
      • 12.1.2 异步通知
      • 12.1.3 监听binlog
    • 12.2 异步通知实现数据同步
      • 12.2.1 声明交换机和队列
      • 12.2.2 发送MQ消息
      • 12.2.3 接收MQ消息并操作ES

前言

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

11 自动补全

在搜索页面,当用户在搜索框输入字符时,应该提示出与该字符有关的搜索项。例如:

这种根据用户输入的字母,提示完整词条的功能,就是自动补全。由于需要根据拼音字母来推断,因此要用到拼音分词功能。

11.1 拼音分词器

要实现根据拼音字母做自动补全,就必须对文档按照拼音分词。

在GitHub上下载elasticsearch的拼音分词插件,地址:https://github.com/medcl/elasticsearch-analysis-pinyin

  • 1)将下载的elasticsearch-analysis-pinyin-7.12.1.zip上传到服务器并解压

  • 2)将插件移动到ES的插件目录/var/lib/docker/volumes/es-plugins/_data/

  • 3)重启ES

  • 4)功能测试

11.2 自定义分词器

默认的拼音分词器会将每个汉字单独分为拼音,但仍然不能满足需求,我们希望的是每个词条形成一组拼音。为此需要对拼音分词器做个性化定制,形成自定义分词器。

ES分词器(analyzer)的组成包含三部分:

  • character filters:在tokenizer之前对文本进行处理,例如删除字符、替换字符等;
  • tokenizer:将文本按照一定的规则切割成词条(term),例如keyword(不分词)、ik_smart等;
  • tokenizer filter:将tokenizer输出的词条做进一步处理,例如大小写转换、同义词处理、拼音处理等。

声明自定义分词器的DSL语法如下:

PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": { // 自定义分词器名称
          "tokenizer": "ik_max_word", // 词条切割规则
          "filter": "py" // 自定义的处理器
        }
      },
      "filter": {
        "py": { // 自定义处理器的具体实现,使用拼音处理
          "type": "pinyin",
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "my_analyzer",
        "search_analyzer": "ik_smart"
      }
    }
  }
}

功能测试:

11.3 自动补全查询

ES提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中字段的类型也有一些约束:

  • 参与补全查询的字段必须是completion类型。
  • 字段的内容一般是用来补全的多个词条形成的数组。

例如,把酒店的品牌、城市、商圈等信息放入一个completion类型的字段中,作为自动补全的提示。

  • 1)由于已经创建好的索引库是无法修改的,因此要删除然后重新创建
DELETE /hotel
  • 2)修改索引库结构,主要做如下改动:设置自定义拼音分词器;修改nameall字段,使用自定义分词器;添加一个新字段suggestion,类型为completion类型,使用自定义的分词器内容
// 酒店数据索引库
PUT /hotel
{
  "settings": {
    "analysis": {
      "analyzer": {
        "text_anlyzer": {
          "tokenizer": "ik_max_word",
          "filter": "py"
        },
        "completion_analyzer": {
          "tokenizer": "keyword",
          "filter": "py"
        }
      },
      "filter": {
        // 设置自定义拼音分词器
        "py": {
          "type": "pinyin",
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "id":{
        "type": "keyword"
      },
      // 使用自定义分词器
      "name":{
        "type": "text",
        "analyzer": "text_anlyzer",
        "search_analyzer": "ik_smart",
        "copy_to": "all"
      },
      "address":{
        "type": "keyword",
        "index": false
      },
      "price":{
        "type": "integer"
      },
      "score":{
        "type": "integer"
      },
      "brand":{
        "type": "keyword",
        "copy_to": "all"
      },
      "city":{
        "type": "keyword"
      },
      "starName":{
        "type": "keyword"
      },
      "business":{
        "type": "keyword",
        "copy_to": "all"
      },
      "location":{
        "type": "geo_point"
      },
      "pic":{
        "type": "keyword",
        "index": false
      },
      // 使用自定义分词器
      "all":{
        "type": "text",
        "analyzer": "text_anlyzer",
        "search_analyzer": "ik_smart"
      },
      // 添加一个新字段suggestion,类型为completion类型
      "suggestion":{
          "type": "completion",
          "analyzer": "completion_analyzer"
      }
    }
  }
}

  • 3)给HotelDoc类添加suggestion字段,内容包含brandbusinesscity
public class HotelDoc {

    // ......
    
    private String brand;
    private String city;
    private String business;
    private List<String> S;

    public HotelDoc(Hotel hotel) {
    
        // ......
        
        this.brand = hotel.getBrand();
        this.city = hotel.getCity();
        this.business = hotel.getBusiness();
        // 组装suggestion
        if(this.business.contains("/")){
            // business有多个值,需要切割
            String[] arr = this.business.split("/");
            // 添加元素
            this.suggestion = new ArrayList<>();
            this.suggestion.add(this.brand);
            this.suggestion.add(this.city);
            Collections.addAll(this.suggestion, arr);
        } else {
            this.suggestion = Arrays.asList(this.brand, this.business, this.city);
        }
    }
}
  • 4)重新导入数据到hotel索引库

  • 5)DSL实现自动补全查询
GET /hotel/_search
{
  "suggest": {
    "mySuggestion": { // 自定义名字
      "text": "sh", // 关键字
      "completion": {
        "field": "suggestion", // 要补全的字段
        "skip_duplicates": true, // 跳过重复项
        "size": 10 // 查询10条数据
      }
    }
  }
}

  • 5)RestAPI实现自动补全查询
@Test
public void testSuggestion() throws IOException {
    // 1.创建Request对象
    SearchRequest request = new SearchRequest("hotel");
    // 2.准备参数
    request.source().suggest(
            new SuggestBuilder()
                    .addSuggestion("mySuggestion", // 自定义名字
                    SuggestBuilders
                            .completionSuggestion("suggestion") // 要补全的字段
                            .prefix("sh") // 关键字
                            .skipDuplicates(true) // 跳过重复项
                            .size(10)) // 查询10条数据
    );
    // 3.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4.处理结果
    Suggest suggest = response.getSuggest();
    // 根据名称获取补全结果
    CompletionSuggestion mySuggestion = suggest.getSuggestion("mySuggestion");
    // 获取options并遍历
    for (CompletionSuggestion.Entry.Option option : mySuggestion.getOptions()) {
        // 获取option中的text
        String text = option.getText().string();
        System.out.println(text);
    }
}

执行以上单元测试,得到如下结果:

12 数据同步

ES中的酒店数据来源于MySQL数据库,因此MySQL数据发生改变时,ES也必须跟着改变,这个就是ES与MySQL之间的数据同步。

12.1 实现方案

12.1.1 同步调用

如上图所示,hotel-demo酒店搜索服务对外提供了一个接口。hotel-admin酒店管理服务在完成数据库操作后,直接调用hotel-demo提供的接口,修改ES中的数据。这种方式实现简单,但业务耦合度较高。

12.1.2 异步通知

如上图所示,hotel-admin酒店管理服务在完成数据库操作后,发送对应的MQ消息到队列。hotel-demo酒店搜索服务监听MQ,接收到消息后完成ES数据修改。这种方式实现难度一般,且低耦合,但对MQ的可靠性依赖较高。

12.1.3 监听binlog

如上图所示,MySQL开启了binlog功能,hotel-admin酒店管理服务完成增、删、改操作都会记录在binlog中
。hotel-demo酒店搜索服务基于canal监听binlog变化,实时更新ES中的内容。这种方式完全解除服务间耦合,但开启binlog会增加数据库负担,且实现复杂度高。

12.2 异步通知实现数据同步

12.2.1 声明交换机和队列

使用docker安装rabbitmq的方法参考:RabbitMQ学习笔记(一)RabbitMQ部署、5种队列模型

  • 1)引入依赖
<!--amqp-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  • 2)声明交换机和队列的名称
public class MqConstants {
    /**
     * 交换机名称
     */
    public final static String HOTEL_EXCHANGE = "hotel.topic";
    /**
     * 新增和修改的队列名称
     */
    public final static String HOTEL_INSERT_QUEUE = "hotel.insert.queue";
    /**
     * 删除的队列名称
     */
    public final static String HOTEL_DELETE_QUEUE = "hotel.delete.queue";
    /**
     * 新增或修改的RoutingKey
     */
    public final static String HOTEL_INSERT_KEY = "hotel.insert";
    /**
     * 删除的RoutingKey
     */
    public final static String HOTEL_DELETE_KEY = "hotel.delete";
}
  • 3)声明交换机和队列
package com.star.sc.totel.mq;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MqConfig {
    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange(MqConstants.HOTEL_EXCHANGE, true, false);
    }

    @Bean
    public Queue insertQueue(){
        return new Queue(MqConstants.HOTEL_INSERT_QUEUE, true);
    }

    @Bean
    public Queue deleteQueue(){
        return new Queue(MqConstants.HOTEL_DELETE_QUEUE, true);
    }

    @Bean
    public Binding insertQueueBinding(){
        return BindingBuilder.bind(insertQueue()).to(topicExchange()).with(MqConstants.HOTEL_INSERT_KEY);
    }

    @Bean
    public Binding deleteQueueBinding(){
        return BindingBuilder.bind(deleteQueue()).to(topicExchange()).with(MqConstants.HOTEL_DELETE_KEY);
    }
}

12.2.2 发送MQ消息

@Autowired
private RabbitTemplate rabbitTemplate;

@Test
public void testSaveHotel() {
    Hotel hotel = new Hotel();
    hotel.setId(2359697L);
    rabbitTemplate.convertAndSend(MqConstants.HOTEL_EXCHANGE, MqConstants.HOTEL_INSERT_KEY, hotel.getId());
}

执行以上单元测试,向rabbitmq发送消息,在管理页面可以看到这条消息:

12.2.3 接收MQ消息并操作ES

package com.star.sc.totel.mq;

import com.alibaba.fastjson.JSON;
import com.star.sc.totel.pojo.Hotel;
import com.star.sc.totel.pojo.HotelDoc;
import com.star.sc.totel.service.IHotelService;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class HotelListener {

    @Autowired
    private RestHighLevelClient client;
    @Autowired
    private IHotelService hotelService;

    /**
     * 监听酒店新增或修改的业务
     * @param id 酒店id
     */
    @RabbitListener(queues = MqConstants.HOTEL_INSERT_QUEUE)
    public void listenHotelInsertOrUpdate(Long id) throws IOException {
        System.out.println("监听到酒店新增或修改的业务,id=" + id);
        // 1.根据id查询酒店数据
        Hotel hotel = hotelService.getById(id);
        HotelDoc hotelDoc = new HotelDoc(hotel);
        // 2.发送请求
        IndexRequest request = new IndexRequest("hotel")
                .id(hotel.getId().toString());
        request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
        client.index(request, RequestOptions.DEFAULT);
    }

    /**
     * 监听酒店删除的业务
     * @param id 酒店id
     */
    @RabbitListener(queues = MqConstants.HOTEL_DELETE_QUEUE)
    public void listenHotelDelete(Long id){
        System.out.println("监听到酒店删除的业务,id=" + id);
    }

}

启动该监听器,日志显示读取到了MQ消息:

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

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

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

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

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

相关文章

数据结构————单向链表

头插&#xff1a; 尾插&#xff1a; 头删&#xff1a; 尾删&#xff1a;

一种常用嵌入式开发代码库

链接&#xff1a;https://gitee.com/zhangxinyuanqi/varch 使用开源协议&#xff1a;GPL-2.0 varch简介 varch&#xff08;we-architecture&#xff0c;意为我们的框架库&#xff09;是嵌入式C语言常用代码模块库&#xff0c;包含了嵌入式中常用的算法库, 数据结构&#xff…

JPA关联MyBatis

3.1 JPA 多表查询 多表查询在 Spring Data JPA 中有两种实现方式&#xff0c;第一种是创建一个结果集的接口来接受多表连接查询后的结果&#xff0c;第二种是利用 JPA 的关联映射来实现 3.1.1 数据库表及关系 CRM 数据库中除 sys_user(用户)表外&#xff0c;还包括sys_role(角…

触想内嵌式工业一体机应用于智能检票机改善旅游体验

一、行业发展背景 每年下半年&#xff0c;暑假、中秋、国庆总是接踵而至&#xff0c;随之而来的出游高峰一波接一波。凶猛需求之下&#xff0c;各地景区、游乐园客流压力加大&#xff0c;特别在检票环节&#xff0c;人工检票效率低、秩序混乱&#xff0c;导致常常出现检票口人山…

POL(Point-of-Load)负载点电源

负载点&#xff08;POL&#xff09;电源在靠近负载处单独放置电源调节器(线性稳压器或DC-DC)&#xff0c;解决了高性能半导体器件&#xff0c;例如&#xff1a;微控制器、ASIC等&#xff0c;所面临的高峰值电流、低噪声裕量等设计挑战。 一般我们会把负载点电源尽量靠近负载放…

乾元通多卡聚合技术在无人配送车应用领域通信保障方案

在无人驾驶公交车、安防车、售卖车、清扫车相继亮相后&#xff0c;无人配送车在全国各地也陆续“上岗”&#xff0c;为我们的城市带来了与众不同的“智慧体验”&#xff0c;让城市有了“科技温度”。 无人配送车在营业部装载好快递后&#xff0c;会按照提前规划好的路线出发&a…

sqli-labs靶场通关攻略 61-65

主页有sqli-labs靶场通关攻略 1-60 第六一关 less-61 步骤一&#xff1a;闭合方式&#xff1a;?id1)) -- 步骤二&#xff1a;查询数据库 ?id1)) and updatexml(1,concat(1,database()),1) -- 步骤三&#xff1a;查出网站的数据库表名 ?id1)) and updatexml(1,concat(0x7e…

15 用户管理

如果我们只能使用root用户&#xff0c;这样存在安全隐患。这时&#xff0c;就需要使用mysql的用户管理 张三只能操纵mytest这个库&#xff0c;李四只能操纵msg这个库&#xff0c;如果给他们root账户&#xff0c;就可以操纵所有库&#xff0c;风险太大 用户 用户信息 用户都存…

项目技巧三

目录 我们现在要实现一个接口功能 1.我们先书写sql语句 2.编写接口 3.书写业务逻辑 4.书写mapper 结果&#xff1a; 缺点&#xff1a;没有根据涨跌幅区间的大小来排序 1.yml文件 2.在value object包下映射这个yml文件 3.开启这个配置类进行映射&#xff0c;并把它交给s…

勒索攻击后快速恢复的八个关键步骤,如何避免被勒索攻击

勒索软件攻击依然是当今企业面临的最大安全威胁之一。根据德迅云安全收集的报告&#xff0c;59%的企业在2023年遭遇了勒索软件攻击&#xff0c;其中56%的受害者最终选择支付赎金以恢复数据。更为严重的是&#xff0c;63%的勒索金额达到或超过了100万美元&#xff0c;平均支付金…

RS232转RS485

1.232转485转换器 232转485转换器是RS-232与RS-485之间的双向接口的转换器&#xff0c;应用于主控机之间&#xff0c;主控机与单片机或外设之间构成点到点&#xff0c;点到多点远程多机通信网络&#xff0c;实现多机应答通信&#xff0c;广泛地应用于工业自动化控制系统&#x…

免费申请aws一年免费服务器使用教程

由于近期要测试一个公网项目&#xff0c;对比之下&#xff0c;选择了aws服务器&#xff0c;免费使用一年。 准备&#xff1a;一个visa信用卡即可&#xff0c;需要一个外网邮箱&#xff08;我这边使用的hotmail&#xff09; 注册的步骤不再赘述&#xff0c;切记几个点&#xff0…

windows 环境下安装OpenCV For Java

windows 环境下安装OpenCV For Java 进入官网下载对应安装包 opencv官网地址&#xff1a; https://opencv.org/releases/ source里面是官方的xml文件&#xff0c;包含人脸识别 windows下载下来是一个exe文件&#xff0c;里面包含项目需要的jar包 执行opencv-4.9.0-windows.ex…

被低估的SQL

SQL是现代数据库管理系统中不可或缺的一部分。尽管它的使用已十分普遍&#xff0c;但在数据处理领域&#xff0c;SQL的某些功能和潜力仍然被许多人低估。接下来&#xff0c;小编将与您一起&#xff0c;探讨SQL的一些被忽视的特性&#xff0c;揭示它在数据管理中的真正实力。 1.…

《深度学习》OpenCV轮廓检测 轮廓近似、模板匹配 解析及实现

一、轮廓近似 1、什么是轮廓近似 指对轮廓进行逼近或拟合&#xff0c;得到近似的轮廓。在图像处理中&#xff0c;轮廓表示了图像中物体的边界&#xff0c;因此轮廓近似可以用来描述和识别物体的形状。 2、参数解析 1&#xff09;用法 import cv2 approx cv2.approxPolyDP(cu…

Java SpringBoot构建助农平台,三步实现高效捐赠,2025届设计新思路!

✍✍计算机毕业编程指导师** ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java…

如何应对 Android 面试官 -> 内存如何进行优化?玩转 LeakCanary

前言 本章主要围绕内存相关的知识点讲解&#xff1b; 内存分配 在内存优化中&#xff0c;我们通常需要借助一些常用的 adb 命令&#xff0c;方便我们快速定位&#xff0c;下面是一些常用的 adb 命令总结 常用 adb 命令 adb shell getprop ro.product.model // 手机型号adb s…

HTTP“请求”和“响应”的报头及正文详解

目录 一、请求 "报头" (header) 二、请求 "正文" (body) 2.1 application/x-www-form-urlencoded 2.2 multipart/form-data 2.3 application/json 三、HTTP 响应状态码 四、响应 "报头" (header) 五、响应 "正文" (body) 5.1…

如何设置Word文档部分内容无法编辑?

工作中&#xff0c;我们可能会在word中制作一些请柬、表格之类的&#xff0c;有些文件内容不想要进行修改&#xff0c;为了防止他人随意修改内容。我们可以设置限制编辑&#xff0c;可以对一部分内容设置限制编辑&#xff0c;具体方法如下&#xff1a; 我们将需要将可以编辑的…

记录 PyQt6 / PySide 6 自定义边框窗口的 Bug 及可能可行的解决方案:窗口抖动和添加 DWM 环绕阴影的大致原理

前言&#xff1a; 本篇文章将要讨论我在前不久发表的关于 PyQt6 / PySide6 自定义边框窗口代码及内容中的问题&#xff1a; &#xff08;终&#xff09;PyQt6 / PySide 6 Pywin32 自定义标题栏窗口 完全还原 Windows 原生窗口边框特效_pyside6 win32 无边框窗口-CSDN博客ht…