当前位置:   article > 正文

SpringCloud + RabbitMQ + Docker + Redis + 搜索 + 分布式--实用篇笔记_springcloud+rabbitmq+docker+redis+搜索+分布式

springcloud+rabbitmq+docker+redis+搜索+分布式

目录

 1、微服务框架

1.1、认识微服务

2、服务拆分及远程调用

2.1、微服务远程调用

2.1.1.微服务调用方式

2.2、分布式服务案例

2.2.1.服务调用关系

3、Eureka注册中心

3.1、搭建EurekaServer

3.2、服务注册

3.3、服务发现

4、Ribbon负载均衡

4.1、Ribbon负载均衡规则

4.2、负载均衡自定义方式

4.3、饥饿加载

5、Nacos注册中心

5.1、Nacos安装及入门

5.1.1、Nacos服务搭建

5.1.2、Nacos服务注册或发现

5.2、Nacos服务分级模型

5.2.1、Nacos服务分级存储模型

5.2.2、如何设置实例的集群属性

5.3、Nacos注册中心-NacosRule负载均衡策略

5.4、Nacos注册中心-加权负载均衡

5.5、Nacos环境隔离

5.6、Nacos与eureka对比

5.6.1、Nacos与eureka的共同点

5.6.2、Nacos与Eureka的区别

6、Nacos配置管理

7、http客户端Feign

7.1、基于feign远程调用

7.2、Feign的日志配置

7.3、Feign的性能优化

7.4、Feign的最佳实践

8、Gateway网关

8.1、网关的作用

8.2、统一网关Gateway-搭建网关服务

8.2.1、网关搭建步骤

8.2.2、路由配置包括

8.3、统一网关Gateway-路由断言工厂

8.4、统一网关Gateway-过滤器工厂

8.5、统一网关Gateway-全局过滤器

8.6、统一网关Gateway-过滤器执行顺序

9、Docker

9.1、初识Docker

9.2、Docker和虚拟机的差别

9.3、Docker架构

9.4、镜像命令

9.5、容器命令

9.6、数据卷命令

9.7、镜像结构

9.8、自定义镜像

9.9、自定义镜像仓库

 10、MQ

10.1、初始MQ-同步通讯的优缺点

10.2、初始MQ-异步通讯的优缺点

10.3、RabbitMQ快速入门

10.4、SpringAMQP

10.4.1、SpringAMQP-FanoutExchange

10.4.2、SpringAMQP-DirectExhange、TopicExchange

10.4.3、SpringAMQP-消息转换器

 11、ElasticSearch

11.1、初识elasticsearch

11.1.1、初识elasticsearch-正向索引和倒排索引

11.1.2、初识elasticsearch-分词器

11.2、操作索引库

11.2.1、mapping属性

11.2.2、创建索引库

11.2.3、RestClient操作索引库

11.2.4、RestClient操作文档

11.3、DSL查询语法

11.3.1、DSL查询语法-match查询

11.3.2、DSL查询语法-全文检索查询

11.3.3、DSL查询语法-精确查询

11.3.4、DSL查询语法-地理查询

11.3.5、DSL查询语法-相关性算分

11.3.6、DSL查询语法-Function Score Query

11.3.7、DSL查询语句-Boolean Query

11.4、搜索结果处理

11.4.1、搜索结果处理-排序

11.4.2、搜索结果处理-分页

11.4.3、搜索结果处理-高亮

11.4.4、搜索结果处理整体语法

11.5、RestClient查询文档

11.5.1、快速入门

11.5.2、结果处理

11.5.3、案例

11.6、数据聚合

11.6.1、数据聚合-聚合的分类

11.6.2、数据聚合-DSL实现Buckey聚合(桶聚合)

11.6.3、数据聚合-DSL实现Metrics聚合(嵌套聚合)

11.7、自动补全

11.7.1、自动补全-自定义分词器

11.7.2、自动补全-DSL实现自动补全查询

11.8、数据同步

11.8.1、数据同步-同步方案分析

11.9、ES集群

11.9.1、ES集群-集群职责及脑裂

11.9.2、ES集群-分布式新增和查询流程

11.9.3、ES集群-故障转移


 1、微服务框架

1.1、认识微服务

单体架构特点?

  • 简单方便,高度耦合,扩展性差,适合小型项目。例如:学生管理系统

分布式架构特点?

  • 松耦合,扩展性好,但架构复杂,难度大。适合大型互联网项目,例如:京东、淘宝。
  • 微服务:一种良好的分布式架构方案
  • 优点:拆分粒度更小、服务更独立、耦合度更低
  • 缺点:架构非常复杂,运维、监控、部署难度更高

2、服务拆分及远程调用

1.微服务需要根据业务模块拆分,做到单一职责,不要重复开发相同业务

2.微服务可以将业务暴露为接口,供其它微服务使用

3.不同微服务都应该有自己独立的数据库

2.1、微服务远程调用

2.1.1.微服务调用方式

  • 基于RestTemplate发起的http请求实现远程调用。
  • http请求做远程调用是与语言无关的调用,只要知道对方的ip、端口、接口路径、请求参数即可。

2.2、分布式服务案例

2.2.1.服务调用关系

  • 服务提供者:暴露接口给其它微服务调用
  • 服务消费者:调用其它微服务提供的接口
  • 提供者与消费者角色其实是相对的
  • 一个服务可以同时是服务提供者和服务消费者

3、Eureka注册中心

在Eureka架构中,微服务角色有两类:

1.EurekaServer:服务端,注册中心

  • 记录服务信息
  • 心跳监控

2.EurekaClient:客户端

Provider:服务提供者,例如案例中的user-service

  • 注册自己的信息到EurekaServer
  • 每隔30秒向EurekaServer发送心跳

Consumer:服务消费者,例如案例中的order-service

  • 根据服务名称从EurekaServer拉去服务列表
  • 基于服务列表做负载均衡,选中一个微服务后发起远程调用

3.1、搭建EurekaServer

  • 引入eureka-server依赖
  • 添加@EnableEurekaServer注解
  • 在application.yml中配置eureka地址

3.2、服务注册

  • 引入eureka-client依赖
  • 在application.yml中配置eureka地址

3.3、服务发现

  • 引入eureka-client依赖
  • 在application.yml中配置eureka地址
  • 给RestTemplate添加@LoadBalaned注解
  • 用服务提供者的服务名称远程调用

4、Ribbon负载均衡

4.1、Ribbon负载均衡规则

  • 规则接口是IRule
  • 默认实现是ZoneAvoidanceRule,根据zone选中服务列表,然后轮询

4.2、负载均衡自定义方式

  • 代码方式:配置灵活,但修改时需要重新打包发布
  • 配置方式:直观,方便,无需重新打包发布,但是无法做全局配置

4.3、饥饿加载

  • 开启饥饿加载
  • 指定饥饿加载的微服务名称

5、Nacos注册中心

5.1、Nacos安装及入门

5.1.1、Nacos服务搭建

  • ①下载安装包
  • ②解压
  • ③在bin目录下运行指令:startup.cmd -m standalone

5.1.2、Nacos服务注册或发现

  • ①引入nacos.discovery依赖
  • ②配置nacos地址spring.cloud.nacos.server-addr

5.2、Nacos服务分级模型

5.2.1、Nacos服务分级存储模型

  • ①一级是服务,例如userservice
  • ②二级是集群。例如杭州或者上海
  • ③三级是实例,例如杭州机房的某台部署了userservice的服务器

5.2.2、如何设置实例的集群属性

  • ①在application.yml文件,添加spring.cloud.nacos.discovery.cluster-name属性即可

5.3、Nacos注册中心-NacosRule负载均衡策略

  • ①优先选择同集群服务实例列表
  • ②本地集群找不到提供者,才去其它集群寻找,并且会报警告
  • ③确定了可用实例列表后,再采用随机负载均衡挑选实例

5.4、Nacos注册中心-加权负载均衡

  • ①Nacos控制台可以设置实例的权重值,0~1之间
  • ②同集群内的多个实例,权重越高被访问的频率越高
  • ③权重设置为0则完全不会被访问

5.5、Nacos环境隔离

  • ①每个namespace都有唯一的id
  • ②服务设置namespace时要写id而不是服务名称
  • ③不同namespace下的服务互相不可见

5.6、Nacos与eureka对比

5.6.1、Nacos与eureka的共同点

  • ①都支持服务注册和服务拉取
  • ②都支持服务提供者心跳方式做健康检测

