学习视频链接:SpringCloud | 黑马程序员
Nacos
官方文档:Nacos Docker 快速开始 | Nacos 官网
Docker部署
docker版本:Docker version 26.1.1【windows版本】
1.拉取镜像
docker pull nacos/nacos-server:v3.0.1
2.运行nacos
docker run --name nacos-new -e MODE=standalone -e PREFER_HOST_MODE=hostname -e NACOS_AUTH_ENABLE=true -e NACOS_AUTH_TOKEN="$( [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((New-Guid).ToString())) )" -e NACOS_AUTH_IDENTITY_KEY="nacos-key" -e NACOS_AUTH_IDENTITY_VALUE="nacos-value" -p 8080:8080 -p 8848:8848 -p 9848:9848 -d nacos/nacos-server:v3.0.1
格式化一下:
docker run --name nacos-new `
-e MODE=standalone `
-e PREFER_HOST_MODE=hostname `
-e NACOS_AUTH_ENABLE=true `
-e NACOS_AUTH_TOKEN="$( [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((New-Guid).ToString())) )" `
-e NACOS_AUTH_IDENTITY_KEY="nacos-key" `
-e NACOS_AUTH_IDENTITY_VALUE="nacos-value" `
-p 8080:8080 -p 8848:8848 -p 9848:9848 `
-d nacos/nacos-server:v3.0.1
--name nacos-new
:指定容器名称为nacos-new
-e MODE=standalone
:设置 Nacos 以单机模式运行(非集群模式,适用于开发 / 测试)-e PREFER_HOST_MODE=hostname
:服务注册时优先使用主机名而非 IP-e NACOS_AUTH_ENABLE=true
:启用 JWT 认证机制(Nacos 3.0 + 默认强制启用)-e NACOS_AUTH_TOKEN=...
:JWT 签名密钥(Base64 格式)-e NACOS_AUTH_IDENTITY_KEY="nacos-key"
:服务间认证的身份标识 Key-e NACOS_AUTH_IDENTITY_VALUE="nacos-value"
:服务间认证的身份标识 Value-p 8080:8080
:Nacos 控制台访问端口(HTTP)-p 8848:8848
:服务注册发现端口(兼容旧版客户端)-p 9848:9848
:gRPC 通信端口(Nacos 2.0 + 长连接通信)
3.测试
访问:http://localhost:8080/index.html
Nacos介绍
核心功能:
- 服务注册与发现:提供基于 DNS 和 RPC 的服务发现机制,支持服务实例的自动注册与反注册,服务消费者可通过接口获取服务列表及健康实例,实现服务的动态发现与调用。
- 动态配置管理:支持分布式系统的配置集中管理,提供配置的动态更新、版本控制及灰度发布能力,通过推送机制实时同步配置到各服务实例,避免服务重启即可生效。
- 服务健康监测:持续检测服务实例的健康状态,自动剔除不健康实例,确保流量导向可用节点,同时支持自定义健康检查规则,适配不同场景的服务存活检测需求。
- 服务路由与流量管理:提供服务级别的路由规则、负载均衡策略(如权重、一致性哈希等),以及流量控制、熔断降级等能力,帮助优化服务调用链路,保障系统稳定性。
- 服务元数据管理:统一管理服务及其实例的元数据信息(如权重、标签、版本等),支持基于元数据的服务筛选与路由,为微服务架构中的服务治理提供数据支撑。
基本概念:
- 命名空间(Namespace)
- 作用:租户或环境隔离的逻辑单元,用于区分不同环境(如开发 / 测试 / 生产)或租户的配置与服务数据。
- 配置路径:左侧菜单 → 命名空间 → 新建或管理命名空间,每个命名空间生成唯一 ID(如
d4a3f92b-xxx
),客户端需通过该 ID 关联对应环境。 - 典型场景:多租户隔离、跨环境配置(如数据库地址差异)。
- 配置分组(Group)
- 作用:对配置集进行逻辑分组,区分相同 Data ID 的不同用途(如
database
组与mq
组)。 - 配置路径:配置管理 → 配置列表 → 新建配置时指定 Group 名称(默认
DEFAULT_GROUP
)。 - 注意:客户端需通过
spring.cloud.nacos.config.group
指定 Group 名称以匹配配置5。
- 作用:对配置集进行逻辑分组,区分相同 Data ID 的不同用途(如
- Data ID
- 作用:配置集的唯一标识,通常对应配置文件名(如
application.properties
),格式为${prefix}-${spring.profile.active}.${file-extension}
。 - 配置路径:配置管理 → 配置列表 → 新建配置时填写 Data ID 及内容。
- 示例:
userservice-dev.yaml
表示userservice
服务在dev
环境的 YAML 格式配置。
- 作用:配置集的唯一标识,通常对应配置文件名(如
部署模式:
1.单机模式(Standalone)
单节点,不采用集群,数据存储在本地文件或嵌入式数据库
2.集群模式(Cluster)
多个nacos节点,集群内数据保持同步
- 多节点组成集群,通过 Raft 算法保证数据一致性,支持高可用性和负载均衡。
- 数据可持久化到外部数据库(如 MySQL),避免单机数据丢失问题。
3.云原生部署模式(Kubernetes)
- 基于容器化技术(Docker)和 Kubernetes 集群部署,支持弹性扩缩容和服务发现。
- 利用 K8s 的 StatefulSet 保证节点顺序性和数据持久化,通过 Service 暴露服务。
4、多集群部署模式(Multi-Cluster)
- 在多个物理隔离的集群(如跨地域、跨云厂商)中部署 Nacos,通过网关或 DNS 实现跨集群服务调用。
- 支持 Namespace 隔离不同集群的服务与配置。
比较:
部署模式 | 可用性 | 数据持久化 | 适用场景 | 复杂度 |
---|---|---|---|---|
单机模式 | 低(单点) | 否(H2 数据库) | 开发测试 | 简单 |
集群模式 | 高(多节点) | 是(外部数据库) | 生产环境、高并发业务 | 中等 |
云原生模式 | 高(K8s 调度) | 是(PVC + 数据库) | 云原生架构、弹性扩缩容 | 较高 |
多集群模式 | 极高(跨地域) | 按需同步 | 全球化业务、多地域容灾 | 高 |
Java-SDK
Nacos 的 Java SDK(或称Nacos-Java-Client),是一个针对 Nacos 配置中心、服务注册中心、分布式锁等场景的 Java SDK。旨在为Java的微服务或分布式应用提供稳定易用的配置中心、服务注册中心、分布式锁等功能,方便开发者访问Nacos进行配置、服务和分布式锁的操作。
Nacos 的 Java SDK需要 JDK 1.8 及以上版本的Java运行环境。
服务注册流程
小案例:
写个小demo来分析一下
application.yml
配置文件:
server:
port: 8081
spring:
application:
name: nacos-test # 服务名称
cloud:
nacos:
discovery:
server-addr: localhost:8848
username: nacos
password: 123
namespace: public
cluster-name: DEFAULT
metadata:
version: 1.0.0
nacos.access.key: nacos-key # 与Docker启动参数一致
nacos.access.value: nacos-value # 与Docker启动参数一致
pom.xml
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/>
</parent>
<groupId>com.xuan</groupId>
<artifactId>nacos-test</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-cloud.version>2023.0.4</spring-cloud.version>
<spring-cloud-alibaba.version>2022.0.0.0-RC2</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Cloud 依赖管理 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba 依赖管理 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Nacos 服务发现依赖(已包含 nacos-client) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
在服务注册之前一定要记得启动nacos!!!
启动项目后,可以在nacos管理界面看到服务:
通过日志来分析一下这个服务注册流程:
、命名空间、超时时间等参数。 - 命名空间检查:尝试从多种来源获取命名空间(如
ans.namespace
、ALIBABA_ALIWARE_NAMESPACE
),若未配置则使用默认命名空间(public
)。 - 认证插件加载:初始化认证机制(如 Nacos 原生认证、RAM 认证),若未配置凭证则使用匿名访问。
- Java sdk : 通过
NacosClientProperties
读取配置文件里面的内容读取到
2. 连接建立(gRPC)
- 创建 RPC 客户端:初始化 gRPC 客户端,生成唯一 ID(如
b5ac26dc-423d-4b96-8f3c-454ab1bf8ae7
)。 - 连接服务端:
- 通过 HTTP 端口(8848)进行初始通信。
- 建立 gRPC 长连接(默认端口 9848),用于双向通信。
- 注册请求处理器:客户端注册用于处理服务端推送的处理器(如连接重置、服务变更通知)。
- gRPC是一个RPC框架,主要用于远程过程调用。
3. 服务注册请求
- 构建服务实例信息:
- 服务名:如
nacos-test
。 - 实例元数据:IP 地址(如
172.20.10.5
)、端口(8081)、权重、健康状态、集群名称(DEFAULT
)等。 - 自定义元数据:如版本号(
version=1.0.0
)、认证信息(nacos.access.key
)。
- 服务名:如
- 发送注册请求:通过 gRPC 将服务实例信息发送至 Nacos 服务端。
4. 服务端处理与响应
- 服务端接收请求:Nacos 服务端收到注册请求后,将实例信息存储在注册表中。
- 分配实例 ID:为临时实例(
ephemeral=true
)生成唯一 ID(如172.20.*.*#8081#DEFAULT#public
)。 - 返回注册结果:服务端返回成功响应,客户端记录注册完成。
5. 注册后操作
- 心跳机制:客户端定期向服务端发送心跳(默认 5 秒一次),维持服务实例的健康状态。
- 服务发现准备:
- 订阅服务:客户端可订阅其他服务的变更事件。
- 接收推送:服务端主动推送服务列表变更,客户端更新本地缓存。
RabbitMQ
部署
docker(Windows)部署RabbitMQ:
1.在线拉取:
docker pull rabbitmq:3-management
如果失败了,很大原因是因为加速器的原因。
可以先下载mq的镜像包,再使用下面这个指令:
docker load -i mq.tar
2.运行:
docker run -e RABBITMQ_DEFAULT_USER=itcast -e RABBITMQ_DEFAULT_PASS=202205567103 --name mq --hostname mq1 -p 15672:15672 -p 5672:5672 -d rabbitmq:3-management
我是在本地运行的,没有用虚拟机,所以我直接访问本地的http://127.0.0.1:15672/就可以访问到RabbitMQ的管理界面,如果用虚拟机部署,这里的ip地址就是你虚拟机的地址。
用户密码就是你在运行镜像的时候所配置的
RabbitMQ 默认使用两个主要的端口号:
- 5672:这是AMQP(Advanced Message Queuing Protocol)协议使用的端口,用于客户端应用程序与RabbitMQ服务器之间的通信。当您配置客户端连接到RabbitMQ时,通常会使用这个端口,除非被更改了默认设置。
- 15672:这是RabbitMQ管理插件所使用的端口,默认情况下提供了基于浏览器的用户界面来管理和监控RabbitMQ服务器。通过访问http://server-name:15672,您可以登录到管理控制台(需要先启用管理插件并配置正确的用户权限)。
概述
官方引导:RabbitMQ tutorial - “Hello World!” | RabbitMQ
- RabbitMQ 是一个开源的消息队列软件(消息代理),它实现了高级消息队列协议(AMQP)。它主要用于应用程序之间或者同一应用的不同组件之间的消息通信,提供可靠的消息传递服务。通过使用RabbitMQ,可以实现系统间的解耦、异步处理和负载均衡等。
- RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。
关键组件
- publisher:生产者
- consumer:消费者
- exchange个:交换机,负责消息路由
- queue:队列,存储消息
- virtualHost:虚拟主机,隔离不同租户的exchange、queue、消息的隔离
!
图源:https://www.cloudamqp.com/img/blog/exchanges-topic-fanout-direct.png
交换机
类型:
1. Direct Exchange(直接交换机)
- 路由规则:消息会根据其路由键(Routing Key)精确匹配绑定到交换机的队列的路由键。
- 使用场景:适用于需要基于特定值进行精确匹配的场景。例如,日志系统中可以根据严重程度(如 info, warning, error)来路由消息。
2. Fanout Exchange(扇出交换机)
- 路由规则:消息会被广播到所有绑定到该交换机的队列,忽略路由键。
- 使用场景:适用于广播消息的场景,例如通知所有订阅者更新状态。
3. Topic Exchange(主题交换机)
- 路由规则:消息会根据路由键和队列绑定键之间的模式匹配来路由。绑定键可以包含通配符:
* 匹配一个单词。
# 匹配零个或多个单词。
- 使用场景:适用于需要灵活路由的场景,例如日志系统中可以根据日志级别和来源来路由消息。
4. Headers Exchange(头部交换机)
- 路由规则:消息会根据消息头属性而不是路由键来进行路由。可以设置多个消息头,并且可以通过逻辑运算符(如 AND, OR)组合这些条件。
- 使用场景:适用于需要基于复杂条件进行路由的场景,例如根据消息头中的某些元数据来决定消息的去向。
5. Default Exchange(默认交换机)
- 路由规则:这是一个特殊的交换机,它没有名称(空字符串 “”),并且隐式地与每个队列绑定。路由键就是队列名称。
- 使用场景:通常用于简单的应用中,当不需要复杂的路由机制时。
区别:
类型 | 路由规则 | 应用场景 |
---|---|---|
Direct | 精确匹配路由键 | 需要精确匹配的消息路由 |
Fanout | 广播消息到所有绑定的队列 | 需要广播消息的场景 |
Topic | 模式匹配路由键 | 需要灵活路由的消息传递 |
Headers | 根据消息头属性进行路由 | 需要基于复杂条件进行路由的场景 |
Default | 隐式绑定到每个队列 ,路由键即队列名称 | 简单的应用场景 |
Spring AMQP
官方文档:Spring AMQP
在pom.xml中引入以下依赖:
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
编写application.yml文件:
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username:
password:
virtual-host: / # 虚拟机名称
创建队列和交换机的方法:
1.通过配置类
package cn.itcast.mq.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitProducerConfig {
// 声明队列
@Bean
public Queue myQueue() {
return new Queue("my.queue", true); // 第二个参数表示是否持久化
}
// 声明直接交换机
@Bean
public DirectExchange myDirectExchange() {
return new DirectExchange("itcast.exchange", true, false); // 是否持久化, 是否自动删除
}
// 声明绑定关系
@Bean
public Binding bindingRed(Queue myQueue, DirectExchange myDirectExchange) {
return BindingBuilder.bind(myQueue).to(myDirectExchange).with("red");
}
@Bean
public Binding bindingPink(Queue myQueue, DirectExchange myDirectExchange) {
return BindingBuilder.bind(myQueue).to(myDirectExchange).with("pink");
}
}
2.通过注解 (消费者端)
- @RabbitListener 注解允许你监听指定的队列,并且通过 bindings 属性可以定义队列、交换机及其绑定关系。这种方式非常适合在消费者端自动声明和管理这些资源。用于监听指定的队列。当有消息到达该队列时,会调用相应的处理方法。
- @QueueBinding:用于定义队列、交换机及其绑定关系。
value:定义队列。name 是队列名称,durable 表示是否持久化。
exchange:定义交换机。name 是交换机名称,type 是交换机类型(如 ExchangeTypes.DIRECT)。
key:定义路由键。这里指定了两个路由键 “red” 和 “pink”,意味着消息可以通过这两个路由键发送到该队列。
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"),
exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
key = {"red", "pink"}
))
public void ListenDirectQueue1(String msg) {
System.out.println("消费者1收到消息:" + msg);
}
生产者发送消息
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MessageProducer {
private final RabbitTemplate rabbitTemplate;
@Autowired
public MessageProducer(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
/**
* 发送消息到指定的交换机和路由键
*
* @param message 要发送的消息内容
* @param routingKey 路由键
*/
public void sendMessage(String message, String routingKey) {
rabbitTemplate.convertAndSend("itcast.direct", routingKey, message);
}
}
消费者接收消息
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@Service
public class MessageConsumer {
@RabbitListener(queues = "direct.queue1")
public void listenDirectQueue1(String msg) {
System.out.println("消费者1收到消息:" + msg);
}
}
配置Json转换器
添加如下依赖:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.10</version>
</dependency>
在启动类添加:
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
Elasticsearch
初识ES
文档:https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
Java Rest: https://elastic.ac.cn/guide/en/elasticsearch/client/java-api-client/current/introduction.html
介绍
Elasticsearch(简称 ES)是一个基于 Lucene 的分布式开源搜索引擎和数据分析引擎,具有实时搜索、全文检索、结构化数据处理和大规模数据聚合分析等能力
结合 Kibana 可实现数据可视化仪表盘,支持实时监控、趋势分析和异常检测(如服务器性能监控、业务指标告警)
原理
倒排索引
采用倒排索引的方式存储数据,倒排索引按 “关键词→包含该词的文档 ID 集合” 存储
三个重要概念:
- 索引:索引是 ES 中存储数据的逻辑容器,相当于数据库中的表结构
- 文档:文档是 ES 中可搜索的最小数据单元,相当于数据库每一行
- 词条:对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条,词条是对文本数据分词后的最小语义单元,是倒排索引的核心元素。
分词
输入文本按语言规则拆分为关键词(如英文按空格,中文需第三方分词器如 IK Analyzer),再构建倒排索引。
Docker部署
1.添加镜像源
进入 /etc/docker目录下创建daemon.json
文件内容如下:
{
"registry-mirrors": [
"https://***.mirror.aliyuncs.com",
"https://registry.docker-cn.com",
"http://hub-mirror.c.163.com",
"https://docker.mirrors.ustc.edu.cn",
"https://cr.console.aliyun.com",
"https://mirror.ccs.tencentyun.com",
"https://docker.registry.cyou",
"https://docker-cf.registry.cyou",
"https://dockercf.jsdelivr.fyi",
"https://docker.jsdelivr.fyi",
"https://dockertest.jsdelivr.fyi",
"https://mirror.aliyuncs.com",
"https://dockerproxy.com",
"https://mirror.baidubce.com",
"https://docker.m.daocloud.io",
"https://docker.nju.edu.cn",
"https://docker.mirrors.sjtug.sjtu.edu.cn",
"https://docker.mirrors.ustc.edu.cn",
"https://mirror.iscas.ac.cn",
"https://docker.rainbond.cc"
]
}
“https://***.mirror.aliyuncs.com” : 替换为自己的阿里云镜像源 https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors
2.启动docker
sudo systemctl start docker
如果是已经启动的docker,可以使用以下命令:
sudo systemctl daemon-reload
sudo systemctl restart docker
3.下载es
docker pull elasticsearch:7.12.1
4.运行单节点es
docker run -d \
--name es \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "discovery.type=single-node" \
-v es-data:/usr/share/elasticsearch/data \
-v es-plugins:/usr/share/elasticsearch/plugins \
--privileged \
--network es-net \
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.12.1
5.访问测试
在本地访问自己虚拟机ip地址的9200端口 可以看到如下输出
6.创建网络
docker network create es-net
7.下载kibana
(注意下载与es版本对应的)
docker pull kibana:7.12.1
8.运行kibana
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601 \
kibana:7.12.1
9.测试访问
访问虚拟机的5601端口
kibana中提供了一个DevTools界面:
这个界面中可以编写DSL来操作elasticsearch。并且对DSL语句有自动补全功能。
10.下载IK分词器插件(离线安装)
10.1 下载ik安装包
https://github.com/infinilabs/analysis-ik/releases
(注意要下载对应es版本)
10.1 上传到虚拟机中es挂载的目录
[root@localhost plugins]# docker volume inspect es-plugins
[
{
"CreatedAt": "2025-05-26T20:04:02-07:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/es-plugins/_data",
"Name": "es-plugins",
"Options": null,
"Scope": "local"
}
]
进入 /var/lib/docker/volumes/es-plugins/_data
将文件上传
10.2 重启容器
docker restart es
10.3 使用kibana测试一下
10.4 分词器配置
可以添加自己的扩展字典,但是这个文件的编码得是UTF-8
测试一下
核心数据类型
类别 | 数据类型 | 描述 |
---|---|---|
字符串 | text |
全文搜索字段,会被分词处理 |
keyword |
精确匹配字段,不分词(如 ID、标签、枚举值) | |
数值 | long 、integer 、short 、byte |
整数类型(范围不同) |
double 、float 、half_float |
浮点数类型(精度不同) | |
scaled_float |
缩放浮点数(适用于货币等需固定精度的值) | |
布尔 | boolean |
布尔值(true /false ) |
日期 | date |
日期时间(支持多种格式,如2025-05-28 ) |
范围 | integer_range 、float_range |
数值范围类型 |
date_range 、ip_range |
日期范围、IP 范围类型 |
ES使用教程
索引库操作
创建索引库
语法:
PUT /索引名
{
"mappings": {
"properties": {
"字段1":{
},
"字段2":{
},
...
}
}
}
}
eg:
# 创建索引库
# demo为索引库名
PUT /demo
{
"mappings": {
"properties": {
"info": {
"type": "text",
"analyzer": "ik_smart"
},
"email":{
"type": "keyword",
"index": false
},
"name":{
"type": "object",
"properties": {
"firstName":{
"type": "keyword"
},
"lastName":{
"type": "keyword"
}
}
}
}
}
}
查看索引库
# 查看索引库
GET /demo
删除索引库
# 删除索引库
DELETE /demo
修改索引库
给索引库添加字段
语法:
PUT /索引名/_mapping
{
"properties":{
"添加的字段":{
"添加的属性": "添加的值"
}
}
}
eg:
# 修改索引库
# 索引库和mapping一旦创建无法修改
# 但是可以添加新的字段
PUT /demo/_mapping
{
"properties":{
"age":{
"type": "integer"
}
}
}
文档操作
新增文档
语法:
#新增文档DSL语法
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": "值3",
"字段4": {
"子属性1": "值4",
"子属性2": "值5"
}
}
eg:
POST /demo/_doc/1
{
"info": "xkbb",
"email": "0921@karry.cn"
"name": {
"firstName": "俊凯",
"lastName": "王"
}
}
查询文档
语法:
GET /索引库名/_doc/文档id
eg:
# 查询文档
GET /demo/_doc/1
删除文档
语法:
DELETE /索引库名/_doc/文档id
eg:
# 删除文档
DELETE /demo/_doc/1
修改文档
全量修改
跟新增文档类似
语法:
PUT /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": "值3",
"字段4": {
"子属性1": "值4",
"子属性2": "值5"
}
}
eg:
# 修改文档--全量修改
PUT /demo/_doc/1
{
"info" : "出生于1999年9月21日",
"email" : "0921@karry.cn",
"name" : {
"firstName" : "俊凯",
"lastName" : "王"
}
}
增量修改
修改指定字段值
语法:
POST /索引库名/_update/文档id
{
"doc": {
"字段": "新的字段值"
}
}
eg:
# 修改文档--增量修改
POST /demo/_update/1
{
"doc": {
"info": "十九岁的时差"
}
}
DSL查询语法
语法
GET /索引名/_search
{
"query": {
"查询方式": {
}
}
}
查询方式
match
: 单字段查询multi_match
: 多字段查询match_all
: 查询所有数据term
: 精确查询,查询不分词的字段,因此查询的条件也必须是不分词的词条range
: 范围查询,一般对数值类型进行限制geo_bounding_box
: 矩形范围查询,左上角到右下角的范围geo_distance
:圆形范围查询,以一个点为圆形,一定距离为半径,这个圆范围内的数据
全文查询
- match: 单字段查询
- multi_match: 多字段查询
- match_all: 查询所有数据
字段查询
单字段:
GET /索引名/_search
{
"query": {
"match": {
"字段": "字段值"
}
}
}
eg:
GET /hotel/_search
{
"query": {
"match": {
"city": "上海"
}
}
}
GET /hotel/_search
{
"query": {
"match": {
"all": "外滩如家"
}
}
}
多字段:
GET /索引名/_search
{
"query": {
"multi_match": {
"query": "搜索内容",
"fields": ["字段1","字段2","字段3",...]
}
}
}
eg:
GET /hotel/_search
{
"query": {
"multi_match": {
"query": "外滩如家",
"fields": ["brand","name","city"]
}
}
}
查询所有
GET /索引名/_search
{
"query": {
"match_all": {}
}
}
精准查询
- term : 精确查询,查询不分词的字段,因此查询的条件也必须是不分词的词条
GET /索引名/_search
{
"query": {
"term": {
"字段": {
"value": "字段值"
}
}
}
}
eg:
GET /hotel/_search
{
"query": {
"term": {
"city": {
"value": "上海"
}
}
}
}
范围查询
- range : 范围查询,一般对数值类型进行限制
- 这里的gte代表大于等于,gt则代表大于
- lte代表小于等于,lt则代表小于
GET /索引名/_search
{
"query": {
"range": {
"字段": {
"gte": minv,
"lte": maxv
}
}
}
}
eg:
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gte": 200,
"lte": 1000
}
}
}
}
地理位置查询
- geo_bounding_box : 矩形范围查询,左上角到右下角的范围
- geo_distance :圆形范围查询,以一个点为圆形,一定距离为半径,这个圆范围内的数据
矩形范围查询
GET /索引名/_search
{
"query": {
"geo_bounding_box": {
"字段": {
"top_left": {
"lat": 40.73,
"lon": 120.0
},
"bottom_right": {
"lat": 30.01,
"lon": 150.0
}
}
}
}
}
eg:
GET /hotel/_search
{
"query": {
"geo_bounding_box": {
"location": {
"top_left": {
"lat": 40.73,
"lon": 120.0
},
"bottom_right": {
"lat": 30.01,
"lon": 150.0
}
}
}
}
}
# 简化表示
GET /hotel/_search
{
"query": {
"geo_bounding_box": {
"location": {
"top_left": [ 120.0, 31.73 ],
"bottom_right": [ 150.0, 30.01 ]
}
}
}
}
圆形范围查询
GET /索引名/_search
{
"query": {
"geo_distance": {
"distance": "200km",
"location": {
"lat": 40,
"lon": 200
}
}
}
}
地理位置表示方式:
"location": "40, -70"
// 或者
"location": { "lat": 40, "lon": -70 }
// 又或者
"location": [ -70, 40 ] // 注意这里经度在前,纬度在后
eg:
GET /hotel/_search
{
"query": {
"geo_distance": {
"distance": "200km",
"location": {
"lat": 31.251433,
"lon": 121.47522
}
}
}
}
GET /hotel/_search
{
"query": {
"geo_distance": {
"distance": "200km",
"location": "31.251433, 121.47522"
}
}
}
GET /hotel/_search
{
"query": {
"geo_distance": {
"distance": "200km",
"location": [121.47522, 31.251433]
}
}
}
复杂查询
function_score
:复杂查询,对查询结果进行算分控制
相关性算分
Elasticsearch(ES)的相关性算分是指查询结果与用户搜索词匹配程度的量化评估,分数越高表示文档越相关。ES 默认使用 BM25(Best Matching 25)算法,但也支持自定义评分模型
在Elasticsearch里,“算分” 其实就是给搜索结果 “打分”,判断每个结果和用户搜索的关键词有多匹配,分数越高就排得越靠前。通俗来说,就像老师改卷打分一样,分数高的 “答案”(文档)更符合用户的需求。
算法主要有BM25、TF-IDF、DFR、IB、平滑算法(LMDirichlet 和 LMJelinek-Mercer)、 自定义算法
控制相关性算分
ES通过function_score
可以对结果的相关性算分进行控制,functions + boost_mode
就是对结果进行控制算法(可以认为是一个函数,对过滤的文档进行控制算分)
filter
: 过滤条件,决定哪些文档要进行控制weight
: 加分boost_mode
:在 Elasticsearch(ES)的function_score
查询中,boost_mode
参数控制着 原始查询分数(query score) 与 自定义函数分数(function score) 如何合并,从而影响最终的文档相关性评分模式有:
multiply
:最终分数 = 原始查询分数 × 自定义函数分数
sum
:最终分数 = 原始查询分数 + 自定义函数分数
min
:最终分数 = min (原始查询分数,自定义函数分数)
max
:最终分数 = max (原始查询分数,自定义函数分数)
replace
:最终分数 = 自定义函数分数(完全忽略原始查询分数)
GET /索引名/_search
{
"query": {
"function_score": {
"query": {
"查询方式": {
}
},
"functions": [
{
"filter": {},
"weight": 10
}
],
"boost_mode": "sum"
}
}
}
eg:
GET /hotel/_search
{
"query": {
"function_score": {
"query": {
"match_all": {
}
},
"functions": [
{
"filter": {
"term": {
"brand": "如家"
}
},
"weight": 10
}
],
"boost_mode": "sum"
}
}
}
布尔查询
复合查询Boolean Query
- must:必须匹配每个子查询,类似“与”
- should:选择性匹配子查询,类似“或”
- must_not:必须不匹配,不参与算分,类似“非”
- filter:必须匹配,不参与算分
语法:
GET /索引/_search
{
"query": {
"bool": {
"must": [],
"should": [],
"must_not": [],
"filter": []
}
}
}
搜索结果处理
排序
- 基本语法:使用
sort
参数,指定字段和排序方向。 - 多字段排序:按多个字段依次排序,确保结果唯一性。
- 特殊字段:
_score
:按相关性分数排序。_geo_distance
:按地理距离排序。_script
:按自定义脚本计算结果排序。
语法:
GET /hotel/_search
{
"query": {
"请求方式": {}
},
"sort": [
{"字段": {"order": "desc"}}
]
}
eg:
# 121.479194,31.220115
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"_geo_distance": {
"location": {
"lat": 31.220115,
"lon": 121.479194
},
"order": "asc",
"unit": "km"
}
}
]
}
分页
1.基本分页:from/size 参数
from
:结果的起始位置,默认从 0 开始。size
:每页返回的文档数量,默认值为 10。- 深度分页问题:当
from
过大时(如from=10000
),ES 需要先扫描前 10000 条数据,再返回结果,性能会显著下降。官方建议总页数不超过 10000(即from + size ≤ 10000
)。
语法:
GET /索引名/_search
{
"query": {
"搜索方式": {}
},
"from": 10,
"size": 10,
"sort": []
}
eg:
GET /hotel/_search
{
"query": {
"match_all": {}
},
"from": 10,
"size": 10,
"sort": [
{"price": {"order": "asc"}}
]
}
2.深度分页:Scroll API
- Scroll 会创建一个临时的搜索上下文,适合批量处理数据,但不适合实时交互场景。
- 每次请求都会返回新的
_scroll_id
,需使用最新的_scroll_id
。 - 数据快照:Scroll 基于查询开始时的索引状态,期间的文档更新不会反映在结果中。
语法:
scroll=1m :scroll参数指定上下文保持时间
GET /索引/_search?scroll=1m
{
"size": 10,
"query": {
"match_all": {}
}
}
响应中会返回一个 _scroll_id
,用于获取下一页
"_scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFlY4R0RUTjVvVGhhc2hWYTQyakx4LXcAAAAAAAAULRZWSjV5VExtRlJNZTg5ekxoU2RuMHh3",
GET /_search/scroll
{
"scroll": "1m",
"scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFlY4R0RUTjVvVGhhc2hWYTQyakx4LXcAAAAAAAAULRZWSjV5VExtRlJNZTg5ekxoU2RuMHh3"
}
3.实时分页:search_after 参数
- 必须指定
sort
参数,且search_after
的值要与sort
字段顺序一致。 - 只支持 “下一页” 操作,不支持回退到上一页(需自行维护历史状态)。
- 实时性好,适合实时滚动加载场景(如社交媒体动态)
GET /hotel/_search
{
"size": 2,
"query": {
"match": {
"all": "如家"
}
},
"sort": [
{"price": "asc"},
{"score": "desc"}
]
}
# 根据上一次查询的返回的sort结果为起点
GET /hotel/_search
{
"size": 2,
"query": {
"match": {
"all": "如家"
}
},
"sort": [
{"price": "asc"},
{"score": "desc"}
],
"search_after": [135,45]
}
高亮
在 Elasticsearch(ES)中,高亮是指将搜索结果中与查询匹配的关键词用特殊标签标记出来,以便用户快速识别。
对搜索结果进行高亮展示
语法:
GET /索引名/_search
{
"query": {
"match": {
"字段": "查询字段值" // 查询字段
}
},
"highlight": {
"fields": {
"字段": {} // 指定要高亮的字段
}
}
}
高亮参数:
参数名 | 作用 |
---|---|
type |
高亮算法,可选 unified (默认)、plain 或 fvh 。 |
fragment_size |
每个高亮片段的最大字符数(默认 100)。 |
number_of_fragments |
返回的高亮片段数量(默认 5)。如果设为 0,返回整个字段内容。 |
no_match_size |
当字段未匹配时,返回的字符数(用于显示上下文)。 |
require_field_match |
仅高亮匹配的字段(默认 true )。如果设为 false ,所有字段都会尝试高亮。 |
eg:
GET /hotel/_search
{
"query": {
"match": {
"all": "如家"
}
},
"highlight": {
"fields": {
"all": {
"pre_tags": "<em>",
"post_tags": "</em>",
"require_field_match": "false"
}
}
}
}
数据聚合
聚合分类
聚合类型 | 描述 | 常见示例 |
---|---|---|
桶聚合(Bucket) | 将文档分组到不同的 “桶” 中,每个桶满足特定条件,用于数据分组统计。 | terms (按字段值分组)、range (数值范围分组)、date_range (日期范围分组)、 histogram (数值直方图)、date_histogram (日期直方图)、filters (多条件过滤分组) |
指标聚合(Metrics) | 对数值型字段进行统计计算,生成数值指标。 | avg (平均值)、sum (总和)、max (最大值)、min (最小值)、 count (计数)、cardinality (去重计数)、percentiles (百分位数)、stats (综合统计) |
管道聚合(Pipeline) | 基于其他聚合的结果进行二次计算(依赖父聚合的输出)。 | avg_bucket (桶的平均值)、sum_bucket (桶的总和)、max_bucket (桶的最大值)、 min_bucket (桶的最小值)、percentiles_bucket (桶的百分位数)、derivative (聚合结果的变化率) |
矩阵聚合(Matrix) | 计算多个数值字段之间的相关性或统计关系。 | matrix_stats (矩阵统计,如协方差、相关系数) |
桶聚合
相当于数据库里面的group by
语法:
GET /hotel/_search
{
"aggs": {
"聚合名称": {
"桶聚合类型": {
"参数": 值,
...
},
"aggs": { // 嵌套聚合(可多层)
"子聚合名称": { ... }
}
}
}
}
常用参数:
field
:指定聚合的字段(需为分词字段时加.keyword
后缀)。size
:限制桶的数量(如terms
聚合中返回前 N 个高频值)。order
:桶的排序方式(如按_count
或自定义字段排序)。missing
:指定字段缺失时的处理方式(如归入某个桶)。
聚合类型 | 描述 |
---|---|
terms | 按字段值分组,常用于分类数据聚合(如按标签、状态分组) |
range | 按数值范围分组(如价格区间、年龄分段) |
date_range | 按日期范围分组(如按年月、季度、自定义时间区间) |
histogram | 按数值间隔分组(自动生成等宽区间,适用于连续数值数据) |
date_histogram | 按时间间隔分组(如按天、小时、分钟生成时间桶) |
filters | 按多个过滤条件分组(每个桶对应一个过滤条件) |
missing | 按字段是否存在分组(筛选出缺少指定字段的文档) |
nested | 对嵌套对象(nested 类型字段)进行聚合(需指定嵌套路径) |
reverse_nested | 从嵌套文档反向关联到父文档进行聚合(需配合 nested 使用) |
sampler | 对文档抽样后再聚合(减少数据量,提升性能) |
eg:
GET /索引名/_search
{
"size": 0,
"query": {
"range": {
"price": {
"gte": 100,
"lte": 300
}
}
},
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"size": 5,
"order": {
"scoreAgg.avg": "desc"
}
},
"aggs": {
"scoreAgg": {
"stats": {
"field": "score"
}
}
}
}
}
}
指标聚合
语法:
GET /索引名/_search
{
"aggs": {
"聚合名称": {
"指标聚合类型": {
"field": "字段名", // 或其他参数
...
}
}
}
}
常用参数:
field
:指定要计算的字段(必须为数值、日期或地理类型)。script
:使用脚本自定义计算逻辑(如"script": "doc['price'].value * 1.1"
)。missing
:指定字段缺失时的默认值(如"missing": 0
)。precision_threshold
:cardinality
专用参数,控制去重精度(值越高越精确,但内存消耗越大)。
聚合类型 | 描述 |
---|---|
stats | 计算数值字段的多个指标,包括avg、sum、min、max、count |
avg | 计算数值字段的平均值 |
sum | 计算数值字段的总和 |
min | 计算数值字段的最小值 |
max | 计算数值字段的最大值 |
value_count | 统计字段值的数量(无论是否唯一) |
cardinality | 计算字段的唯一值数量(去重计数,近似算法) |
stats | 一次性返回多个统计指标(count、min、max、avg、sum) |
extended_stats | 扩展统计指标(包含方差、标准差、平方和等) |
percentiles | 计算数值字段的百分位数(如中位数、95% 分位数) |
percentile_ranks | 计算指定值对应的百分位排名(与 percentiles 相反) |
geo_bounds | 计算地理坐标的边界范围(最小 / 最大经纬度) |
geo_centroid | 计算地理坐标的质心(中心点) |
scripted_metric | 使用自定义脚本计算复杂指标(需编写 Painless 脚本) |
eg:
根据brand进行分组,计算每一组的指标
# 通过brand进行分组
GET /hotel/_search
{
"size": 0,
"aggs": {
"brand_agg": {
"terms": {
"field": "brand",
"size": 2
},
"aggs": {
"price_stats": {
"stats": {
"field": "price"
}
},
"sum_price": {
"sum": {
"field": "price"
}
},
"min_price": {
"min": {
"field": "price"
}
},
"max_price":{
"max": {
"field": "price"
}
}
}
}
}
}
响应结果:
"aggregations" : {
"brand_agg" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 141,
"buckets" : [
{
"key" : "7天酒店",
"doc_count" : 30,
"max_price" : {
"value" : 781.0
},
"min_price" : {
"value" : 143.0
},
"price_stats" : {
"count" : 30,
"min" : 143.0,
"max" : 781.0,
"avg" : 446.46666666666664,
"sum" : 13394.0
},
"sum_price" : {
"value" : 13394.0
}
},
{
"key" : "如家",
"doc_count" : 30,
"max_price" : {
"value" : 459.0
},
"min_price" : {
"value" : 127.0
},
"price_stats" : {
"count" : 30,
"min" : 127.0,
"max" : 459.0,
"avg" : 253.2,
"sum" : 7596.0
},
"sum_price" : {
"value" : 7596.0
}
}
]
}
}
管道聚合
语法:
GET /索引名/_search
{
"aggs": {
"聚合名称": {
"管道聚合类型": {
"buckets_path": "源聚合路径", // 必选参数,引用上游聚合结果
"参数1": "值",
"参数2": "值",
...
}
}
}
}
常用参数:
buckets_path
:引用上游聚合的路径(必选)。gap_policy
:处理缺失值的策略(如skip
、insert_zeros
)。format
:结果格式化(如"format": "0.00%"
)。script
:自定义计算逻辑。
聚合类型 | 描述 |
---|---|
avg_bucket | 计算父聚合中多个桶的平均值 |
sum_bucket | 计算父聚合中多个桶的总和 |
min_bucket | 计算父聚合中多个桶的最小值 |
max_bucket | 计算父聚合中多个桶的最大值 |
stats_bucket | 一次性计算父聚合中多个桶的统计指标(count、min、max、avg、sum) |
extended_stats_bucket | 计算父聚合中多个桶的扩展统计指标(含方差、标准差等) |
percentiles_bucket | 计算父聚合中多个桶的百分位数 |
derivative | 计算相邻桶之间的变化率(如增长率、减少率) |
cumulative_sum | 计算累积和(如销售额的累计值) |
moving_avg | 计算滑动平均值(如 7 天移动平均) |
bucket_sort | 对父聚合的桶进行排序和分页 |
bucket_selector | 基于条件过滤父聚合的桶(类似 SQL 的 HAVING 子句) |
矩阵聚合
语法:
GET /索引名/_search
{
"aggs": {
"行维度聚合": {
"terms": {
"field": "row_field",
"size": 10
},
"aggs": {
"列维度聚合": {
"terms": {
"field": "column_field",
"size": 10
},
"aggs": {
"矩阵指标": { "sum": { "field": "value_field" } }
}
}
}
}
}
}
聚合类型 | 描述 | 核心参数 |
---|---|---|
matrix_stats | 计算多个数值字段间的统计关系(协方差、相关性等) | fields (待分析的多字段列表)、missing (缺失值处理) |
nested | 对嵌套对象(nested 类型字段)进行聚合,生成嵌套矩阵结构 | path (嵌套路径)、子聚合(如terms 、stats ) |
reverse_nested | 从嵌套文档反向关联回父文档,补全矩阵维度 | path (反向路径)、子聚合(如terms 、stats ) |
自动补全
拼音分词器
要实现根据字母做补全,就必须对文档按照拼音分词。
拼音分词插件地址:https://github.com/medcl/elasticsearch-analysis-pinyin
eg:
GET /_analyze
{
"text": ["我的小名是安安"],
"analyzer": "pinyin"
}
拼音分词器会将每个字分出拼音
如下:
{
"tokens" : [
{
"token" : "wo",
"start_offset" : 0,
"end_offset" : 0,
"type" : "word",
"position" : 0
},
{
"token" : "wdxmsaa",
"start_offset" : 0,
"end_offset" : 0,
"type" : "word",
"position" : 0
},
{
"token" : "de",
"start_offset" : 0,
"end_offset" : 0,
"type" : "word",
"position" : 1
},
{
"token" : "xiao",
"start_offset" : 0,
"end_offset" : 0,
"type" : "word",
"position" : 2
},
{
"token" : "ming",
"start_offset" : 0,
"end_offset" : 0,
"type" : "word",
"position" : 3
},
{
"token" : "shi",
"start_offset" : 0,
"end_offset" : 0,
"type" : "word",
"position" : 4
},
{
"token" : "an",
"start_offset" : 0,
"end_offset" : 0,
"type" : "word",
"position" : 5
},
{
"token" : "an",
"start_offset" : 0,
"end_offset" : 0,
"type" : "word",
"position" : 6
}
]
}
自定义分词器
在 Elasticsearch 中,自定义分词器(Analyzer)由三个主要组件构成:字符过滤器(Character Filters)、分词器(Tokenizer)和词元过滤器(Token Filters).
elasticsearch中分词器(analyzer)的组成包含三部分:
字符过滤器(Character Filters)
它的作用是在分词之前对原始文本进行预处理。
分词器(Tokenizer)
分词器的任务是将经过预处理的文本切割成一个个独立的词元(Token),可以把它看作是文本的 “切割机”,处理粒度为词语级。
词元过滤器(Token Filters)
词元过滤器是对分词器输出的词元进行进一步的加工处理,可将其视为词元的 “加工厂”,处理粒度同样是词语级。它可以执行多种操作,例如将词元转换为小写、移除停用词(像 “的”“是” 这类常见但无实际意义的词)、提取词干(例如把 “running” 处理成 “run”)、添加同义词,以及在你使用的配置中,将中文转换为拼音等。
语法:
{
"settings": {
"analysis": {
"analyzer": {
"自定义分词器名称": {
"type": "custom", // 声明为自定义类型
"char_filter": ["字符过滤器1", "字符过滤器2"], // 可选,可多个
"tokenizer": "分词器名称", // 必选,只能一个
"filter": ["词元过滤器1", "词元过滤器2"] // 可选,可多个
}
},
"char_filter": { ... }, // 字符过滤器定义
"tokenizer": { ... }, // 分词器定义
"filter": { ... } // 词元过滤器定义
}
}
}
字符过滤器:
过滤器类型 | 描述 |
---|---|
html_strip |
移除 HTML 标签,保留文本内容 |
mapping |
基于映射规则替换字符(如将 “&” 替换为 “and”) |
pattern_replace |
使用正则表达式替换文本 |
icu_normalizer |
Unicode 文本标准化(基于 ICU 库) |
分词器类型:
分词器类型 | 描述 |
---|---|
standard |
默认分词器,按词切分,处理大多数语言,支持停用词 |
simple |
按非字母字符切分,将文本转为小写 |
whitespace |
按空白字符(空格、制表符等)切分,不转换大小写 |
stop |
类似 simple ,但支持停用词过滤 |
keyword |
不分词,将整个文本作为单个词元 |
pattern |
使用正则表达式切分文本 |
language |
特定语言分词器(如英语、中文等),针对特定语言优化 |
ngram |
生成 N-gram 词元(连续字符组合) |
edge_ngram |
生成边缘 N-gram 词元(从开头开始的连续字符组合) |
ik_max_word |
IK 中文分词器,细粒度切分(你示例中使用的分词器) |
ik_smart |
IK 中文分词器,粗粒度切分 |
词元过滤器:
过滤器类型 | 描述 |
---|---|
lowercase |
将词元转为小写 |
uppercase |
将词元转为大写 |
stop |
移除停用词(如 “the”, “and” 等) |
stemmer |
词干提取(如将 “running” 转为 “run”) |
snowball |
基于 Snowball 算法的词干提取器 |
ngram |
生成词元的 N-gram 变体 |
edge_ngram |
生成词元的边缘 N-gram 变体 |
synonym |
同义词替换 |
pinyin |
拼音转换过滤器(你示例中使用的过滤器) |
trim |
去除词元前后的空白字符 |
unique |
移除重复词元 |
length |
过滤指定长度范围外的词元 |
asciifolding |
将非 ASCII 字符转换为等效的 ASCII 字符(如将 “é” 转为 “e”) |
eg:
定义了一个名为 my_analyzer
的自定义分词器,它使用 ik_max_word
分词器对中文进行细粒度分词,并通过 pinyin
词元过滤器将中文转换为拼音。这个配置允许你通过拼音搜索中文内容。
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "ik_max_word", // 使用 IK 分词器进行中文分词
"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 // 非中文不进行拼音转换
}
}
}
}
}
RestHighLevelClient
通过JavaRestClient操作索引库,就像RedisTemplate操作Redis一样,是Java提供的可以与索引库交互的一系列接口
引入依赖:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
指定对应的版本号(与之前下载的es版本保持一致):
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
创建表便于测试:
DROP TABLE IF EXISTS `tb_hotel`;
CREATE TABLE `tb_hotel` (
`id` bigint(20) NOT NULL COMMENT '酒店id',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店名称',
`address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店地址',
`price` int(10) NOT NULL COMMENT '酒店价格',
`score` int(2) NOT NULL COMMENT '酒店评分',
`brand` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店品牌',
`city` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所在城市',
`star_name` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '酒店星级,1星到5星,1钻到5钻',
`business` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商圈',
`latitude` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '纬度',
`longitude` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '经度',
`pic` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '酒店图片',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
(数据来源—黑马程序员)
索引操作:
操作类型 | 功能描述 | 核心类与方法 |
---|---|---|
创建索引 | 创建新索引 | IndicesClient.create(CreateIndexRequest) |
删除索引 | 删除现有索引 | IndicesClient.delete(DeleteIndexRequest) |
检查索引存在 | 判断索引是否存在 | IndicesClient.exists(GetIndexRequest) |
获取索引设置 | 获取索引配置信息 | IndicesClient.getSettings(GetSettingsRequest) |
更新索引设置 | 修改索引配置 | IndicesClient.putSettings(PutSettingsRequest) |
获取索引映射 | 获取索引字段映射 | IndicesClient.getMapping(GetMappingsRequest) |
更新索引映射 | 修改索引字段映射 | IndicesClient.putMapping(PutMappingRequest) |
刷新索引 | 使最近的更改可搜索 | IndicesClient.refresh(RefreshRequest) |
关闭索引 | 关闭索引(释放资源) | IndicesClient.close(CloseIndexRequest) |
打开索引 | 重新打开已关闭的索引 | IndicesClient.open(OpenIndexRequest) |
索引别名管理 | 添加 / 删除索引别名 | IndicesClient.updateAliases(UpdateAliasesRequest) |
查看索引健康状态 | 获取索引健康统计信息 | ClusterClient.health(ClusterHealthRequest) |
文档操作:
操作类型 | 功能描述 | 核心类与方法 |
---|---|---|
添加文档 | 插入新文档(自动生成 ID) | DocumentClient.index(IndexRequest) |
添加 / 更新文档 | 插入或替换文档(指定 ID) | DocumentClient.index(IndexRequest) |
获取文档 | 根据 ID 获取文档 | DocumentClient.get(GetRequest) |
更新文档 | 部分更新文档字段 | DocumentClient.update(UpdateRequest) |
删除文档 | 根据 ID 删除文档 | DocumentClient.delete(DeleteRequest) |
检查文档存在 | 判断文档是否存在 | DocumentClient.exists(GetRequest) |
批量操作 | 批量执行增删改操作 | BulkClient.bulk(BulkRequest) |
批量导入 | 从源索引复制文档 | ReindexClient.reindex(ReindexRequest) |
文档计数 | 统计匹配条件的文档数量 | DocumentClient.count(CountRequest) |
查询操作:
操作类型 | 功能描述 | 核心类与方法 |
---|---|---|
基本查询 | 执行标准搜索 | SearchClient.search(SearchRequest) |
布尔查询 | 组合多个查询条件 | BoolQueryBuilder.must()/should()/must_not() |
全文查询 | 文本搜索(分词匹配) | QueryBuilders.matchQuery() |
精确查询 | 精确匹配单个值 | QueryBuilders.termQuery() |
范围查询 | 数值 / 日期范围过滤 | QueryBuilders.rangeQuery() |
多字段查询 | 多字段同时搜索 | QueryBuilders.multiMatchQuery() |
前缀查询 | 按前缀匹配 | QueryBuilders.prefixQuery() |
通配符查询 | 使用通配符匹配 | QueryBuilders.wildcardQuery() |
正则查询 | 使用正则表达式匹配 | QueryBuilders.regexpQuery() |
地理查询 | 基于地理位置的查询 | QueryBuilders.geoDistanceQuery() |
排序 | 对查询结果排序 | SearchSourceBuilder.sort(FieldSortBuilder) |
分页 | 实现结果分页 | SearchSourceBuilder.from()/size() 或 searchAfter() |
聚合查询 | 分组统计与计算 | AggregationBuilders.terms()/avg()/sum()/histogram() |
高亮显示 | 搜索结果关键词高亮 | SearchSourceBuilder.highlighter(HighlightBuilder) |
滚动查询 | 大数据量分页(Scroll API) | ScrollRequest + ClearScrollRequest |
异步查询 | 非阻塞方式执行查询 | SearchClient.searchAsync() |
索引操作:
使用Java RestClient实现如下操作:
PUT /hotel
{
"mappings": {
"properties": {
"id":{
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "ik_max_word",
"copy_to": "all"
},
"address": {
"type": "text"
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword",
"copy_to": "all"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "text",
"analyzer": "ik_max_word"
},
"location":{
"type": "geo_point",
"index": false
},
"pic":{
"type": "keyword",
"index": false
},
"all":{
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
GET /hotel
DELETE /hotel
PUT /hotel/_mapping
{
"properties":{
"age":{
"type": "integer"
}
}
}
初始化客户端
- 功能:初始化 Elasticsearch 客户端
- 关键 API:
RestHighLevelClient
构造函数RestClient.builder()
- 流程:
- 创建
RestHighLevelClient
实例 - 通过
RestClient.builder()
设置连接地址
- 创建
/**
* 创建客户端
*/
@BeforeEach
public void setUp() {
this.client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("192.168.***.***", 9200)
)
);
}
创建索引
- 功能:创建名为 “hotel” 的索引并设置映射
- 关键 API:
CreateIndexRequest
client.indices().create()
request.source()
- 流程:
- 创建索引创建请求
CreateIndexRequest
- 设置索引映射 JSON 作为请求体
- 调用
indices().create()
发送请求创建索引
- 创建索引创建请求
/**
* 创建索引
* 相当于发送
* PUT /hotel
* {
*
* }
* @throws IOException
*/
@Test
public void testCreateHotelIndex() throws IOException {
// 1.创建请求 相当于 PUT /hotel
CreateIndexRequest request = new CreateIndexRequest("hotel");
// 2.指定请求体
request.source(CreateHotelIndex, XContentType.JSON);
// 3.发送请求
client.indices().create(request, RequestOptions.DEFAULT);
}
查询索引
- 功能:查询 “hotel” 索引的映射信息
- 关键 API:
GetIndexRequest
indices().exists()
indices().get()
- 流程:
- 创建索引查询请求
GetIndexRequest
- 先检查索引是否存在
indices().exists()
- 若存在,调用
indices().get()
获取索引映射 - 解析并打印映射信息
- 创建索引查询请求
/**
* 查询索引
* GET /hotel
*/
@Test
public void testGetHotelIndex() throws IOException {
GetIndexRequest request = new GetIndexRequest("hotel");
IndicesClient http = client.indices();
if (!http.exists(request, RequestOptions.DEFAULT)) {
System.out.println("索引不存在");
return;
}
GetIndexResponse response = http.get(request, RequestOptions.DEFAULT);
Map<String, MappingMetadata> mappings = response.getMappings();
for (MappingMetadata mapping : mappings.values()) {
System.out.println(mapping.getSourceAsMap());
}
}
删除索引
- 功能:删除 “hotel” 索引
- 关键 API:
DeleteIndexRequest
client.indices().delete()
- 流程:
- 创建索引删除请求
DeleteIndexRequest
- 调用
indices().delete()
发送删除请求 - 根据响应
isAcknowledged()
判断是否删除成功
- 创建索引删除请求
/**
* 查询索引
* GET /hotel
*/
@Test
public void testGetHotelIndex() throws IOException {
GetIndexRequest request = new GetIndexRequest("hotel");
IndicesClient http = client.indices();
if (!http.exists(request, RequestOptions.DEFAULT)) {
System.out.println("索引不存在");
return;
}
GetIndexResponse response = http.get(request, RequestOptions.DEFAULT);
Map<String, MappingMetadata> mappings = response.getMappings();
for (MappingMetadata mapping : mappings.values()) {
System.out.println(mapping.getSourceAsMap());
}
}
新增字段
- 功能:更新 “hotel” 索引的映射(添加字段)
- 关键 API:
PutMappingRequest
client.indices().putMapping()
- 流程:
- 创建映射更新请求
PutMappingRequest
- 设置新增字段的 JSON 作为请求体
- 调用
indices().putMapping()
发送请求更新映射
- 创建映射更新请求
/**
* 修改索引(其实是添加字段)
* PUT /hotel/_mapping
* {
* "properties":{
* "age":{
* "type": "integer"
* }
* }
* }
*/
@Test
public void updateHotelIndex() throws IOException {
// 1.创建请求
PutMappingRequest request = new PutMappingRequest("hotel");
// 2.设置参数
request.source("{\n" +
" \"properties\": {\n" +
" \"age\": {\n" +
" \"type\": \"integer\"\n" +
" }\n" +
" }\n" +
"}", XContentType.JSON);
// 3. 发送请求更新 mapping
client.indices().putMapping(request, RequestOptions.DEFAULT);
}
释放资源
/**
* 释放资源
* @throws IOException
*/
@AfterEach
void tearDown() throws IOException {
this.client.close();
}
文档操作
IndexRequest
:增 / 全量更新DeleteRequest
:删除UpdateRequest
:增量更新GetRequest
:查询BulkRequest
:批量操作
初始化客户端
@BeforeEach
public void setUp() {
this.client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("192.168.140.138", 9200)
)
);
}
添加文档
- 功能:向 ES 添加单个文档
- 关键 API:
IndexRequest
:创建索引请求client.index()
:执行文档添加
- 流程:
- 从数据库获取酒店数据
- 转换为 ES 文档对象(HotelDoc)
- 转 JSON 字符串
- 创建
IndexRequest
并指定 ID - 设置 JSON 源并发送请求
/**
* 添加文档
* POST /索引库名/_doc/文档id
* {
* "字段1": "值1",
* "字段2": "值2",
* "字段3": "值3",
* "字段4": {
* "子属性1": "值4",
* "子属性2": "值5"
* }
* }
*/
@Test
public void testAddDocument() throws IOException {
// 1. 获得数据
Hotel hotel = hotelService.getById(45870);
// 2.封装成文档对象
HotelDoc hotelDoc = new HotelDoc(hotel);
// 3.转换成json对象
String json = JSON.toJSONString(hotelDoc);
// 1. 创建索引请求 相当于 /索引库名/_doc/文档id
IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
// 2. 指定文档内容
request.source(json, XContentType.JSON);
// 3. 发送请求
client.index(request, RequestOptions.DEFAULT);
}
删除文档
- 功能:删除 ES 中的文档
- 关键 API:
DeleteRequest
:创建删除请求client.delete()
:执行文档删除
- 流程:
- 创建
DeleteRequest
并指定索引和 ID - 发送删除请求
- 打印操作结果(Result)
- 创建
/**
* 删除文档
* DELETE /索引库名/_doc/文档id
*/
@Test
public void testDeleteDocument() throws IOException {
DeleteRequest request = new DeleteRequest("hotel", "45870");
DeleteResponse deleted = client.delete(request, RequestOptions.DEFAULT);
System.out.println(deleted.getResult());
}
修改文档
全量修改
- 功能:全量更新文档(覆盖方式)
- 关键 API:
IndexRequest
:本质是重新索引文档client.index()
:通过索引请求实现全量更新
- 流程:
- 修改酒店数据(如名称)
- 转换为文档对象并转 JSON
- 创建
IndexRequest
(同添加文档) - 发送请求(ES 会覆盖原有文档)
/**
* 全量修改
* PUT /索引库名/_doc/文档id
* {
* "字段1": "值1",
* "字段2": "值2",
* "字段3": "值3",
* "字段4": {
* "子属性1": "值4",
* "子属性2": "值5"
* }
* }
*/
@Test
public void testUpdateDocumentByAll() throws IOException {
// 1. 获得数据
Hotel hotel = hotelService.getById(45870);
hotel.setName("测试数据");
// 2.封装成文档对象
HotelDoc hotelDoc = new HotelDoc(hotel);
// 3.转换成json对象
String json = JSON.toJSONString(hotelDoc);
// 1. 创建索引请求 相当于 /索引库名/_doc/文档id
IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
// 2. 指定文档内容
request.source(json, XContentType.JSON);
// 3. 发送请求
client.index(request, RequestOptions.DEFAULT);
}
增量修改
- 功能:增量更新文档字段
- 关键 API:
UpdateRequest
:创建更新请求client.update()
:执行字段级更新
- 流程:
- 创建
UpdateRequest
并指定索引和 ID - 通过
doc()
方法设置需要更新的字段 - 发送请求实现增量更新
- 创建
/**
* 增量修改
* POST /索引库名/_update/文档id
* {
* "doc": {
* "字段": "新的字段值"
* }
* }
*/
@Test
public void testUpdateDocumentByField() throws IOException {
UpdateRequest request = new UpdateRequest("hotel", "45870");
request.doc(
"name","测试数据2"
);
client.update(request, RequestOptions.DEFAULT);
}
查询文档
- 功能:根据 ID 查询文档
- 关键 API:
GetRequest
:创建查询请求client.get()
:获取文档内容
- 流程:
- 创建
GetRequest
并指定索引和 ID - 先检查文档是否存在(
client.exists()
) - 若存在则获取文档并打印源数据
- 创建
/**
* 查询文档
* GET /索引库名/_doc/文档id
*/
@Test
public void testGetDocument() throws IOException {
GetRequest request = new GetRequest("hotel", "45870");
if (!client.exists(request, RequestOptions.DEFAULT)) {
System.out.println("文档不存在!");
return;
}
GetResponse response = client.get(request, RequestOptions.DEFAULT);
System.out.println(response.getSourceAsString());
}
释放资源
/**
* 释放资源
* @throws IOException
*/
@AfterEach
public void tearDown() throws IOException {
client.close();
}
批量导入
BulkRequest将数据库中的数据批量导入ES中
- 功能:批量导入大量文档
- 关键 API:
BulkRequest
:批量操作请求client.bulk()
:执行批量操作
- 流程:
- 从数据库获取所有酒店数据
- 创建
BulkRequest
- 循环添加
IndexRequest
(每个文档一个请求) - 发送批量请求一次性导入
/**
* 运用BulkRequest批量导入数据
*/
@Test
public void testBulkAddDocument() throws IOException {
List<Hotel> hotelList = hotelService.list();
BulkRequest request = new BulkRequest();
for (Hotel hotel : hotelList) {
HotelDoc hotelDoc = new HotelDoc(hotel);
request.add(new IndexRequest("hotel")
.id(hotel.getId().toString())
.source(JSON.toJSONString(hotelDoc), XContentType.JSON));
}
client.bulk(request, RequestOptions.DEFAULT);
}
查询操作
语法
1. 客户端初始化
方法名 | 功能描述 | 参数 | 返回类型 |
---|---|---|---|
RestHighLevelClient |
创建高级 REST 客户端 | RestClient.builder() |
客户端实例 |
close() |
关闭客户端 | 无 | void |
2. 文档查询操作
方法名 | 功能描述 | 参数 | 返回类型 |
---|---|---|---|
get() |
根据 ID 查询单个文档 | GetRequest(index, id) |
GetResponse |
search() |
搜索文档(核心查询方法) | SearchRequest(index) |
SearchResponse |
mget() |
批量查询多个文档 | MultiGetRequest |
MultiGetResponse |
3. 核心搜索 API
(SearchRequest)
方法名 | 功能描述 | 参数示例 | 返回类型 |
---|---|---|---|
source() |
设置查询字段 | source("field1", "field2") |
SearchRequest |
query() |
设置查询条件 | QueryBuilders.matchQuery("field", "value") |
SearchRequest |
from()/size() |
设置分页 | from(0), size(10) |
SearchRequest |
sort() |
设置排序 | SortBuilders.fieldSort("field").order(SortOrder.DESC) |
SearchRequest |
aggregation() |
设置聚合查询 | AggregationBuilders.terms("term_agg").field("field") |
SearchRequest |
execute() |
执行搜索 | RequestOptions.DEFAULT |
SearchResponse |
4. 常用查询
(QueryBuilders)
方法名 | 功能描述 | 参数示例 | 返回类型 |
---|---|---|---|
matchQuery() |
全文匹配查询 | field, text, operator |
QueryBuilder |
termQuery() |
精确词项查询 | field, value |
QueryBuilder |
rangeQuery() |
范围查询 | field, from, to |
QueryBuilder |
boolQuery() |
布尔组合查询 | must(), should(), mustNot() |
QueryBuilder |
wildcardQuery() |
通配符查询 | field, wildcardPattern |
QueryBuilder |
prefixQuery() |
前缀查询 | field, prefix |
QueryBuilder |
5. 聚合查询
(AggregationBuilders)
方法名 | 功能描述 | 参数示例 | 返回类型 |
---|---|---|---|
terms() |
术语聚合(分组统计) | name, field |
TermsAggregationBuilder |
avg() |
平均值聚合 | name, field |
AvgAggregationBuilder |
sum() |
求和聚合 | name, field |
SumAggregationBuilder |
dateHistogram() |
日期直方图聚合 | name, field, interval |
DateHistogramAggregationBuilder |
nested() |
嵌套字段聚合 | name, path, sub-aggregation |
NestedAggregationBuilder |
6. 高级查询功能
方法名 | 功能描述 | 参数示例 | 返回类型 |
---|---|---|---|
scroll() |
滚动查询(处理大数据量) | SearchRequest, TimeValue |
SearchResponse |
msearch() |
批量搜索 | MultiSearchRequest |
MultiSearchResponse |
suggest() |
自动补全 / 建议查询 | SuggestRequest, SuggestBuilder |
SuggestResponse |
初始化客户端
private RestHighLevelClient client;
@BeforeEach
public void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(
new HttpHost("192.168.***.***", 9200)
));
}
@AfterEach
public void tearDown() throws IOException {
this.client.close();
}
查询所有数据
- 功能:查询索引中所有文档
- 关键 API:
SearchRequest("hotel")
QueryBuilders.matchAllQuery()
client.search()
- 流程:
- 创建搜索请求,指定索引名
- 设置查询类型为
match_all
- 执行搜索并遍历结果
/**
* 查询所有
* GET /索引名/_search
* {
* "query": {
* "match_all": {}
* }
* }
*/
@Test
public void testSearchAll() throws IOException {
// 1.创建请求 相当于 /索引名/_search
SearchRequest request = new SearchRequest("hotel");
// 2.指定查询方式 相当于 { "query": { "match_all": {} } }
request.source().query(QueryBuilders.matchAllQuery());
// 3.发送请求 相当于 发送 get 请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> System.out.println(hit.getSourceAsMap()));
}
单字段查询
- 功能:单字段全文搜索
- 关键 API:
QueryBuilders.matchQuery("name", "上海")
- 流程:
- 指定字段名
name
和搜索词上海
- 执行全文搜索(分词匹配)
- 指定字段名
/**
* GET /索引/_search
* {
* "query": {
* "match": {
* "字段": "字段值"
* }
* }
* }
*/
@Test
public void testSearchBySingleField() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().query(QueryBuilders.matchQuery("name", "上海"));
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> System.out.println(hit.getSourceAsMap()));
}
多字段查询
- 功能:多字段全文搜索
- 关键 API:
QueryBuilders.multiMatchQuery("上海", "name", "city")
- 流程:
- 在
name
和city
字段中同时搜索上海
- 合并匹配结果
- 在
/**
* 多字段查询
* GET /索引名/_search
* {
* "query": {
* "multi_match": {
* "query": "搜索内容",
* "fields": ["字段1","字段2","字段3",...]
* }
* }
* }
*/
@Test
public void testSearchByMultiField() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().query(QueryBuilders.multiMatchQuery("上海", "name", "city"));
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> System.out.println(hit.getSourceAsMap()));
}
精确查询
- 功能:精确查询(不分词)
- 关键 API:
QueryBuilders.termQuery("city", "上海")
- 流程:
- 对
city
字段进行精确匹配 - 仅返回值为
上海
的文档
- 对
/**
* 精准查询
* GET /索引名/_search
* {
* "query": {
* "term": {
* "字段": {
* "value": "字段值"
* }
* }
* }
* }
*/
@Test
public void testTermSearch() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().query(QueryBuilders.termQuery("city", "上海"));
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> System.out.println(hit.getSourceAsMap()));
}
范围查询
- 功能:范围查询
- 关键 API:
QueryBuilders.rangeQuery("price").gte(100).lte(200)
- 流程:
- 筛选
price
字段在 100-200 之间的文档
- 筛选
/**
* 范围查询
* GET /索引名/_search
* {
* "query": {
* "range": {
* "字段": {
* "gte": minv,
* "lte": maxv
* }
* }
* }
* }
*/
@Test
public void testRangeSearch() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().query(QueryBuilders.rangeQuery("price").gte(100).lte(200));
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> System.out.println(hit.getSourceAsMap()));
}
矩形范围查询
- 功能:矩形范围地理位置查询
- 关键 API:
QueryBuilders.geoBoundingBoxQuery("location")
- 流程:
- 指定矩形区域(左上角和右下角坐标)
- 返回
location
字段落在区域内的文档
/**
* 矩形范围查询
* GET /索引名/_search
* {
* "query": {
* "geo_bounding_box": {
* "字段": {
* "top_left": {
* "lat": 40.73,
* "lon": 120.0
* },
* "bottom_right": {
* "lat": 30.01,
* "lon": 150.0
* }
* }
* }
* }
* }
*/
@Test
public void testGeoBoxSearch() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().query(QueryBuilders.geoBoundingBoxQuery("location")
.setCorners(40.73, 120.0, 30.01, 150.0));
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> System.out.println(hit.getSourceAsMap()));
}
圆形范围查询
- 功能:圆形范围地理位置查询
- 关键 API:
QueryBuilders.geoDistanceQuery("location")
- 流程:
- 指定中心点坐标和距离半径(200km)
- 返回距离中心点 200km 内的文档
/**
* 圆形范围查询
* GET /索引名/_search
* {
* "query": {
* "geo_distance": {
* "distance": "200km",
* "location": {
* "lat": 40,
* "lon": 200
* }
* }
* }
* }
*/
@Test
public void testGeoDistanceSearch() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().query(QueryBuilders.geoDistanceQuery("location")
.point(40, 200)
.distance(200, DistanceUnit.KILOMETERS));
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> System.out.println(hit.getSourceAsMap()));
}
复杂查询
- 功能:算分控制查询
- 关键 API:
QueryBuilders.functionScoreQuery()
- 流程:
- 基础查询:品牌为
如家
- 过滤条件:城市为
上海
- 权重调整:符合过滤条件的文档得分 +10
- 算分模式:SUM(叠加权重)
- 基础查询:品牌为
/**
* 复杂查询 --- 算分控制
* GET /索引名/_search
* {
* "query": {
* "function_score": {
* "query": {
* "查询方式": {
*
* }
* },
* "functions": [
* {
* "filter": {},
* "weight": 10
* }
* ],
* "boost_mode": "sum"
* }
* }
* }
*/
@Test
public void testComplexSearch() throws IOException {
// 1.构建请求 /索引名/_search
SearchRequest request = new SearchRequest("hotel");
// 2.构建请求体
// 2.1 基础查询 query{}
MatchQueryBuilder query = QueryBuilders.matchQuery("brand", "如家");
// 2.2 构建functions ScoreFunctionBuilders
// 构建过滤器 filter{}
MatchQueryBuilder filter = QueryBuilders.matchQuery("city", "上海");
// 权重函数 weight{}
WeightBuilder weight = ScoreFunctionBuilders.weightFactorFunction(10);
// 结合生成 functions{}
FunctionScoreQueryBuilder.FilterFunctionBuilder functions = new FunctionScoreQueryBuilder.FilterFunctionBuilder(filter, weight);
// 2.3 指定boost_mode
FunctionScoreQueryBuilder scoreQuery = QueryBuilders.functionScoreQuery(query,
new FunctionScoreQueryBuilder
.FilterFunctionBuilder[]{functions})
.boostMode(CombineFunction.SUM);
// 2.4 构建请求 /索引名/_search {}
request.source().query(scoreQuery);
// 3. 发起请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit-> System.out.println(hit.getSourceAsMap()));
}
布尔查询
- 功能:布尔组合查询
- 关键 API:
BoolQueryBuilder.must()
BoolQueryBuilder.should()
BoolQueryBuilder.mustNot()
BoolQueryBuilder.filter()
- 流程:
must
:城市必须为上海
should
:品牌为如家
或汉庭
(提高匹配度)mustNot
:价格不得 >= 500filter
:星级必须为二钻
(不参与算分
/**
* 布尔查询
* GET /索引/_search
* {
* "query": {
* "bool": {
* "must": [],
* "should": [],
* "must_not": [],
* "filter": []
* }
* }
* }
*/
@Test
public void testBooleanQuery() throws IOException {
// 1. 创建请求
SearchRequest request = new SearchRequest("hotel");
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 2. 添加查询条件
// 2.1 添加must条件
boolQuery.must(QueryBuilders.termQuery("city", "上海"));
// 2.2 添加should条件
boolQuery.should(QueryBuilders.multiMatchQuery("brand", "如家"));
boolQuery.should(QueryBuilders.multiMatchQuery("brand", "汉庭"));
// 2.3 添加must_not条件
boolQuery.mustNot(QueryBuilders.rangeQuery("price").gte(500));
// 2.4 添加filter条件
boolQuery.filter(QueryBuilders.termQuery("starName", "二钻"));
// 3. 设置请求
request.source().query(boolQuery);
// 4. 发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> System.out.println(hit.getSourceAsMap()));
}
排序
- 功能:结果排序
- 关键 API:
SearchSourceBuilder.sort()
- 流程:
- 先按
price
升序排序 - 价格相同的按
score
降序排序 - 返回前 20 条结果
- 先按
/**
* 对搜索结果进行排序
* GET /hotel/_search
* {
* "query": {
* "match_all": {}
* },
* "sort": [
* {"字段": {"order": "desc"}}
* ]
* }
*/
@Test
public void testSortResult() throws IOException {
// 1.构建请求
SearchRequest request = new SearchRequest("hotel");
// 2.创建请求体
SearchSourceBuilder sortQuery = new SearchSourceBuilder();
// 相当于 query{"match_all":{}}
sortQuery.query(QueryBuilders.matchAllQuery());
sortQuery.sort(new FieldSortBuilder("price").order(SortOrder.ASC));
sortQuery.sort(new FieldSortBuilder("score").order(SortOrder.DESC));
sortQuery.size(20); // 设置结果数量
request.source(sortQuery);
// 3.发起请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> System.out.println(hit.getSourceAsMap()));
}
分页
基础分页
- 功能:基础分页(from/size)
- 关键 API:
SearchSourceBuilder.from(0).size(15)
- 流程:
- 从第 0 条记录开始
- 返回 15 条结果
- 适合浅分页(from 值较小)
/**
* 基础分页查询 --- from/size 参数
* GET /索引名/_search
* {
* "query": {
* "搜索方式": {}
* },
* "from": 10,
* "size": 10,
* "sort": []
* }
*/
@Test
public void testBasePage() throws IOException {
// 1.创建请求
SearchRequest request = new SearchRequest("hotel");
// 2.创建请求体
SearchSourceBuilder query = new SearchSourceBuilder();
query.query(QueryBuilders.matchAllQuery());
query.from(0).size(15);
query.sort(new FieldSortBuilder("price").order(SortOrder.ASC));
request.source(query);
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> System.out.println(hit.getSourceAsMap()));
}
深度分页
- 功能:深度分页(Scroll API)
- 关键 API:
request.scroll(TimeValue.timeValueMinutes(1))
SearchScrollRequest
- 流程:
- 初始化滚动会话,设置超时时间
- 通过 scroll_id 持续获取下一页
- 适合大数据量导出(不适合实时交互)
/**
* 深度分页
* GET /_search/scroll
* {
* "scroll": "1m",
* "scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFlY4R0RUTjVvVGhhc2hWYTQyakx4LXcAAAAAAAAULRZWSjV5VExtRlJNZTg5ekxoU2RuMHh3"
* }
*/
@Test
public void testScrollPage() throws IOException {
// 1. 创建第一次查询请求
SearchRequest request = new SearchRequest("hotel");
// 2. 创建SearchSourceBuilder对象,用于构建查询源
SearchSourceBuilder query = new SearchSourceBuilder();
query.query(QueryBuilders.matchAllQuery());
query.size(2);
request.scroll(TimeValue.timeValueMinutes(1));
request.source(query);
// 3.执行搜索请求并获取响应
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 获取滚动查询的ID
String scrollId = response.getScrollId();
// 获取查询结果中的文档数组
SearchHit[] hits = response.getHits().getHits();
// 初始化页码为1
int pageNum = 1;
// 打印第一页的数据
System.out.println("第" + pageNum + "页:");
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsMap());
}
System.out.println();
// 4.循环滚动查询,直到没有更多结果
while (hits != null && hits.length > 0) {
// 4.1 创建滚动查询请求对象
SearchScrollRequest scrollRequest = new SearchScrollRequest("hotel");
scrollRequest.scrollId(scrollId);
scrollRequest.scroll(TimeValue.timeValueMinutes(1));
// 4.2 执行滚动查询并获取响应
response = client.scroll(scrollRequest, RequestOptions.DEFAULT);
hits = response.getHits().getHits();
// 页码递增
pageNum++;
// 打印当前页的数据
System.out.println("第" + pageNum + "页:");
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsMap());
}
// 更新滚动查询的ID
scrollId = response.getScrollId();
System.out.println();
}
}
实时分页
- 功能:实时分页(search_after)
- 关键 API:
SearchSourceBuilder.searchAfter()
hit.getSortValues()
- 流程:
- 基于排序值(价格、评分、ID)实现无偏移分页
- 每页使用上一页最后文档的排序值作为起点
- 适合实时用户交互场景
/**
* 实时分页查询 --- search_after 参数
* GET /hotel/_search
* {
* "size": 2,
* "query": {
* "match": {
* "all": "如家"
* }
* },
* "sort": [
* {"price": "asc"},
* {"score": "desc"}
* ],
* "search_after": [135,45]
* }
*/
@Test
public void testTimePage() throws IOException {
// 创建搜索请求并指定索引
SearchRequest request = new SearchRequest("hotel");
SearchSourceBuilder query = new SearchSourceBuilder();
// 设置查询条件:搜索所有字段中包含"如家"的文档
query.query(QueryBuilders.matchQuery("all", "如家"));
// 设置排序规则(必须包含至少一个唯一字段以确保结果稳定)
// 1. 按价格升序排序
query.sort(new FieldSortBuilder("price").order(SortOrder.ASC));
// 2. 价格相同的情况下按评分降序排序
query.sort(new FieldSortBuilder("score").order(SortOrder.DESC));
// 3. 最终按文档ID升序排序(确保唯一性)
query.sort(new FieldSortBuilder("_id").order(SortOrder.ASC));
// 设置每页大小(每次查询返回的文档数量)
query.size(2);
// 用于存储上一页最后一个文档的排序值,作为下一页的起始点
Object[] searchAfter = null;
// 页码计数器
int pageNum = 1;
// 循环分页查询,直到没有更多结果
do {
// 非第一页需要设置 search_after 参数
if (searchAfter != null) {
// 设置 search_after 为上一页最后一个文档的排序值
query.searchAfter(searchAfter);
}
// 将查询配置应用到请求中
request.source(query);
// 执行搜索请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 获取搜索结果文档数组
SearchHit[] hits = response.getHits().getHits();
// 如果没有结果,退出循环
if (hits.length == 0) {
break;
}
// 更新 search_after 为当前页最后一个文档的排序值,用于下一页查询
searchAfter = hits[hits.length - 1].getSortValues();
// 增加页码计数
pageNum++;
// 打印当前页的所有文档
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsMap());
}
// 分页间隔空行
System.out.println();
} while (true); // 持续循环直到没有更多结果
}
高亮
- 功能:搜索结果高亮
- 关键 API:
HighlightBuilder()
- 流程:
- 对
name
字段中匹配的如家
进行高亮 - 使用
<em>
标签包裹高亮内容 - 从结果中提取高亮片段并打印
- 对
/**
* 对搜索结果高亮处理
* GET /索引名/_search
* {
* "query": {
* "match": {
* "字段": "查询字段值" // 查询字段
* }
* },
* "highlight": {
* "fields": {
* "字段": {} // 指定要高亮的字段
* }
* }
* }
* @throws IOException
*/
@Test
public void testHighlight() throws IOException {
SearchRequest request = new SearchRequest("hotel");
SearchSourceBuilder query = new SearchSourceBuilder();
query.query(QueryBuilders.matchQuery("name", "如家"));
query.highlighter(new HighlightBuilder().field("name").requireFieldMatch(false).preTags("<em>").postTags("</em>"));
request.source(query);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
response.getHits().forEach(hit -> {
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
HighlightField name = highlightFields.get("name");
System.out.println(name.getFragments()[0].string());
});
}
桶聚合
- 作用:将文档 “分组” 到不同的桶中,每个桶满足特定条件(如字段值、范围等)。
- 核心逻辑:类似 SQL 的
GROUP BY
,但更灵活。 - 常用类型:
terms
:按字段值分组(如按城市、标签分组)。range
:按数值范围分组(如价格区间 [0-100]、[100-200])。date_range
:按时间范围分组(如按月份、季度分组)。filters
:按多个过滤条件分组。
- 应用场景:统计不同城市的酒店数量、按价格区间划分商品销量等。
/**
* 桶聚合
* GET /hotel/_search
* {
* "aggs": {
* "聚合名称": {
* "桶聚合类型": {
* "参数": 值,
* ...
* },
* "aggs": { // 嵌套聚合(可多层)
* "子聚合名称": { ... }
* }
* }
* }
* }
*/
@Test
public void testBucketAggregation() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source().size(0);
request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(5));
request.source().sort("price", SortOrder.DESC);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 正确获取并类型转换为 Terms 聚合
Aggregations aggregations = response.getAggregations();
Terms brandAgg = aggregations.get("brandAgg"); // 直接返回 Terms 类型
if (brandAgg != null) {
brandAgg.getBuckets().forEach(bucket -> {
System.out.println("桶名称:" + bucket.getKey() + " | 文档数:" + bucket.getDocCount());
});
if (brandAgg.getMetadata() != null) {
brandAgg.getMetadata().forEach((k, v) -> System.out.println(k + ":" + v));
}
} else {
System.out.println("brandAgg 为空,请确认索引和字段是否正确");
}
}
自动补全
/**
* 根据前缀关键词获取酒店自动补全建议
*
* @param key 搜索关键词前缀
* @return 包含自动补全建议的字符串列表
* @throws RuntimeException 当搜索请求执行失败时抛出
*
* 工作流程:
* 1. 创建针对hotel索引的搜索请求
* 2. 配置Completion Suggester:
* - 使用字段suggestion的完成建议器
* - 设置搜索前缀为用户输入的关键词
* - 启用重复项过滤
* - 限制返回结果数量为10条
* 3. 执行搜索请求并解析返回结果
* 4. 提取建议文本并返回列表
*/
@Override
public List<String> suggestions(String key){
SearchRequest request = new SearchRequest("hotel");
request.source().suggest(new SuggestBuilder().addSuggestion(
"suggestions",
SuggestBuilders.completionSuggestion("suggestion")
.prefix(key)
.skipDuplicates(true)
.size(10)
));
List<String> list = new ArrayList<>();
try {
SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
Suggest suggest = response.getSuggest();
CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();
for (CompletionSuggestion.Entry.Option option : options) {
String text = option.getText().toString();
list.add(text);
}
System.out.println(list);
} catch (IOException e) {
throw new RuntimeException(e);
}
return list;
}
/**
* 从聚合结果中提取指定名称的聚合桶键列表
*
* @param aggregations 聚合结果对象
* @param aggName 目标聚合名称
* @return 包含聚合桶键的字符串列表
*
* 工作流程:
* 1. 根据聚合名称从聚合结果中获取Terms聚合
* 2. 提取聚合中的所有桶(buckets)
* 3. 遍历每个桶并提取键值
* 4. 将所有键值收集到列表中返回
*/
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> list = new ArrayList<>();
for (Terms.Bucket bucket : buckets) {
// 4.4.获取key
String key = bucket.getKeyAsString();
list.add(key);
}
return list;
}
如有错误,欢迎指正!!!