5.6.2、Nacos与Eureka的区别

  • ①Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
  • ②临时实例心跳不正常会被剔除,非临时实例则不会被剔除
  • ③Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
  • ④Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

6、Nacos配置管理

将配置交给Nacos管理步骤

  • ①在Nacos中添加配置文件
  • ②在微服务中引入nacos的config依赖
  • ③在微服务中添加bootstrap.yml,配置nacos地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时去nacos读取哪个文件

Nacos配置更改后,微服务可以实现热更新,方式:

  • ①通过@Value注解注入,结合@RefreshScope来刷新
  • ②通过@ConfigurationProperties注入,自动刷新

注意事项:

  • 不是所有的配置都适合放到配置中心,维护起来比较麻烦
  • 建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置

微服务会从nacos读取的配置文件:

  • ①[服务名]-[spring.profile.active].yaml,配置环境
  • ②[服务名].yaml,默认配置,多环境共享

优先级:

  • ①[服务名]-[环境].yaml > [服务名].yaml > 本地配置

微服务默认读取的配置文件:

  • ①[服务名]-[spring.profile.active].yaml,默认配置
  • ②[服务名].yaml,多环境共享

不同微服务共享的配置文件:

  • ①通过shared-config指定
  • ②通过extension-configs指定

优先级:

  • ①环境配置 > 服务名.yaml > extension-config > extension-configs > shared-configs > 本地配置

Nacos集群搭建步骤:

  • ①搭建MySQL集群并初始化数据库表
  • ②下载解压nacos
  • ③修改集群配置(节点信息)、数据库配置
  • ④分别启动多个nacos节点
  • ⑤nginx反向代理

7、http客户端Feign

7.1、基于feign远程调用

  • ① 引入依赖
  • ② 添加@EnableFeignClient注解
  • ③ 编写FeignClient接口
  • ④ 使用FeignClient中定义的方法替代RestTemplate

7.2、Feign的日志配置

1、方式一是配置文件,feign.client.config.xxx.loggerLever

  • ① 如果xxx是default则代表全局
  • ② 如果xxx是服务名称,例如userservice则代表某服务

2、方式二是java代码配置Logger.Lever这个Bean

  • ① 如果在@EnableFeignClients注解声明则代表全局
  • ② 如果在@FeignClient注解中声明则代表某服务

7.3、Feign的性能优化

1、日志级别尽量用Basic

2、使用HttpClient或OKHttp替代URLConnection

  • ① 引入feign-httpClient依赖
  • ② 配置文件开启httpClient功能,设置连接池参数

7.4、Feign的最佳实践

方式一(继承):给消费者的FeignClient和提供者的controller定义同一的父接口为标准

弊端:

  • ① 服务紧耦合
  • ② 父接口参数列表中的映射不会被继承

方式二(抽取):将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用

实现最佳实践方式二的步骤如下:

  • 1. 首先创建一个moudle,命名为feign-api,然后引入feign的starter依赖
  • 2. 将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
  • 3. 在order-service中引入feign-api的依赖
  • 4. 修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包
  • 5. 重启测试

重启之后出现的问题,因为新建的模块在不同包下,所以在order-service项目启动时,无法引入

不同包的FeignClient的导入有两种方式:

  • ① 在@EnableFeignClient注解中添加basePackages,指定FeignClient所在的包
  • ② 在@EnableFeignClient注解中添加clients,指定具体FeignClient的字节码

8、Gateway网关

8.1、网关的作用

  • 1、对用户请求做身份认证、权限校验
  • 2、将用户请求路由到微服务,并实现负载均衡
  • 3、对用户请求做限流

8.2、统一网关Gateway-搭建网关服务

8.2.1、网关搭建步骤

  • 1、创建项目,引入nacos服务发现和gateway依赖
  • 2、配置application.yml,包括服务基本信息、nacos地址、路由

8.2.2、路由配置包括

  • 1、路由id:路由的唯一标识
  • 2、路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
  • 3、路由断言(predicates):判断路由的规则
  • 4、路由过滤器(filters):对请求或响应做处理

8.3、统一网关Gateway-路由断言工厂

PredicateFactory的作用是什么?

  • 读取用户的断言条件,对请求做出判断

Path = /user/**是什么含义?

  • 路径以/user开头的就认为是符合的

8.4、统一网关Gateway-过滤器工厂

过滤器的作用是什么?

  • ① 对路由的请求或响应做加工处理,比如添加请求头
  • ② 配置在路由下的过滤器只对当前路由的请求生效

defaultFilters的作用是什么?

  • ① 对所有都生效的过滤器

8.5、统一网关Gateway-全局过滤器

全局过滤器的作用是什么?

  • 对所有路由都生效的过滤器,并且可以自定义处理逻辑

实现全局过滤器的步骤?

  • ① 实现GlobalFilter接口
  • ② 添加@Order注解或实现Ordered接口
  • ③ 编写处理逻辑

8.6、统一网关Gateway-过滤器执行顺序

路由过滤器、defaultFilter、全局过滤器的执行顺序?

  • ① order值越小,优先级越高 
  • ② 当order值一样时,顺序是defaultFilter最先,然后是局部的路由过滤器,最后是全局过滤器

9、Docker

9.1、初识Docker

Docker是一个快速交付应用、运行应用的技术:

  • 1、可以将程序及其依赖、运行环境一起打包为一个镜像,可以迁移到任意Linux操作系统
  • 2、运行时利用沙箱机制形成隔离容器,各个应用互补干扰
  • 3、启动、移除都可以通过一行命令完成,方便快捷

9.2、Docker和虚拟机的差别

  • 1、docker是一个系统进程;虚拟机是在操作系统中的操作系统
  • 2、docker体积小、启动速度快、性能好;虚拟机体积大、启动速度慢、性能一般

9.3、Docker架构

镜像:

  • 将应用程序及其依赖、环境、配置打包在一起

容器:

  • 镜像运行起来就是容器,一个镜像可以运行多个容器

Docker结构:

  • 服务端:接收命令或远程请求,操作镜像或容器
  • 客户端:发送命令或者请求到Docker服务端

DockerHub:

  •  一个镜像托管的服务器,类似的还有阿里云镜像服务,统称为DockerRegistry

9.4、镜像命令

  • docker images 查看镜像
  • docker rmi 删除镜像
  • docker pull 拉取镜像
  • docker push
  • docker save 将镜像保存为tar文件
  • docker load 将tar文件保存为镜像

9.5、容器命令

docker run 命令的常见参数有哪些?

  • --name : 指定容器名称
  • -p : 指定端口映射
  • -d : 让容器后台运行

查看容器日志的命令:

  • docker logs
  • 添加 -f 参数可以持续查看日志

查看容器状态

  • docker ps

删除容器

  • docker rm
  • 不能删除运行中的容器,除非添加 -f 参数

进入容器

  • 命令是docker exec -it [容器名] [要执行的命令]
  • exec命令可以进入容器修改文件,但是在容器内修改文件是不推荐的

9.6、数据卷命令

数据卷的作用:

  • 将容器与数据分离,解耦合,方便操作容器内数据,保证数据安全

数据卷操作:

  • docker volume create 创建数据卷
  • docker volume ls 查看数据卷
  • docker volume inspect 查看某个数据卷的详细信息
  • docker volume rm 删除一个或多个数据卷
  • docker volume prune 删除未使用过的数据卷

数据卷挂载方式:

  • -v valomeName:/targetContainerPath
  • 如果容器运行时volume不存在,会自动创建出来

 数据卷管理:

1、docker run 的命令中通过 -v 参数挂载文件或目录到容器中

  • ① -v volume名称 : 容器内目录
  • ② -v 宿主机文件 : 容器内文件
  • ③ -v 宿主机目录 : 容器内目录

2、数据卷挂载与目录直接挂载的

  • ① 数据卷挂载耦合度低,由docker来管理目录,但是目录较深,不好找
  • ② 目录挂载耦合度高,需要我们自己管理目录,不过目录容易寻找查看

9.7、镜像结构

镜像是分层结构,每一层称为一个Layer

  • BaseImage层:包含基本的系统函数库、环境变量、文件系统
  • Entrypoint:入口,是镜像中应用启动的命令
  • 其它:在BaseImage基础上添加依赖、安装程序、完成整个应用的安装和配置

9.8、自定义镜像

  • 1、Dockerfile的本质是一个文件,通过指令描述镜像的构建过程
  • 2、Dockerfile第一行必须是FROM,从一个基础镜像来构建
  • 3、基础镜像可以是基本操作系统,如Ububtu。也可以是其它制作好的镜像,例如:java:8-apline

 DockerCompose有什么作用?

  • 帮助我们快速部署分布式应用,无需一个个微服务去构建镜像和部署。

9.9、自定义镜像仓库

  • 1、推送本地镜像到仓库前都必须重命名(docker tag)镜像,以镜像仓库地址为前缀

  • 2、镜像仓库推送前需要把仓库地址配置到docker服务的daemon.json文件中,被docker信任

  • 3、推送使用 docker push 命令

  • 4、拉取使用 docker pull 命令

 10、MQ

10.1、初始MQ-同步通讯的优缺点

同步调用的优点:

  • 时效性较强,可以立即得到结果

同步调用的问题:

  • 耦合度高
  • 性能和吞吐能力下降
  • 有额外的资源消耗
  • 有级联失效问题

10.2、初始MQ-异步通讯的优缺点

异步通讯的优点:

  • 耦合度低
  • 吞吐量提升
  • 故障隔离
  • 流量削峰

异步通讯的缺点:

  • 依赖Broker的可靠性、安全性、吞吐能力
  • 架构复杂了,业务没有明显的流程线,不好追踪管理

10.3、RabbitMQ快速入门

RabbitMQ的几个概念:

  • channel:操作MQ的工具
  • exchange:路由消息到队列中
  • queue:缓存消息
  • virtual host:虚拟主机,是对queue、exchange等资源逻辑分组

基本消息队列的消息发送流程:

  • 1、建立connection
  • 2、创建channel
  • 3、利用channel声明队列
  • 4、利用channel向队列发送消息
  1. package cn.itcast.mq.helloworld;
  2. import com.rabbitmq.client.Channel;
  3. import com.rabbitmq.client.Connection;
  4. import com.rabbitmq.client.ConnectionFactory;
  5. import org.junit.Test;
  6. import java.io.IOException;
  7. import java.util.concurrent.TimeoutException;
  8. public class PublisherTest {
  9. @Test
  10. public void testSendMessage() throws IOException, TimeoutException {
  11. // 1.建立连接
  12. ConnectionFactory factory = new ConnectionFactory();
  13. // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
  14. factory.setHost("192.168.16.3");
  15. factory.setPort(5672);
  16. factory.setVirtualHost("/");
  17. factory.setUsername("liufang");
  18. factory.setPassword("123456");
  19. // 1.2.建立连接
  20. Connection connection = factory.newConnection();
  21. // 2.创建通道Channel
  22. Channel channel = connection.createChannel();
  23. // 3.创建队列
  24. String queueName = "simple.queue";
  25. channel.queueDeclare(queueName, false, false, false, null);
  26. // 4.发送消息
  27. String message = "hello, rabbitmq!";
  28. channel.basicPublish("", queueName, null, message.getBytes());
  29. System.out.println("发送消息成功:【" + message + "】");
  30. // 5.关闭通道和连接
  31. channel.close();
  32. connection.close();
  33. }
  34. }

基本消息队列的消息接收流程:

  • 1、建立connection
  • 2、创建channel
  • 3、利用channel声明队列
  • 4、定义consumer的消费行为handleDelivery()
  • 5、利用channel将消费者与队列绑定
  1. package cn.itcast.mq.helloworld;
  2. import com.rabbitmq.client.*;
  3. import java.io.IOException;
  4. import java.util.concurrent.TimeoutException;
  5. public class ConsumerTest {
  6. public static void main(String[] args) throws IOException, TimeoutException {
  7. // 1.建立连接
  8. ConnectionFactory factory = new ConnectionFactory();
  9. // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
  10. factory.setHost("192.168.16.3");
  11. factory.setPort(5672);
  12. factory.setVirtualHost("/");
  13. factory.setUsername("liufang");
  14. factory.setPassword("123456");
  15. // 1.2.建立连接
  16. Connection connection = factory.newConnection();
  17. // 2.创建通道Channel
  18. Channel channel = connection.createChannel();
  19. // 3.创建队列
  20. String queueName = "simple.queue";
  21. channel.queueDeclare(queueName, false, false, false, null);
  22. // 4.订阅消息
  23. channel.basicConsume(queueName, true, new DefaultConsumer(channel){
  24. @Override
  25. public void handleDelivery(String consumerTag, Envelope envelope,
  26. AMQP.BasicProperties properties, byte[] body) throws IOException {
  27. // 5.处理消息
  28. String message = new String(body);
  29. System.out.println("接收到消息:【" + message + "】");
  30. }
  31. });
  32. System.out.println("等待接收消息。。。。");
  33. }
  34. }

10.4、SpringAMQP

什么是AMQP?

  • 应用间消息通信的一种协议,与语言和平台无关。

SpringAMQP如何发送消息?

  • 引入amqp的starter依赖
  • 配置RabbitMQ地址
  • 利用RabbitTemplate的convertAndSend方法

SpringAMQP如何接收消息?

  • 引入amqp的starter依赖
  • 配置RabbitMQ地址
  • 定义类,添加@Component注解
  • 类中声明方法,添加@RabbitListener注解,方法参数接收消息

注意:消息一旦消费就会从队列删除,RabbitMQ没有消息回溯功能

Work模型的使用:

  • 多个消费者绑定到一个队列,同一条消息只会被一个消费者处理
  • 通过设置prefetch来控制消费者预取的消息数量

10.4.1、SpringAMQP-FanoutExchange

交换机的作用是什么?

  • 接收publisher发送的消息
  • 将消息按照规则路由到与之绑定的队列
  • 不能缓存消息,路由失败,消息丢失
  • FanoutExchange会将消息路由到每个绑定的队列中

声明队列、交换机、绑定关系的Bean是什么?

  • Queue
  • FanoutExchange
  • Binding

10.4.2、SpringAMQP-DirectExhange、TopicExchange

描述下Direct交换机与Fanout交换机的差别?

  • Fanout交换机将消息路由给每一个与之绑定的队列
  • Direct交换机根据RoutingKey判断路由给哪个队列
  • 如果多个队列具有相同的RoutingKey,则与Fanout功能类似

基于@RabbitListener注解声明队列和交换机有哪些常见注解?

  • @Queue
  • @Exchange

描述下Topic交换机与Direct交换机的差异?

  • 交换机类型不同,一个是TOPIC,一个是DIRECT
  • TopicExchange交换机的RoutingKey使用的是通配符,而DirectExchange交换机的RoutingKey使用的是完整的key名称

10.4.3、SpringAMQP-消息转换器

SpringAMQP中消息的序列化和反序列化是怎么实现的?

  • 利用MessageConverter实现的,默认是JDK的序列化
  • 注意发送方与接收方必须使用相同的MessageConverter

RabbitMQ只支持字节,Spring却允许我们发Object对象,说明它会将我们的对象做序列化,用的是java序列化也就是JDK序列化(ObjetOutputStream)

 11、ElasticSearch

11.1、初识elasticsearch

什么是elasticsearch?

  • 一个开源的分布式搜索引擎,可以用来实现搜索、日志统计、分析、系统监控等功能

什么是elastic stack(ELK)?

  • 是以elasticsearch为核心的技术栈,包括beats、Logstash、kibana、elasticsearch

什么是Lucene?

  • 是Apache的开源搜索引擎类库,提供了搜索引擎的核心API

11.1.1、初识elasticsearch-正向索引和倒排索引

什么是文档和词条?

  • 每一条数据就是一个文档
  • 对文档中的内容分词,得到的词语就是词条

什么是正向索引?

  • 基于文档id创建索引,查询词条时必须先找到文档,而后判断是否包含词条

什么是倒排索引?

  • 对文档内容分词,对词条创建索引,并记录词条所在文档的信息。查询时先根据词条查询到文档id,而后获得到文档

11.1.2、初识elasticsearch-分词器

分词器的作用是什么?

  • 创建倒排索引时对文档分词
  • 用户搜索时,对输入的内容分词

IK分词器有几种模式?

  • ik_smart:智能切分,粗粒度
  • ik_max_word:最细切分,细粒度

IK分词器如何拓展词条?如何停用词条?

  • 利用config目录的IKAnalyzer.cfg.xml文件添加拓展词典和停用词典
  • 在词典中添加拓展词条或者停用词条

11.2、操作索引库

11.2.1、mapping属性

mapping常见属性有哪些?

  • type:数据类型
  • index:是否索引
  • analyzer:分词器
  • properties:子字段

type常见的有哪些?

  • 字符串:text、keyword
  • 数字:long、integer、short、byte、double、float
  • 布尔:boolean
  • 日期:date
  • 对象:object

11.2.2、创建索引库

索引库操作有哪些?

  • 创建索引库:PUT /索引库名
  • 查询索引库:GET /索引库名
  • 删除索引库:DELETE /索引库名
  • 添加字段:DELETE /索引库名/_mapping

文档操作有哪些?

  • 创建文档:POST /索引库名/_doc/文档id {json文档}
  • 查询文档:GET /索引库名/_doc/文档id
  • 删除文档:DELETE /索引库名/_doc/文档id
  • 修改文档:
  •        全量修改:PUT /索引库名/_doc/文档id {json文档}
  •        增量修改:POST /索引库名/_update/文档id {"doc": {字段}}
  1. #创建索引库
  2. PUT /heima
  3. {
  4. "mappings": {
  5. "properties": {
  6. "info": {
  7. "type": "text",
  8. "analyzer": "ik_smart"
  9. },
  10. "email": {
  11. "type": "keyword",
  12. "index": false
  13. },
  14. "name": {
  15. "properties": {
  16. "firstName": {
  17. "type": "keyword"
  18. },
  19. "lastName": {
  20. "type": "keyword"
  21. }
  22. }
  23. }
  24. }
  25. }
  26. }
  27. #查询
  28. GET /heima
  29. #修改,添加字段
  30. PUT /heima/_mapping
  31. {
  32. "properties": {
  33. "age": {
  34. "type": "integer"
  35. }
  36. }
  37. }
  38. #删除
  39. DELETE /heima
  40. #插入文档
  41. POST /heima/_doc/1
  42. {
  43. "info": "程序员",
  44. "email": "2531994628@qq.com",
  45. "name": {
  46. "firstName": "云",
  47. "lastName": "赵"
  48. }
  49. }
  50. #查询文档
  51. GET /heima/_doc/1
  52. #删除文档
  53. DELETE /heima/_doc/1
  54. #全量修改文档
  55. PUT /heima/_doc/1
  56. {
  57. "info": "程序员",
  58. "email": "ZhaoYun@qq.com",
  59. "name": {
  60. "firstName": "云",
  61. "lastName": "赵"
  62. }
  63. }
  64. #局部修改文档字段
  65. POST /heima/_update/1
  66. {
  67. "doc": {
  68. "email": "253@qq.com"
  69. }
  70. }

11.2.3、RestClient操作索引库

索引库操作的基本步骤

  • 初始化RestHighLevelClient
  • 创建xxxIndexRequest。XXX是CREATE、Get、Delete
  • 准备DSL(CREATE时需要)
  • 发起请求。调用RestHighLevelClient#indices().xxx方法
  •        xxx是create、exists、delete
  1. package cn.itcast.hotel.constants;
  2. public class HotelConstants {
  3. public final static String MAPPING_TEMPLATE = "{\n" +
  4. " \"mappings\": {\n" +
  5. " \"properties\": {\n" +
  6. " \"id\": {\n" +
  7. " \"type\": \"keyword\"\n" +
  8. " },\n" +
  9. " \"name\": {\n" +
  10. " \"type\": \"text\",\n" +
  11. " \"analyzer\": \"ik_max_word\",\n" +
  12. " \"copy_to\": \"all\"\n" +
  13. " },\n" +
  14. " \"address\": {\n" +
  15. " \"type\": \"keyword\",\n" +
  16. " \"index\": false\n" +
  17. " },\n" +
  18. " \"price\": {\n" +
  19. " \"type\": \"integer\"\n" +
  20. " },\n" +
  21. " \"score\": {\n" +
  22. " \"type\": \"integer\"\n" +
  23. " },\n" +
  24. " \"brand\": {\n" +
  25. " \"type\": \"keyword\"\n" +
  26. " },\n" +
  27. " \"city\": {\n" +
  28. " \"type\": \"keyword\"\n" +
  29. " },\n" +
  30. " \"standName\": {\n" +
  31. " \"type\": \"keyword\"\n" +
  32. " },\n" +
  33. " \"business\": {\n" +
  34. " \"type\": \"keyword\",\n" +
  35. " \"copy_to\": \"all\"\n" +
  36. " },\n" +
  37. " \"location\": {\n" +
  38. " \"type\": \"geo_point\"\n" +
  39. " },\n" +
  40. " \"pic\": {\n" +
  41. " \"type\": \"keyword\",\n" +
  42. " \"index\": false\n" +
  43. " },\n" +
  44. " \"all\": {\n" +
  45. " \"type\": \"text\",\n" +
  46. " \"analyzer\": \"ik_max_word\"\n" +
  47. " }\n" +
  48. " }\n" +
  49. " }\n" +
  50. "}";
  51. }
  1. package cn.itcast.hotel;
  2. import cn.itcast.hotel.constants.HotelConstants;
  3. import org.apache.http.HttpHost;
  4. import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
  5. import org.elasticsearch.client.RequestOptions;
  6. import org.elasticsearch.client.RestClient;
  7. import org.elasticsearch.client.RestHighLevelClient;
  8. import org.elasticsearch.client.indices.CreateIndexRequest;
  9. import org.elasticsearch.client.indices.GetIndexRequest;
  10. import org.elasticsearch.common.xcontent.XContentType;
  11. import org.junit.jupiter.api.AfterEach;
  12. import org.junit.jupiter.api.BeforeEach;
  13. import org.junit.jupiter.api.Test;
  14. import org.springframework.boot.test.context.SpringBootTest;
  15. import java.io.IOException;
  16. @SpringBootTest
  17. public class HotelIndexTest {
  18. private RestHighLevelClient client;
  19. /**
  20. * 创建索引库
  21. * @throws IOException
  22. */
  23. @Test
  24. void testCreatHotelIndex() throws IOException {
  25. //1、创建Request对象
  26. CreateIndexRequest request = new CreateIndexRequest("hotel");
  27. //2、请求参数,MAPPING_TEMPLATE是静态常量字符串,内容是创建索引库的DSL语句
  28. request.source(HotelConstants.MAPPING_TEMPLATE, XContentType.JSON);
  29. //3、发起请求
  30. client.indices().create(request, RequestOptions.DEFAULT);
  31. }
  32. /**
  33. * 删除索引库
  34. * @throws IOException
  35. */
  36. @Test
  37. void testDeleteHotelIndex() throws IOException{
  38. //1、创建Request对象
  39. DeleteIndexRequest request = new DeleteIndexRequest("hotel");
  40. //2、发起请求,删除索引库
  41. client.indices().delete(request,RequestOptions.DEFAULT);
  42. }
  43. /**
  44. * 判断索引库是否存在
  45. * @throws IOException
  46. */
  47. @Test
  48. void testExistHotelIndex() throws IOException{
  49. //1、创建Request对象
  50. GetIndexRequest request = new GetIndexRequest("hotel");
  51. //2、发起请求,判断是否存在
  52. boolean exists = client.indices().exists(request,RequestOptions.DEFAULT);
  53. //3、输出
  54. System.out.println(exists ? "索引库已经存在!" : "索引库不存在!");
  55. }
  56. @BeforeEach
  57. void setUp(){
  58. this.client = new RestHighLevelClient(RestClient.builder(
  59. HttpHost.create("http://192.168.16.3:9200")
  60. ));
  61. }
  62. @AfterEach
  63. void tearDown(){
  64. try {
  65. client.close();
  66. } catch (IOException e) {
  67. e.printStackTrace();
  68. }
  69. }
  70. }

11.2.4、RestClient操作文档

文档操作的基本步骤:

  • 初始化RestHighLevelClient
  • 创建xxxRequest。xxx是Index、Get、Update、Delete
  • 准备参数(Index和Update时需要)
  • 发送请求。调用RestHighLevelClient#.xxx()方法,xxx时index、get、update、delete
  • 解析结果(Get时需要)
  1. package cn.itcast.hotel;
  2. import cn.itcast.hotel.pojo.Hotel;
  3. import cn.itcast.hotel.pojo.HotelDoc;
  4. import cn.itcast.hotel.service.IHotelService;
  5. import com.alibaba.fastjson.JSON;
  6. import org.apache.http.HttpHost;
  7. import org.elasticsearch.action.delete.DeleteRequest;
  8. import org.elasticsearch.action.get.GetRequest;
  9. import org.elasticsearch.action.get.GetResponse;
  10. import org.elasticsearch.action.index.IndexRequest;
  11. import org.elasticsearch.action.update.UpdateRequest;
  12. import org.elasticsearch.client.RequestOptions;
  13. import org.elasticsearch.client.RestClient;
  14. import org.elasticsearch.client.RestHighLevelClient;
  15. import org.elasticsearch.common.xcontent.XContentType;
  16. import org.junit.jupiter.api.AfterEach;
  17. import org.junit.jupiter.api.BeforeEach;
  18. import org.junit.jupiter.api.Test;
  19. import org.springframework.beans.factory.annotation.Autowired;
  20. import org.springframework.boot.test.context.SpringBootTest;
  21. import java.io.IOException;
  22. @SpringBootTest
  23. public class HotelDocumentTest {
  24. @Autowired
  25. private IHotelService hotelService;
  26. @Autowired
  27. private RestHighLevelClient client;
  28. /**
  29. * 新增文档
  30. * @throws IOException
  31. */
  32. @Test
  33. void testAddDocument() throws IOException {
  34. //根据id查询酒店数据
  35. Hotel hotel = hotelService.getById(61083L);
  36. //转换为文档类型
  37. HotelDoc hotelDoc = new HotelDoc(hotel);
  38. //1、准备Request对象
  39. IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
  40. //2、准备JSON文档
  41. request.source(hotelDoc, XContentType.JSON);
  42. //3、发送请求
  43. client.index(request, RequestOptions.DEFAULT);
  44. }
  45. /**
  46. * 查询文档
  47. * @throws IOException
  48. */
  49. @Test
  50. void GetDocumentById() throws IOException{
  51. //1、准备request对象
  52. GetRequest request = new GetRequest("hotel","61083");
  53. //2、发送请求,得到响应
  54. GetResponse response = client.get(request,RequestOptions.DEFAULT);
  55. //3、解析响应结果
  56. String json = response.getSourceAsString();
  57. // HotelDoc hotelDoc = JSON.parseObject(json,HotelDoc.class);
  58. System.out.println(json);
  59. }
  60. /**
  61. * 更新文档
  62. * @throws IOException
  63. */
  64. @Test
  65. void testUpdateDocument() throws IOException {
  66. //1、准备Request对象
  67. UpdateRequest request = new UpdateRequest("hotel","61083");
  68. //2、准备请求参数
  69. request.doc(
  70. "price","952",
  71. "starName","四钻"
  72. );
  73. //3、发送请求
  74. client.update(request,RequestOptions.DEFAULT);
  75. }
  76. /**
  77. * 删除文档
  78. * @throws IOException
  79. */
  80. @Test
  81. void testDeleteDocument() throws IOException{
  82. //1、准备Request对象
  83. DeleteRequest request = new DeleteRequest("hotel","61083");
  84. //2、发送请求
  85. client.delete(request,RequestOptions.DEFAULT);
  86. }
  87. @BeforeEach
  88. void setUp(){
  89. this.client = new RestHighLevelClient(RestClient.builder(
  90. HttpHost.create("http://192.168.16.3:9200")
  91. ));
  92. }
  93. @AfterEach
  94. void tearDown() throws IOException{
  95. this.client.close();
  96. }
  97. }

将数据库中的数据批量导入到索引库中

  1. /**
  2. * 批量导入文档
  3. * @throws IOException
  4. */
  5. @Test
  6. void testBulkDocument() throws IOException{
  7. //批量查询酒店数据
  8. List<Hotel> hotels = hotelService.list();
  9. //1、创建request对象
  10. BulkRequest request = new BulkRequest();
  11. //2、准备参数,添加多个新增的Request
  12. for (Hotel hotel : hotels) {
  13. //转换文档类型HotelDoc
  14. HotelDoc hotelDoc = new HotelDoc(hotel);
  15. //创建新增文档的Request对象
  16. request.add(new IndexRequest("hotel")
  17. .id(hotelDoc.getId().toString())
  18. .source(JSON.toJSONString(hotelDoc),XContentType.JSON));
  19. }
  20. //3、发送请求
  21. client.bulk(request,RequestOptions.DEFAULT);
  22. }

11.3、DSL查询语法

11.3.1、DSL查询语法-match查询

查询DSL的基本语法是什么?

  • GET /索引库名/_search
  • { "query": { "查询类型": { "FIELD: TEXT" } } }
  1. ###查询所有
  2. GET /hotel/_search
  3. {
  4. "query": {
  5. "match_all": {}
  6. }
  7. }

11.3.2、DSL查询语法-全文检索查询

match和multi_match的区别是什么?

  • match:根据一个字段查询
  • multi_match:根据多个字段查询,参与查询字段越多,查询性能越差
  1. ###match查询
  2. GET /hotel/_search
  3. {
  4. "query": {
  5. "match": {
  6. "all": "如家外滩"
  7. }
  8. }
  9. }
  10. ###multi_match查询
  11. GET /hotel/_search
  12. {
  13. "query": {
  14. "multi_match": {
  15. "query": "如家外滩",
  16. "fields": ["brand","name","business"]
  17. }
  18. }
  19. }

11.3.3、DSL查询语法-精确查询

精确查询常见的有哪些?

  • term查询:根据词条精确匹配,一般搜索keyword类型、数值类型、布尔类型、日期类型字段
  • range查询:根据数值范围查询,可以是数值、日期范围
  1. ### term查询
  2. GET /hotel/_search
  3. {
  4. "query": {
  5. "term": {
  6. "city": {
  7. "value": "广东"
  8. }
  9. }
  10. }
  11. }
  12. ### range查询
  13. GET /hotel/_search
  14. {
  15. "query": {
  16. "range": {
  17. "price": {
  18. "gte": 1000,
  19. "lte": 2000
  20. }
  21. }
  22. }
  23. }

11.3.4、DSL查询语法-地理查询

  • 以点为坐标,distance为半径,画圆,查找在圆内的所有酒店数据
  1. ### geo_distance查询
  2. GET /hotel/_search
  3. {
  4. "query": {
  5. "geo_distance": {
  6. "distance": "2km",
  7. "location": "31.21,121.5"
  8. }
  9. }
  10. }

11.3.5、DSL查询语法-相关性算分

elasticsearch中的相关性打分算法是什么?

  • TF-IDF:在elasticsearch5.0之前,会随着词频增加而越来越大
  • BM25:在elasticsearch5.0之后,会随着词频增加而增大,但增长水平会趋于水平

11.3.6、DSL查询语法-Function Score Query

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

  • 过滤条件:哪些文档要加分
  • 算分函数:如何计算function score
  • 加权方式:function score 与 query score 如何运算
  1. ### function score query 打分算法查询
  2. GET /hotel/_search
  3. {
  4. "query": {
  5. "function_score": {
  6. "query": {
  7. "match": {
  8. "all": "外滩"
  9. }
  10. },
  11. "functions": [
  12. {
  13. "filter": {
  14. "term": {
  15. "brand": "如家"
  16. }
  17. },
  18. "weight": 10
  19. }
  20. ],
  21. "boost_mode": "multiply"
  22. }
  23. }
  24. }

11.3.7、DSL查询语句-Boolean Query

bool查询有几种逻辑关系?

  • must:必须匹配的条件,可以理解为“与”
  • should:选择性匹配的条件,可以理解为“或”
  • must_not:必须不匹配的条件,不参与打分
  • filter:必须匹配的条件,不参与打分

利用bool查询实现功能

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

  1. ### bool查询
  2. GET /hotel/_search
  3. {
  4. "query": {
  5. "bool": {
  6. "must": [
  7. {
  8. "match": {
  9. "name": "如家"
  10. }
  11. }
  12. ],
  13. "must_not": [
  14. {
  15. "range": {
  16. "price": {
  17. "gt": 400
  18. }
  19. }
  20. }
  21. ],
  22. "filter": [
  23. {
  24. "geo_distance": {
  25. "distance": "10km",
  26. "location": {
  27. "lat": 31.21,
  28. "lon": 121.5
  29. }
  30. }
  31. }
  32. ]
  33. }
  34. }
  35. }

11.4、搜索结果处理

11.4.1、搜索结果处理-排序

案例1:对酒店数据按照用户评价降序排序,评价相同的按照价格升序排序

  1. ### sort排序
  2. GET /hotel/_search
  3. {
  4. "query": {
  5. "match_all": {}
  6. },
  7. "sort": [
  8. {
  9. "score": "desc"
  10. },
  11. {
  12. "price": "asc"
  13. }
  14. ]
  15. }

案例2:实现对酒店数据按照到你的位置坐标的距离升序排序

  1. ### 找到121.531.21周围的酒店,距离升序排序
  2. GET /hotel/_search
  3. {
  4. "query": {
  5. "match_all": {}
  6. },
  7. "sort": [
  8. {
  9. "_geo_distance": {
  10. "location": {
  11. "lat": 31.21,
  12. "lon": 121.5
  13. },
  14. "order": "asc",
  15. "unit": "km"
  16. }
  17. }
  18. ]
  19. }

11.4.2、搜索结果处理-分页

from + size:

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

after search:

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

scroll:

  • 优点:没有查询上限(单次查询的size不超过10000)
  • 缺点:会有额外内存消耗,并且搜索结果是非实时的
  • 场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用after search方案
  1. ###分页查询
  2. GET /hotel/_search
  3. {
  4. "query": {
  5. "match_all": {}
  6. },
  7. "sort": [
  8. {
  9. "price": "asc"
  10. }
  11. ],
  12. "from": 20,
  13. "size": 10
  14. }

11.4.3、搜索结果处理-高亮

  1. ###高亮查询,默认情况下,ES搜索字段必须与高亮字段一致
  2. GET /hotel/_search
  3. {
  4. "query": {
  5. "match": {
  6. "all": "如家"
  7. }
  8. },
  9. "highlight": {
  10. "fields": {
  11. "name": {
  12. "require_field_match": "false"
  13. }
  14. }
  15. }
  16. }

11.4.4、搜索结果处理整体语法

  1. GET /hotel/_search
  2. {
  3. "query": {
  4. "match": {
  5. "name": "如家"
  6. }
  7. },
  8. "from": 20, //分页开始的位置
  9. "size": 10, //期望获取的文档总数
  10. "sort": [
  11. {
  12. "price": "desc" //普通排序
  13. },
  14. {
  15. "_geo_distance": { //距离排序
  16. "location": {
  17. "lat": 31.21,
  18. "lon": 121.5
  19. },
  20. "order": "asc",
  21. "unit": "km"
  22. }
  23. }
  24. ],
  25. "highlight": {
  26. "fields": { //高亮字段
  27. "name": {
  28. "pre_tags": "<em>", //用来标记高亮字段的前置标签
  29. "post_tags": "<em>", //用来标记高亮字段的后置标签
  30. "require_field_match": "false" //搜索字段与高亮是否需要匹配,默认是true
  31. }
  32. }
  33. }
  34. }

11.5、RestClient查询文档

11.5.1、快速入门

查询的基本步骤:

  • 1、创建SearchRequest对象
  • 2、准备Request.source(),也就是DSL

                ①QueryBuilders来构建查询条件

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

  • 3、发送请求,得到结果
  • 4、解析结果(参考JSON结果,从外到内,逐层解析)

11.5.2、结果处理

  • 所有搜索DSL的构建,记住一个API:SearchRequest的source()方法
  • 高亮结果解析是参考JSON结果,逐层解析
  1. package cn.itcast.hotel;
  2. import cn.itcast.hotel.pojo.HotelDoc;
  3. import com.alibaba.fastjson.JSON;
  4. import org.apache.http.HttpHost;
  5. import org.elasticsearch.action.search.SearchRequest;
  6. import org.elasticsearch.action.search.SearchResponse;
  7. import org.elasticsearch.client.RequestOptions;
  8. import org.elasticsearch.client.RestClient;
  9. import org.elasticsearch.client.RestHighLevelClient;
  10. import org.elasticsearch.index.query.BoolQueryBuilder;
  11. import org.elasticsearch.index.query.QueryBuilders;
  12. import org.elasticsearch.search.SearchHit;
  13. import org.elasticsearch.search.SearchHits;
  14. import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
  15. import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
  16. import org.elasticsearch.search.sort.SortOrder;
  17. import org.junit.jupiter.api.AfterEach;
  18. import org.junit.jupiter.api.BeforeEach;
  19. import org.junit.jupiter.api.Test;
  20. import org.springframework.boot.test.context.SpringBootTest;
  21. import org.springframework.util.CollectionUtils;
  22. import java.io.IOException;
  23. import java.util.Map;
  24. @SpringBootTest
  25. public class HotelSearchTest {
  26. private RestHighLevelClient client;
  27. /**
  28. * 查询所有酒店数据
  29. * @throws IOException
  30. */
  31. @Test
  32. void testMatchAll() throws IOException{
  33. //1、准备Request
  34. SearchRequest request = new SearchRequest("hotel");
  35. //2、准备DSL
  36. request.source().query(QueryBuilders.matchAllQuery());
  37. //3、发送请求
  38. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  39. handlerResponce(response);
  40. }
  41. /**
  42. * 全文检索
  43. * @throws IOException
  44. */
  45. @Test
  46. void testMatch() throws IOException{
  47. //1、准备Request对象
  48. SearchRequest request = new SearchRequest("hotel");
  49. //2、准备DSL
  50. request.source().query(QueryBuilders.matchQuery("name","如家"));
  51. //3、发送请求
  52. SearchResponse response = client.search(request,RequestOptions.DEFAULT);
  53. handlerResponce(response);
  54. }
  55. /**
  56. * bool查询
  57. * @throws IOException
  58. */
  59. @Test
  60. void testBool() throws IOException{
  61. //1、准备Request对象
  62. SearchRequest request = new SearchRequest("hotel");
  63. //2、准备DSL
  64. //2.1、准备BooleanQuery
  65. BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
  66. //2.2、添加term
  67. queryBuilder.must(QueryBuilders.termQuery("city","上海"));
  68. //2.3、添加range
  69. queryBuilder.filter(QueryBuilders.rangeQuery("price").lt(250));
  70. request.source().query(queryBuilder);
  71. //3、发送请求
  72. SearchResponse response = client.search(request,RequestOptions.DEFAULT);
  73. handlerResponce(response);
  74. }
  75. /**
  76. * 排序和分页
  77. * @throws IOException
  78. */
  79. @Test
  80. void testPageAndSort() throws IOException{
  81. int page = 1,size = 5;
  82. //1、创建Request对象
  83. SearchRequest request = new SearchRequest("hotel");
  84. //2、准备DSL
  85. //2.1、查询所有数据matchAll
  86. request.source().query(QueryBuilders.matchAllQuery());
  87. //2.2、sort 排序
  88. request.source().sort("price", SortOrder.ASC);
  89. //2.3、page 分页
  90. request.source().from((page - 1) * size).size(size);
  91. //3、发送请求
  92. SearchResponse response = client.search(request,RequestOptions.DEFAULT);
  93. handlerResponce(response);
  94. }
  95. /**
  96. * 高亮显示查询
  97. * @throws IOException
  98. */
  99. @Test
  100. void testHighLighter() throws IOException{
  101. //1、创建Request对象
  102. SearchRequest request = new SearchRequest("hotel");
  103. //2、准备DSL
  104. //2.1、query
  105. request.source().query(QueryBuilders.matchQuery("all","如家"));
  106. //2.2、高亮
  107. request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
  108. //3、发送请求
  109. SearchResponse response = client.search(request,RequestOptions.DEFAULT);
  110. handlerResponce(response);
  111. }
  112. /**
  113. * 解析
  114. * @param response
  115. */
  116. private void handlerResponce(SearchResponse response) {
  117. //4、解析结果
  118. SearchHits searchHits = response.getHits();
  119. //4.1查询的总条数
  120. long total = searchHits.getTotalHits().value;
  121. System.out.println("共搜索到" + total + "条数据");
  122. //4.2文档数组
  123. SearchHit[] hits = searchHits.getHits();
  124. //4.3遍历
  125. for (SearchHit hit : hits) {
  126. //获取文档source
  127. String json = hit.getSourceAsString();
  128. //反序列化
  129. HotelDoc hotelDoc = JSON.parseObject(json,HotelDoc.class);
  130. //获取高亮结果
  131. Map<String, HighlightField> highlightFields = hit.getHighlightFields();
  132. if (!CollectionUtils.isEmpty(highlightFields)){
  133. //根据字段名获取高亮结果
  134. HighlightField highlightField = highlightFields.get("name");
  135. if (highlightField != null){
  136. //获取高亮值
  137. String name = highlightField.getFragments()[0].string();
  138. //覆盖非高亮结果
  139. hotelDoc.setName(name);
  140. }
  141. }
  142. System.out.println("hotelDoc = "+ hotelDoc);
  143. }
  144. }
  145. @BeforeEach
  146. void setUp(){
  147. this.client = new RestHighLevelClient(RestClient.builder(
  148. HttpHost.create("http://192.168.16.3:9200")
  149. ));
  150. }
  151. @AfterEach
  152. void tearDown() throws IOException {
  153. client.close();
  154. }
  155. }

11.5.3、案例

搜索、分页、条件过滤、附近的酒店、广告置顶

  1. package cn.itcast.hotel.service.impl;
  2. import cn.itcast.hotel.mapper.HotelMapper;
  3. import cn.itcast.hotel.pojo.Hotel;
  4. import cn.itcast.hotel.pojo.HotelDoc;
  5. import cn.itcast.hotel.pojo.PageResult;
  6. import cn.itcast.hotel.pojo.RequestParams;
  7. import cn.itcast.hotel.service.IHotelService;
  8. import com.alibaba.fastjson.JSON;
  9. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  10. import org.elasticsearch.action.search.SearchRequest;
  11. import org.elasticsearch.action.search.SearchResponse;
  12. import org.elasticsearch.client.RequestOptions;
  13. import org.elasticsearch.client.RestHighLevelClient;
  14. import org.elasticsearch.common.geo.GeoPoint;
  15. import org.elasticsearch.common.unit.DistanceUnit;
  16. import org.elasticsearch.index.query.BoolQueryBuilder;
  17. import org.elasticsearch.index.query.QueryBuilders;
  18. import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
  19. import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
  20. import org.elasticsearch.search.SearchHit;
  21. import org.elasticsearch.search.SearchHits;
  22. import org.elasticsearch.search.sort.SortBuilders;
  23. import org.elasticsearch.search.sort.SortOrder;
  24. import org.springframework.beans.factory.annotation.Autowired;
  25. import org.springframework.stereotype.Service;
  26. import java.io.IOException;
  27. import java.util.ArrayList;
  28. import java.util.List;
  29. @Service
  30. public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
  31. @Autowired
  32. private RestHighLevelClient client;
  33. @Override
  34. public PageResult search(RequestParams params) {
  35. try {
  36. //1、创建request对象
  37. SearchRequest request = new SearchRequest("hotel");
  38. //2、准备DSL
  39. //2.1、query
  40. bulidBasicQuery(params, request);
  41. //2.3、分页
  42. int page = params.getPage();
  43. int size = params.getSize();
  44. request.source().from((page - 1) * size).size(size);
  45. //2.4、排序
  46. if (params.getLocation() != null && !params.getLocation().equals("")){
  47. request.source().sort(SortBuilders.geoDistanceSort("location",new GeoPoint(params.getLocation()))
  48. .order(SortOrder.ASC)
  49. .unit(DistanceUnit.KILOMETERS));
  50. }
  51. //3、发送请求,得到响应
  52. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  53. //4、解析响应
  54. return handlerResponce(response);
  55. } catch (IOException e) {
  56. throw new RuntimeException(e);
  57. }
  58. }
  59. /**
  60. * 条件过滤
  61. * @param params 参数
  62. * @param request 搜索请求
  63. */
  64. private void bulidBasicQuery(RequestParams params, SearchRequest request) {
  65. //2.2构建BooleanQuery
  66. BoolQueryBuilder queryBuilder = new BoolQueryBuilder();
  67. String key = params.getKey();
  68. if (key == null || "".equals(key)) {
  69. queryBuilder.must(QueryBuilders.matchAllQuery());
  70. } else {
  71. queryBuilder.must(QueryBuilders.matchQuery("all", key));
  72. }
  73. //2.3、条件过滤
  74. //2.3.1、城市条件
  75. if (params.getCity() != null && !params.getCity().equals("")){
  76. queryBuilder.filter(QueryBuilders.termQuery("city",params.getCity()));
  77. }
  78. //2.3.2、品牌条件
  79. if (params.getBrand() != null && !params.getBrand().equals("")){
  80. queryBuilder.filter(QueryBuilders.termQuery("brand",params.getBrand()));
  81. }
  82. //2.3.3、星级条件
  83. if (params.getStarName() != null && !params.getStarName().equals("")){
  84. queryBuilder.filter(QueryBuilders.termQuery("starName",params.getStarName()));
  85. }
  86. //2.3.4、价格条件
  87. if (params.getMinPrice() != null && params.getMaxPrice() != null){
  88. queryBuilder.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
  89. }
  90. //3、算分控制
  91. FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(
  92. //原始查询,相关性算分的查询
  93. queryBuilder,
  94. //function score的数组
  95. new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
  96. //其中的一个 function score 元素
  97. new FunctionScoreQueryBuilder.FilterFunctionBuilder(
  98. //过滤条件
  99. QueryBuilders.termQuery("isAD",true),
  100. //算分函数
  101. ScoreFunctionBuilders.weightFactorFunction(10)
  102. )
  103. });
  104. request.source().query(functionScoreQueryBuilder);
  105. }
  106. /**
  107. * 解析
  108. *
  109. * @param response
  110. */
  111. private PageResult handlerResponce(SearchResponse response) {
  112. //4、解析结果
  113. SearchHits searchHits = response.getHits();
  114. //4.1查询的总条数
  115. long total = searchHits.getTotalHits().value;
  116. //4.2文档数组
  117. SearchHit[] hits = searchHits.getHits();
  118. //4.3遍历
  119. List<HotelDoc> hotels = new ArrayList<>();
  120. for (SearchHit hit : hits) {
  121. //获取文档source
  122. String json = hit.getSourceAsString();
  123. //反序列化
  124. HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
  125. //获取排序值
  126. Object[] sortValues = hit.getSortValues();
  127. if (sortValues.length > 0){
  128. Object sortValue = sortValues[0];
  129. hotelDoc.setDistance(sortValue);
  130. }
  131. hotels.add(hotelDoc);
  132. }
  133. //4.4、封装返回
  134. return new PageResult(total,hotels);
  135. }
  136. }

11.6、数据聚合

11.6.1、数据聚合-聚合的分类

什么是聚合?

  • 聚合是对文档数据的统计、分析、计算

聚合的常见种类有哪些?

  • Bucket:对文档数据分组,并统计每组数量
  • Metric:对文档数据做计算,例如avg
  • Pipline:基于其它聚合结果再做聚合

参与聚合的字段类型必须是:

  • keyword
  • 数值
  • 日期
  • 布尔

11.6.2、数据聚合-DSL实现Buckey聚合(桶聚合)

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

  • 限定文档的聚合范围

聚合必须的三要素:

  • 聚合名称
  • 聚合类型
  • 聚合字段

聚合可配置属性有:

  • size:指定聚合结果数量
  • order:指定聚合结果排序方式
  • field:指定聚合字段
  1. ###聚合功能,自定义排序规则
  2. GET /hotel/_search
  3. {
  4. "size": 0,
  5. "aggs": {
  6. "brandAgg": {
  7. "terms": {
  8. "field": "brand",
  9. "size": 10,
  10. "order": {
  11. "_count": "asc"
  12. }
  13. }
  14. }
  15. }
  16. }
  17. ###聚合功能,限定聚合范围
  18. GET /hotel/_search
  19. {
  20. "query": {
  21. "range": {
  22. "price": {
  23. "lte": 200
  24. }
  25. }
  26. },
  27. "size": 0,
  28. "aggs": {
  29. "brandAgg": {
  30. "terms": {
  31. "field": "brand",
  32. "size": 10,
  33. "order": {
  34. "_count": "asc"
  35. }
  36. }
  37. }
  38. }
  39. }

11.6.3、数据聚合-DSL实现Metrics聚合(嵌套聚合)

  1. ### 嵌套聚合Metrics
  2. GET /hotel/_search
  3. {
  4. "size": 0,
  5. "aggs": {
  6. "brandAgg": {
  7. "terms": {
  8. "field": "brand",
  9. "size": 10,
  10. "order": {
  11. "scoreAgg.avg": "desc"
  12. }
  13. },
  14. "aggs": {
  15. "scoreAgg": {
  16. "stats": {
  17. "field": "score"
  18. }
  19. }
  20. }
  21. }
  22. }
  23. }
  1. /**
  2. * 数据聚合 多条件聚合
  3. * @return
  4. */
  5. @Override
  6. public Map<String, List<String>> filters() {
  7. try {
  8. //1、创建Request对象
  9. SearchRequest request = new SearchRequest("hotel");
  10. //2、准备DSL
  11. //2.1、query
  12. // bulidBasicQuery(params, request);
  13. //2.2、设置size
  14. request.source().size(0);
  15. //2.3、聚合
  16. bulidAggregation(request);
  17. //3、发送请求
  18. SearchResponse response = client.search(request,RequestOptions.DEFAULT);
  19. //4、解析结果
  20. Map<String,List<String>> result = new HashMap<>();
  21. Aggregations aggregations = response.getAggregations();
  22. List<String> brandList = getAggByName(aggregations, "brandAgg");
  23. //4.4、放入map
  24. result.put("品牌",brandList);
  25. List<String> cityList = getAggByName(aggregations, "cityAgg");
  26. //4.4、放入map
  27. result.put("城市",cityList);
  28. List<String> starList = getAggByName(aggregations, "starAgg");
  29. //4.4、放入map
  30. result.put("星级",starList);
  31. return result;
  32. } catch (IOException e) {
  33. throw new RuntimeException(e);
  34. }
  35. }
  36. /**
  37. * 封装 聚合
  38. * @param request
  39. */
  40. private void bulidAggregation(SearchRequest request) {
  41. request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(100));
  42. request.source().aggregation(AggregationBuilders.terms("cityAgg").field("city").size(100));
  43. request.source().aggregation(AggregationBuilders.terms("starAgg").field("starName").size(100));
  44. }
  45. /**
  46. * 根据聚合名称获取聚合结果
  47. * @param aggregations
  48. * @return
  49. */
  50. private List<String> getAggByName(Aggregations aggregations,String aggName) {
  51. //4.1、根据聚合名称获取聚合结果
  52. Terms terms = aggregations.get(aggName);
  53. //4.2、获取buckets
  54. List<? extends Terms.Bucket> buckets = terms.getBuckets();
  55. //4.3、遍历
  56. List<String> list = new ArrayList<>();
  57. for (Terms.Bucket bucket : buckets) {
  58. //4.4、获取key
  59. String key = bucket.getKeyAsString();
  60. list.add(key);
  61. }
  62. return list;
  63. }

11.7、自动补全

11.7.1、自动补全-自定义分词器

如何使用拼音分词器?

①下载pinyin分词器

②解压并放到elasticsearch的plugin目录

③重启即可

如何自定义分词器?

创建索引时,在settings中配置,可以包含三部分:

  • character filter
  • tockenizer
  • filter

拼音分词器注意事项?

为了避免搜索到同音字,搜索时尽量不要使用拼音分词器

  1. ### 自定义拼音分词器
  2. PUT /test
  3. {
  4. "settings": {
  5. "analysis": {
  6. "analyzer": {
  7. "my_analyzer": {
  8. "tokenizer": "ik_max_word",
  9. "filter": "py"
  10. }
  11. },
  12. "filter": {
  13. "py": {
  14. "type": "pinyin",
  15. "keep_full_pinyin": false,
  16. "keep_joined_full_pinyin": true,
  17. "keep_original": true,
  18. "limit_first_letter_length": 16,
  19. "remove_duplicated_term": true,
  20. "none_chinese_pinyin_tokenize": false
  21. }
  22. }
  23. }
  24. }
  25. , "mappings": {
  26. "properties": {
  27. "name": {
  28. "type": "text",
  29. "analyzer": "my_analyzer",
  30. "search_analyzer": "ik_smart"
  31. }
  32. }
  33. }
  34. }
  35. ### 查询自定义分词器数据
  36. GET /test/_analyze
  37. {
  38. "text": ["查询自定义分词器数据"],
  39. "analyzer": "my_analyzer"
  40. }
  41. ### 添加文档数据
  42. POST /test/_doc/1
  43. {
  44. "id": 1,
  45. "name": "狮子"
  46. }
  47. POST /test/_doc/2
  48. {
  49. "id": 2,
  50. "name": "虱子"
  51. }
  52. ### 查询文档数据
  53. GET /test/_search
  54. {
  55. "query": {
  56. "match": {
  57. "name": "掉入狮子笼咋办"
  58. }
  59. }
  60. }

11.7.2、自动补全-DSL实现自动补全查询

自动补全对字段的要求?

  • 类型是completion类型
  • 字段值是多词条的数组
  1. ### 自动补全的索引库
  2. PUT /test2
  3. {
  4. "mappings": {
  5. "properties": {
  6. "title":{
  7. "type": "completion"
  8. }
  9. }
  10. }
  11. }
  12. ### 示例数据
  13. POST /test2/_doc
  14. {
  15. "title": ["Sony", "WH-1000XM3"]
  16. }
  17. POST /test2/_doc
  18. {
  19. "title": ["SK-II", "PITERA"]
  20. }
  21. POST /test2/_doc
  22. {
  23. "title": ["Nintendo", "switch"]
  24. }
  25. ### 自动补全查询
  26. POST /test2/_search
  27. {
  28. "suggest": {
  29. "title_suggest": {
  30. "text": "w",
  31. "completion": {
  32. "field": "title",
  33. "skip_duplicates": true,
  34. "size": 10
  35. }
  36. }
  37. }
  38. }
  1. /**
  2. * 自动补全查询
  3. * @param prefix
  4. * @return
  5. */
  6. @Override
  7. public List<String> getSuggestions(String prefix) {
  8. try {
  9. //1、创建Request对象
  10. SearchRequest request = new SearchRequest("hotel");
  11. //2、准备DSL
  12. request.source().suggest(new SuggestBuilder().addSuggestion(
  13. "suggestions",
  14. SuggestBuilders.completionSuggestion("suggestion")
  15. .prefix(prefix)
  16. .skipDuplicates(true)
  17. .size(10)));
  18. //3、发送请求
  19. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  20. //4、解析结果
  21. Suggest suggest = response.getSuggest();
  22. //4.1、根据补全名称获取补全结果
  23. CompletionSuggestion suggestion = suggest.getSuggestion("suggestions");
  24. //4.2、获取options
  25. List<CompletionSuggestion.Entry.Option> options = suggestion.getOptions();
  26. //4.3、遍历
  27. List<String> list = new ArrayList<>();
  28. for (CompletionSuggestion.Entry.Option option : options) {
  29. String text = option.getText().toString();
  30. list.add(text);
  31. }
  32. return list;
  33. } catch (IOException e) {
  34. throw new RuntimeException(e);
  35. }
  36. }

11.8、数据同步

11.8.1、数据同步-同步方案分析

方式一:同步调用

  • 优点:实现简单,粗暴
  • 缺点:业务耦合度高

方式二:异步通知

  • 优点:低耦合,实现难度一般
  • 缺点:依赖mq的可靠性

方式三:监听binlog

  • 优点:完全解除服务间耦合
  • 缺点:开启binlog增加数据库负担、实现复杂度高

11.9、ES集群

11.9.1、ES集群-集群职责及脑裂

master eligible节点的作用是什么?

  • 参与集群选主
  • 主节点可以管理集群状态、管理分片信息、处理创建和删除索引库的请求

data节点的作用是什么?

  • 数据库的CRUD

coordinator节点的作用是什么?

  • 路由请求到其它节点
  • 合并查询到的结果,返回给用户

11.9.2、ES集群-分布式新增和查询流程

分布式新增如何确定分片?

  • coordinating node(协调节点)根据id做hash运算,得到结果为shard数量取余,余数就是对应的分片

分布式查询:

  • 分散阶段:coordinating node将查询请求分发给不同分片
  • 收集阶段:将查询结果汇总到coordinating node,整理并返回给用户

11.9.3、ES集群-故障转移

故障转移:

  • master宕机后,EligibleMaster(候选节点)选举为新的主节点
  • master节点监控分片、节点状态,将故障节点上的分片转移到正常节点,确保数据安全
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/一键难忘520/article/detail/925968?site
推荐阅读
相关标签
  

闽ICP备14008679号