ElasticStack
日志注意事项
- 日志颗粒度:(级别:DEBUG、INFO、WARNING、ERROR等)
- 日志格式化:方便阅读分析(时间戳、日志级别、请求ID、模块名称、错误信息等,统一使用Log4j、logback等)
- 异常处理:(捕获异常错误信息、堆栈跟踪等,以便于日后排查)
- 日志存储和管理:输出到文件、数据库、日志服务器等,确保合理的日志轮转和备份策略,有效的日志管理和监控机制。
- 敏感信息保护:在日志输出时,注意敏感信息的处理,如密码、个人身份信息等,应进行脱敏处理或者在合适的级别下去除。
- 日志审计和安全:对于敏感操作或重要日志事件,可以考虑加入审计日志,记录相关的操作细节和安全事件,以便追踪和监控系统的安全性。
- 性能影响:日志输出可能对系统的性能产生一定的影响,要谨慎选择日志输出的频率和内容,避免频繁的输出大量冗余信息。
- 异步日志:尽量采用异步方式进行,避免阻塞主线程,可以利用线程池等机制一部处理日志消息。(Logback默认情况下是同步输出日志,可以调整配置)
SpringBoot3.X整合logback配置示例
SLF4J(Simple Logging Facade for Java)
- 把不同的日志系统实现了具体的抽象化,提供统一的日志使用接口
- 具体的日志系统实现有:log4j、logback
- logback、log4j是一个作者,logback有更好的特性,可以取代log4,是slf4j的原生实现日志系统
- log4j、logback可以单独的使用,也可以绑定slf4j一起使用
- 编码规范不建议直接使用log4j、logback。sl4j,日后更换框架所带来的成本会很低
src/main/resources/logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志路径 -->
<property name="LOG_HOME" value="C:\\Users\\chao1\\Desktop\\tmp\\log"/>
<!-- 打印到控制台 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 打印到日志文件 -->
<appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/soulboy.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天回滚 daily -->
<fileNamePattern>${LOG_HOME}/soulboy.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 保留30天的日志 -->
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 指定某个类单独打印日志 -->
<logger name="net.xdclass.controller.ProductController" level="INFO" additivity="false">"
<appender-ref ref="console"/>
<appender-ref ref="rollingFile"/>
</logger>
<!-- 其他所有类打印日志的 -->
<root level="INFO" additivity="false">
<appender-ref ref="rollingFile"/>
<appender-ref ref="console"/>
</root>
</configuration>
Elastic Stack
核心产品包括 Elasticsearch、Kibana、Beats 和 Logstash(也称为 ELK Stack)等等。能够安全可靠地从任何来源获取任何格式的数据,然后对数据进行搜索、分析和可视化。
ELK = Elasticsearch + Logstash + Kibana, 随着 Beats 的加入,原本的 ELK 体系变为了 Elastic Stack, Elastic Stack = Elasticsearch + Logstash + Kibana + Beats。
Elasticsearch
一个分布式实时艘索和分析引擎,高性能和可伸缩性闻名,能够快速地存储、搜索和分析大量结构化和非结构化数据基于java开发,它是Elastic Stack的核心组件,提供分布式数据存储和搜索能力。
Logstash
是一个用于数据收集、转换和传输的数据处理引擎,支持从各种来源(如文件、日志、数据库等)收集数据。基于java开发,并对数据进行结构化、过滤和转换,然后将数据发送到Elasticsearch等目标存储或分析系统。
Kibana
基于node.js开发,数据可视化和仪表盘工具,连接到Elasticsearch,通过简单易用的用户界面创建各种图表、图形和仪表盘帮助用户快速探索和理解数据,并进行强大的数据分析和可视化。
Beats
是轻量级的数据收集器,用于收集和发送各种类型的数据到Elasticsearch或Logstash,Beats提供了多种插件和模块,用于收集和传输日志、指标数据、网络数据和安全数据等。
如果只是单纯的为了收集日志,使用logstash就有点大材小用了,另外有点浪费资源而beats是轻量级的用来收集日志的,而logstash更加专注一件事,那就是数据转换,格式化,等处理工作
JDK17安装
Linux服务器安装JDK17+ElasticSearch8.X,Elasticsearch是使用Java开发的,8.1版本的ES需要JDK17及以上版本
### 解压
[root@localhost /]# mkdir -pv /usr/local/software/elk/jdk17
[root@localhost tmp]# tar -zxvf jdk-17_linux-x64_bin.tar.gz
[root@localhost tmp]# mv jdk-17.0.7 /usr/local/software/elk/jdk17
### 配置环境变量
[root@localhost tmp]# vim /etc/profile
JAVA_HOME=/usr/local/software/elk/jdk17
CLASSPATH=$JAVA_HOME/lib/
PATH=$PATH:$JAVA_HOME/bin
export PATH JAVA_HOME CLASSPATH
[root@localhost tmp]# source /etc/profile
[root@localhost tmp]# java -version
java version "17.0.7" 2023-04-18 LTS
安装ElasticSearc8.X
### 解压
[root@localhost tmp]# tar -zxvf elasticsearch-8.4.1-linux-x86_64.tar.gz
[root@localhost tmp]# mv elasticsearch-8.4.1 /usr/local/software/elk/
### 新建一个用户,安全考虑,elasticsearch默认不允许以root账号运行
创建用户:useradd es_user
设置密码:passwd es_user 12345678
[root@localhost tmp]# chgrp -R es_user /usr/local/software/elk/elasticsearch-8.4.1
[root@localhost tmp]# chown -R es_user /usr/local/software/elk/elasticsearch-8.4.1
[root@localhost tmp]# chmod -R 777 /usr/local/software/elk/elasticsearch-8.4.1
### 修改文件和进程最大打开数,需要root用户,如果系统本身有这个文件最大打开数和进程最大打开数配置,则不用
# 在文件内容最后添加后面两行(切记*不能省略)
[root@localhost tmp]# vim /etc/security/limits.conf
* soft nofile 65536
* hard nofile 65536
### 修改虚拟内存空间,默认太小
# 在配置文件中改配置 最后一行上加上
[root@localhost tmp]# vim /etc/sysctl.conf
vm.max_map_count=262144
#执行 sysctl -p(立即生效)
[root@localhost tmp]# sysctl -p
vm.max_map_count = 262144
### 修改elasticsearch的JVM内存,机器内存不足,常规线上推荐16到24G内存
[root@localhost tmp]# vim /usr/local/software/elk/elasticsearch-8.4.1/config/jvm.options
-Xms1g
-Xmx1g
### 修改 elasticsearch相关配置
[root@localhost tmp]# vim /usr/local/software/elk/elasticsearch-8.4.1/config/elasticsearch.yml
cluster.name: my-application
node.name: node-1
path.data: /usr/local/software/elk/elasticsearch-8.4.1/data
path.logs: /usr/local/software/elk/elasticsearch-8.4.1/logs
network.host: 0.0.0.0
http.port: 9200
cluster.initial_master_nodes: ["node-1"]
xpack.security.enabled: false
xpack.security.enrollment.enabled: false
ingest.geoip.downloader.enabled: false
配置说明
cluster.name: 指定Elasticsearch集群的名称。所有具有相同集群名称的节点将组成一个集群。
node.name: 指定节点的名称。每个节点在集群中应该具有唯一的名称。
path.data: 指定用于存储Elasticsearch索引数据的路径。
path.logs: 指定Elasticsearch日志文件的存储路径。
network.host: 指定节点监听的网络接口地址。0.0.0.0表示监听所有可用的网络接口,开启远程访问连接
http.port: 指定节点上的HTTP服务监听的端口号。默认情况下,Elasticsearch的HTTP端口是9200。
cluster.initial_master_nodes: 指定在启动集群时作为初始主节点的节点名称。
xpack.security.enabled: 指定是否启用Elasticsearch的安全特性。在这里它被禁用(false),意味着不使用安全功能。
xpack.security.enrollment.enabled: 指定是否启用Elasticsearch的安全认证和密钥管理特性。在这里它被禁用(false)。
ingest.geoip.downloader.enabled: 指定是否启用GeoIP数据库下载功能。在这里它被禁用(false)
### 启动ElasticSearch
# 切换到es_user用户启动, 进入bin目录下启动, &为后台启动,再次提示es消息时 Ctrl + c 跳出
[es_user@localhost bin]$ su es_user
[es_user@localhost bin]$ cd /usr/local/software/elk/elasticsearch-8.4.1/bin
[es_user@localhost bin]$ ./elasticsearch &
[es_user@localhost bin]$ su root
Password:
[root@localhost bin]# yum install -y lsof
[root@localhost bin]# lsof -i:9200
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 3649 es_user 408u IPv6 94785 0t0 TCP *:wap-wsp (LISTEN)
### 常见命令,可以用postman访问(网络安全组记得开发端口)
#查看集群健康情况
http://192.168.10.68:9200/_cluster/health
#查看分片情况
http://192.168.10.68:9200/_cat/shards?v=true&pretty
#查看节点分布情况
http://192.168.10.68:9200/_cat/nodes?v=true&pretty
#查看索引列表
http://192.168.10.68:9200/_cat/indices?v=true&pretty
安装Kibana8.X
### 解压
[root@localhost tmp]# tar -zxvf kibana-8.4.1-linux-x86_64.tar.gz
[root@localhost tmp]# mv kibana-8.4.1 /usr/local/software/elk/
### 授权
[root@localhost tmp]# chgrp -R es_user /usr/local/software/elk/kibana-8.4.1
[root@localhost tmp]# chown -R es_user /usr/local/software/elk/kibana-8.4.1
[root@localhost tmp]# chmod -R 777 /usr/local/software/elk/kibana-8.4.1
### 修改配置文件
[root@localhost tmp]# vim /usr/local/software/elk/kibana-8.4.1/config/kibana.yml
server.port: 5601
server.host: "0.0.0.0"
elasticsearch.hosts: ["http://192.168.10.68:9200"]
i18n.locale: "zh-CN" #汉化
### 启动
[root@localhost tmp]# su es_user
[es_user@localhost tmp]$ cd /usr/local/software/elk/kibana-8.4.1/bin/
[es_user@localhost bin]$ ./kibana &
### 访问
http://192.168.10.68:5601
#查看集群健康情况
GET /_cluster/health
#查看分片情况
GET /_cat/shards?v=true&pretty
#查看节点分布情况
GET /_cat/nodes?v=true&pretty
#查看索引列表
GET /_cat/indices?v=true&pretty
# 控制台
http://192.168.10.68:5601/app/dev_tools#/console
logstash->Elasticsearch->kibana
### 解压
[root@localhost tmp]# tar -zxvf logstash-8.4.1-linux-x86_64.tar.gz
[root@localhost tmp]# mv logstash-8.4.1 /usr/local/software/elk/
### 准备nginx日志文件
[root@localhost tmp]# mkdir /usr/local/software/elk/log
[root@localhost tmp]# touch /usr/local/software/elk/log/access.log
[root@localhost tmp]# vim /usr/local/software/elk/log/access.log
2023/12/15 14:39:29 [error] 29#29: *465 connect() failed (111: Connection refused) while connecting to upstream, client: 10.42.0.1, server: alipaytest.ddns.net, request: "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1", upstream: "http://192.168.10.28:8888/coupon-server/api/coupon/v1/page_coupon", host: "182.112.39.117:8088"
10.42.0.1 - - [15/Dec/2023:14:39:29 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2110 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:39:30 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 59 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:39:32 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2110 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:40:07 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2110 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:41:11 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2118 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
2023/12/15 14:41:18 [error] 29#29: *476 connect() failed (111: Connection refused) while connecting to upstream, client: 10.42.0.1, server: alipaytest.ddns.net, request: "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1", upstream: "http://192.168.10.28:8888/coupon-server/api/coupon/v1/page_coupon", host: "182.112.39.117:8088"
10.42.0.1 - - [15/Dec/2023:14:41:18 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2110 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:41:18 +0000] "GET /favicon.ico HTTP/1.1" 404 284 "http://182.112.39.117:8088/coupon-server/api/coupon/v1/page_coupon" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:42:39 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2118 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:42:41 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2110 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
2023/12/15 14:42:41 [error] 29#29: *485 connect() failed (111: Connection refused) while connecting to upstream, client: 10.42.0.1, server: alipaytest.ddns.net, request: "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1", upstream: "http://192.168.10.28:8888/coupon-server/api/coupon/v1/page_coupon", host: "182.112.39.117:8088"
10.42.0.1 - - [15/Dec/2023:14:42:41 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 59 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:42:47 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2110 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:42:48 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 59 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:42:48 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2110 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:42:59 +0000] "GET /favicon.ico HTTP/1.1" 404 284 "http://182.112.39.117:8088/coupon-server/api/coupon/v1/page_coupon" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:43:01 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2110 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
2023/12/15 14:43:01 [error] 29#29: *498 connect() failed (111: Connection refused) while connecting to upstream, client: 10.42.0.1, server: alipaytest.ddns.net, request: "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1", upstream: "http://192.168.10.28:8888/coupon-server/api/coupon/v1/page_coupon", host: "182.112.39.117:8088"
10.42.0.1 - - [15/Dec/2023:14:43:01 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 59 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:43:02 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2117 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:43:02 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 59 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:43:02 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 59 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:43:03 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2110 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:43:33 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2110 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:43:35 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2117 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
2023/12/15 14:43:37 [error] 29#29: *513 connect() failed (111: Connection refused) while connecting to upstream, client: 10.42.0.1, server: alipaytest.ddns.net, request: "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1", upstream: "http://192.168.10.28:8888/coupon-server/api/coupon/v1/page_coupon", host: "182.112.39.117:8088"
10.42.0.1 - - [15/Dec/2023:14:43:37 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2110 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:43:38 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 59 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:43:40 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2125 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:44:16 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2110 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:44:39 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2110 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
2023/12/15 14:44:39 [error] 29#29: *524 connect() failed (111: Connection refused) while connecting to upstream, client: 10.42.0.1, server: alipaytest.ddns.net, request: "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1", upstream: "http://192.168.10.28:8888/coupon-server/api/coupon/v1/page_coupon", host: "182.112.39.117:8088"
10.42.0.1 - - [15/Dec/2023:14:44:39 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2110 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:45:26 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2118 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
10.42.0.1 - - [15/Dec/2023:14:45:41 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2110 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
2023/12/15 14:45:43 [error] 29#29: *531 connect() failed (111: Connection refused) while connecting to upstream, client: 10.42.0.1, server: alipaytest.ddns.net, request: "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1", upstream: "http://192.168.10.28:8888/coupon-server/api/coupon/v1/page_coupon", host: "182.112.39.117:8088"
10.42.0.1 - - [15/Dec/2023:14:45:43 +0000] "GET /coupon-server/api/coupon/v1/page_coupon HTTP/1.1" 200 2110 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)
### 修改配置文件
[root@localhost bin]# vim /usr/local/software/elk/logstash-8.4.1/config/test-logstash.conf
input {
file {
path => "/usr/local/software/elk/log/access.log"
start_position => "beginning"
stat_interval => "3"
type => "nginx-access-log"
}
}
output {
if [type] == "nginx-access-log" {
elasticsearch {
hosts => ["http://192.168.10.68:9200"]
index => "nginx-accesslog-%{+YYYY.MM.dd}"
}
}
}
### 启动
[root@localhost bin]# cd /usr/local/software/elk/logstash-8.4.1/bin/
[root@localhost bin]# ./logstash -f /usr/local/software/elk/logstash-8.4.1/config/test-logstash.conf &
FileBeat->[logstash]->Elasticsearch->kibana
### 解压
[root@localhost tmp]# tar -zxvf filebeat-8.4.1-linux-x86_64.tar.gz
[root@localhost tmp]# mv filebeat-8.4.1-linux-x86_64 /usr/local/software/elk/
[root@localhost tmp]# touch /usr/local/software/elk/filebeat-8.4.1-linux-x86_64/test-filebeat.yml
### 修改filebeat的配置文件
[root@localhost tmp]# vim /usr/local/software/elk/filebeat-8.4.1-linux-x86_64/test-filebeat.yml
filebeat.inputs:
- type: log
paths: ["/tmp/access.log*"]
encoding: UTF-8
fields:
service: nginx-access-log
output.logstash:
hosts: ["192.168.10.68:5044"]
### 修改logstash的配置文件
[root@localhost tmp]# touch /usr/local/software/elk/logstash-8.4.1/config/test-beat-logstash.conf
[root@localhost tmp]# vim /usr/local/software/elk/logstash-8.4.1/config/test-beat-logstash.conf
input {
beats {
port => 5044
}
}
output {
if [type] == "nginx-access-log" {
elasticsearch {
hosts => ["http://192.168.10.68:9200"]
index => "nginx-accesslog-%{+YYYY.MM.dd}"
}
}
if [fields][service] == "nginx-access-log" {
elasticsearch {
hosts => ["http://192.168.10.68:9200"]
index => "beat-nginx-accesslog-%{+YYYY.MM.dd}"
}
}
}
### 启动logstash
[root@localhost tmp]# cd /usr/local/software/elk/logstash-8.4.1/bin/
[root@localhost bin]# ./logstash -f /usr/local/software/elk/logstash-8.4.1/config/test-beat-logstash.conf &
[root@localhost bin]# lsof -i:5044
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 2224 root 96u IPv6 22858 0t0 TCP *:lxi-evntsvc (LISTEN)
### 启动filebeat
[root@localhost bin]# cd /usr/local/software/elk/filebeat-8.4.1-linux-x86_64/
[root@localhost filebeat-8.4.1-linux-x86_64]# ./filebeat -e -c test-filebeat.yml &
### 查找进程并且kill掉
[root@localhost bin]# ps -ef | grep filebeat
多服务日志收集于存储配置
logstash配置
###
[root@localhost tmp]# cat /usr/local/software/elk/logstash-8.4.1/config/test-beat-logstash.conf
input {
beats {
port => 5044
}
}
output {
if [type] == "nginx-access-log" {
elasticsearch {
hosts => ["http://192.168.10.68:9200"]
index => "nginx-accesslog-%{+YYYY.MM.dd}"
}
}
if [fields][service] == "nginx-access-log" {
elasticsearch {
hosts => ["http://192.168.10.68:9200"]
index => "beat-nginx-accesslog-%{+YYYY.MM.dd}"
}
}
if [fields][service] == "solo-access-log" {
elasticsearch {
hosts => ["http://192.168.10.68:9200"]
index => "solo-nginx-accesslog-%{+YYYY.MM.dd}"
}
}
if [fields][service] == "abc1024-access-log" {
elasticsearch {
hosts => ["http://192.168.10.68:9200"]
index => "abc1024-nginx-accesslog-%{+YYYY.MM.dd}"
}
}
}
filebeat配置
[root@localhost tmp]# cat /usr/local/software/elk/filebeat-8.4.1-linux-x86_64/test-filebeat.yml
filebeat.inputs:
- type: log
paths: ["/tmp/access.log"]
encoding: UTF-8
fields:
service: nginx-access-log
- type: log
paths: ["/tmp/solo_access.log"]
encoding: UTF-8
fields:
service: solo-access-log
- type: log
paths: ["/tmp/abc1024.log"]
encoding: UTF-8
fields:
service: abc1024-access-log
output.logstash:
hosts: ["192.168.10.68:5044"]
ElasticSearch
是⼀个开源,是⼀个基于Apache Lucene库构建的Restful搜索引擎. Elasticsearch是在Solr之后几年推出的。
它提供了⼀个分布式,多租户能力的全文搜索引擎,具有HTTP Web界⾯(REST)和无架构JSON文档(指的是没有预定义结构的JSON数据。
)。Elasticsearch的官方客户端库提供Java,Groovy,PHP,Ruby,Perl,Python,.NET和Javascript。
应用场景
适用场景 | 说明 |
---|---|
搜索引擎 | 可以快速而准确地搜索大量的结构化和非结构化数据,用于构建搜索引擎、电商网站搜索等。 |
实时日志分析 | 提供了强大的实时索引功能和聚合功能,非常适合实时日志分析。 |
数据分析和可视化 | Elasticsearch与Kibana(Elastic Stack的一个组件)结合使用,可以进行复杂的数据分析和可视化。 |
实时推荐系统 | 于Elasticsearch的实时搜索和聚合功能,可以用于构建实时推荐系统。根据用户的行为和兴趣,动态地推荐相关的内容和产品。 |
电商商品搜索和过滤 | Elasticsearch具有强大的搜索和过滤能力,使其成为构建电商网站的商品搜索和过滤引擎的理想选择。可以快速搜索和过滤商品属性、价格范围、地理位置等。 |
地理空间数据分析 | Elasticsearch内置了地理空间数据类型和查询功能,可以对地理位置进行索引和搜索,适合于构建地理信息系统(GIS)、位置服务和地理数据分析应用 。 |
业界产品应用场景
维基百科、Stack Overflow、GitHub
核心概念
在新版Elasticsearch中,文档document就是一行记录(json),而这些记录存在于索引库(index)中, 索引名称必须是小写
Mysql数据库 | Elastic Search |
---|---|
Database | Index(7.X版本前有Type,对比数据库中的表,新版取消了) |
Table | Index |
Row | Document |
Column | Field |
分片(shards)
- 数据量特大,没有足够大的硬盘空间来一次性存储,且一次性搜索那么多的数据,响应跟不上
- ES提供把数据进行分片存储,这样方便进行拓展和提高吞吐
副本(replicas)
分片的拷贝,当主分片不可用的时候,副本就充当主分片进行使用;索引分片的备份,shard和replica一般存储在不同的节点上,用来提高高可靠性
元数据
Elasticsearch中以 “ _” 开头的属性都成为元数据,都有自己特定的意思
ES默认为一个索引创建1个主分片和1个副本,在创建索引的时候使用settings属性指定,每个分片
必须有零到多个副本
索引一旦创建成功,主分片primary shard数量不可以变(只能重建索引),副本数量可以改变
正排索引和倒排索引
正排索引 (Forward Index )
正排索引用于快速访问和提取文档的内容
将文档的内容按照文档的顺序进行索引,每个文档对应一个索引条目,包含了文档的各个字段的内容
在数据库系统中,将正排索引类比为表格的结构,每一行是一个记录,每一列是一个字段
正排索引的优劣
优劣 | 描述 |
---|---|
优势 | 可以快速的查找某个文档里包含哪些词项 |
劣势 | 不适用于查找包含某个词项 的文档有哪些 |
例子
假设我们有三个文档的标题和内容
- 文档1:标题 "Apple iPhone 12",内容 "The latest iPhone model"
- 文档2:标题 "Samsung Galaxy S21",内容 "Powerful Android smartphone"
- 文档3:标题 "Microsoft Surface Laptop",内容 "Thin and lightweight laptop"
正排索引的结构示意图如下:
DocumentID | Title | Content |
---|---|---|
1 | Apple iPhone 12 | The latest iPhone model |
2 | Samsung Galaxy S21 | Powerful Android smartphone |
3 | Microsoft Surface Laptop | Thin and lightweight laptop |
倒排索引(Inverted Index)
倒排索引用于快速定位和检索包含特定词语的文档
根据关键词构建的索引结构,记录了每个关键词出现在哪些文档或数据记录中
,适用于全文搜索和关键词检索的场景
;它将文档或数据记录划分成关键词的集合,并记录每个关键词所出现的位置和相关联的文档或数据记录的信息
倒排索引的优劣
优劣 | 描述 |
---|---|
优势 | 倒排索引记录了每个词语出现在哪些文档中,通过这种方式,我们可以非常快速地得知每个关键词分别出现在哪些文档中。 |
劣势 | 不适用于查找某个文档里包含哪些词项 |
例子
- 例子假设 使用以下文档构建倒排索引
- 文档1:标题 "Apple iPhone 12",内容 "The latest iPhone model"
- 文档2:标题 "Samsung Galaxy S21",内容 "Powerful Android smartphone"
- 文档3:标题 "Microsoft Surface Laptop",内容 "Thin and lightweight laptop Apple"
倒排索引的结构示意图如下:
Term | Documents |
---|---|
Apple | 1,3 |
iPhone | 1 |
12 | 1 |
latest | 1 |
Samsung | 2 |
Galaxy | 2 |
S21 | 2 |
Powerful | 2 |
Android | 2 |
smartphone | 2 |
Microsoft | 3 |
Surface | 3 |
Laptop | 3 |
Thin | 3 |
lightweight | 3 |
索引Index核心操作
#查看索引列表
# 查看索引列表
GET /_cat/indices?v=true&pretty
# 查看分片情况(主分片和副本分片需要在不同的服务器上,否则副本分片显示UNASSIGNED )
GET /_cat/shards?v=true&pretty
# 创建索引
PUT /soulboy_shop
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 1
}
}
# 查看索引是否存在( 结果是200 和 404)
HEAD /soulboy_shop
# 获取索引(Get Index)
GET /soulboy_shop
# 更新索引设置(Update Index Settings)
PUT /soulboy_shop/_settings
{
"settings": {
"number_of_replicas": 2
}
}
# 删除索引(Delete Index)
DELETE /soulboy_shop
# 查看集群健康情况
GET /_cluster/health
# 查看节点分布情况
GET /_cat/nodes?v=true&pretty
Document核心操作
真正的数据,存储一条数据就是一份文档,存储格式为JOSN,等同于mysql中的一条数据
# 查询文档
GET /soulboy_shop/_doc/1
# 新增文档(需要指定ID)
PUT /soulboy_shop/_doc/1
{
"id":5555,
"title":"葵花宝典",
"pv":144
}
# 新增文档(不指定ID或者指定ID都可以),如果不指定id会自动生成id QPaOdZEBfJIIaWpZZ95i
POST /soulboy_shop/_doc
{
"id":6666,
"title":"九阳神功",
"pv":244
}
GET /soulboy_shop/_doc/QPaOdZEBfJIIaWpZZ95i
# 修改(put和post都行,需要指定id)
POST /soulboy_shop/_doc/QPaOdZEBfJIIaWpZZ95i
{
"id":6666,
"title":"九阳神功",
"pv":399,
"uv":88
}
GET /soulboy_shop/_doc/QPaOdZEBfJIIaWpZZ95i
# 搜索
GET /soulboy_shop/_search
took 字段表示该操作的耗时(单位为毫秒)
timed_out 字段表示是否超时
hits 字段表示搜到的记录,数组形式
total 返回记录数
max_score 最高的匹配程度
sd
# 删除数据
DELETE /soulboy_shop/_doc/PvaLdZEBfJIIaWpZvd49
Mapping
类似于数据库中的表结构定义 schema,定义索引中的字段的名称,字段的数据类型,比如字符串、数字、布尔等
Dynamic Mapping(动态映射)
- 用于在索引文档时自动检测和定义字段的数sdasdada据类型
- 当我们向索引中添加新文档时,Elasticsearch会自动检测文档中的各个字段,并根据它们的值来尝试推断字段类型
- 动态映射具备自动解析和创建字段的便利性,但在某些情况下,由于字段类型的不确定性,动态映射可能会带来一些问题,
例如字段解析错误、字段类型不一致等,如果对字段类型有明确的要求,最好在索引创建前通过显式映射定义来指定字段类型
- 常见的字段类型包括
文本(text)、关键词(keyword)、日期(date)、数值(numeric)等
常见的数据类型
- 如果需要进行全文本检索,并且希望根据分词结果计算相关性得分,以获得最佳的匹配结果,则选择text字段类型。
- 如果需要进行精确匹配、排序或聚合操作,并且不需要对内容进行分词,则选择keyword字段类型。
数据类型 | 描述 |
---|---|
Text类型(字符串类型) | 用于全文搜索的字符串类型,支持分词和索引建立 ,text字段在搜索时会根据分词结果进行匹配,并计算相关性得分,以便返回最佳匹配的结果。text类型主要用于全文本搜索,适合存储需要进行全文本分词的文本内容,如文章、新闻等。 |
Keyword类型(字符串类型) | 用于精确匹配的字符串类型,不进行分词,而是将整个字段作为一个整体进行索引和搜索,这使得搜索只能从精确的值进行匹配,而不能根据词项对内容进行模糊检索,适合用作过滤和聚合操作 |
Numeric类型 | 包括整数类型(long、integer、short、byte)和浮点数类型(double、float) |
Date类型 | 用于存储日期和时间的类型 |
Boolean类型 | 用于存储布尔值(true或false)的类型 |
Binary类型 | 用于存储二进制数据的类型 |
Array类型 | 用于存储数组或列表数据的类型 |
Object类型 | 用于存储复杂结构数据的类型 |
# 指定索引库字段类型mapping
PUT /my_index
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"title": {
"type": "text"
},
"price": {
"type": "float"
}
}
}
}
GET /my_index/_mapping
DELETE /my_index
### 创建索引并插入文档
# 创建索引(指定字段和数据类型)
PUT /my_index
{
"mappings": {
"properties": {
"title": {
"type": "text"
},
"tags": {
"type": "keyword"
},
"publish_date": {
"type": "date"
},
"rating": {
"type": "float"
},
"is_published": {
"type": "boolean"
},
"author": {
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "integer"
}
}
},
"comments": {
"type": "nested",
"properties": {
"user": {
"type": "keyword"
},
"message": {
"type": "text"
}
}
}
}
}
}
# 并插入文档
POST /my_index/_doc/1
{
"title": "ElasticSearch学习资料",
"tags": ["search", "big data", "distributed system", "soulboy"],
"publish_date": "2025-01-01",
"rating": 4.5,
"is_published": true,
"author": {
"name": "John Doe",
"age": 30
},
"comments": [
{
"user": "Alice",
"message": "Great article!"
},
{
"user": "Bob",
"message": "Very informative."
}
]
}
GET /my_index/_mapping
GET /my_index/_doc/1
# 查询匹配关键词的文档
GET /my_index/_search
{
"query": {
"match": {
"title": "ElasticSearch"
}
}
}
# 查询匹配关键词的文档(keyword数据类型必须精准匹配)
GET /my_index/_search
{
"query": {
"match": {
"tags": "big data"
}
}
}
ES默认分词器
在Elasticsearch中,分词(tokenization)是将文本内容拆分成独立的单词或词项(tokens)的过程
分词是搜索引擎在建立索引和执行查询时的关键步骤,将文本拆分成单词,并构建倒排索引,可以实现更好的搜索和检索效果。
假设我们有两个产品标题:
"Apple iPhone 12 Pro Max 256GB"
"Samsung Galaxy S21 Ultra 128GB"
使用默认的标准分词器(Standard Tokenizer),这些标题会被分割为以下令牌:
标题1:["Apple", "iPhone", "12", "Pro", "Max", "256GB"]
标题2:["Samsung", "Galaxy", "S21", "Ultra", "128GB"]
分词器根据标点符号和空格将标题拆分为独立的词语(令牌)。当我们执行搜索时,可以将查询进行分词,并将其与标题中的令牌进行匹配。
例如
如果我们搜索"iPhone 12",使用默认的分词器,它会将查询分解为["iPhone", "12"],然后与令牌进行匹配。
对于标题1,令牌["iPhone", "12"]匹配,它与查询相符。 标题2中没有与查询相符的令牌
分词规则
分词规则是指定义如何将文本进行拆分的规则和算法。
Elasticsearch使用一系列的分词器(analyzer)和 标记器(tokenizer)来处理文本内容
分词器
分词器通常由一个或多个标记器组成,用于定义分词的具体规则,默认的Standard分词器
常见的分词器,如Standard分词器、Simple分词器、Whitespace分词器、IK分词等,还支持自定义分词器
### 标点符号切分
标点符号会被删除,并将连字符分隔为两个独立的词。
例如,"Let's go!" 会被切分为 "Let", "s", "go"。
### 小写转换
所有的文本会被转换为小写形式。
例如,"Hello World" 会被切分为 "hello", "world"。
### 停用词过滤
停用词(stop words)是在搜索中没有实际意义的常见词,停用词会被过滤掉,不会作为独立的词进行索引和搜索。
例如, "a", "an", "the" 等。
### 词干提取
通过应用Porter2词干提取算法,将单词还原为其原始形式。
例如,running -> run、swimming -> swim、jumped -> jump
### 词分隔
按照空格将文本切分成词项(tokens)
使用指定ES分词器查看分词后的存储效果
分词结果对象包含属性 | 描述 |
---|---|
token | 分词后的单词 |
start_offset | 开始位置 |
end_offset | 结束位置 |
type | 类型 |
ALPHANUM 是一种数据类型,表示一个字符串字段只包含字母和数字,并且不会进行任何其他的分词或处理 | |
position | 单词在原始文本中的位置 |
# 使用指定ES分词器查看分词后的存储效果(中文分词效果不好)
GET /_analyze
{
"analyzer": "standard",
"text": "我是一头小象,我喜欢吃饭和睡觉,我喜欢伺候哥哥。"
}
# 字段是text类型(中文分词效果不好)
POST /my_index/_analyze
{
"field": "title",
"text": "今天我在油管学习数字孪"
}
# 字段是keyword类型
POST /my_index/_analyze
{
"field": "tags",
"text": ["This is","Spring Boot" ]
}
IK中文分词器配置
在Elasticsearch 8.X中,分词(tokenization)是将文本内容拆分成独立的单词或词项(tokens)的过程。默认的Standard分词器对中文支持不是很友好
# 使用指定ES分词器查看分词后的存储效果
GET /_analyze
{
"analyzer": "standard",
"text": "我是一头小象,我喜欢吃饭和睡觉,我喜欢伺候哥哥。"
}
# 输出结果
{
"tokens": [
{
"token": "我",
"start_offset": 0,
"end_offset": 1,
"type": "<IDEOGRAPHIC>",
"position": 0
},
{
"token": "是",
"start_offset": 1,
"end_offset": 2,
"type": "<IDEOGRAPHIC>",
"position": 1
},
{
"token": "一",
"start_offset": 2,
"end_offset": 3,
"type": "<IDEOGRAPHIC>",
"position": 2
},
{
"token": "头",
"start_offset": 3,
"end_offset": 4,
"type": "<IDEOGRAPHIC>",
"position": 3
},
{
"token": "小",
"start_offset": 4,
"end_offset": 5,
"type": "<IDEOGRAPHIC>",
"position": 4
},
{
"token": "象",
"start_offset": 5,
"end_offset": 6,
"type": "<IDEOGRAPHIC>",
"position": 5
},
{
"token": "我",
"start_offset": 7,
"end_offset": 8,
"type": "<IDEOGRAPHIC>",
"position": 6
},
{
"token": "喜",
"start_offset": 8,
"end_offset": 9,
"type": "<IDEOGRAPHIC>",
"position": 7
},
{
"token": "欢",
"start_offset": 9,
"end_offset": 10,
"type": "<IDEOGRAPHIC>",
"position": 8
},
{
"token": "吃",
"start_offset": 10,
"end_offset": 11,
"type": "<IDEOGRAPHIC>",
"position": 9
},
{
"token": "饭",
"start_offset": 11,
"end_offset": 12,
"type": "<IDEOGRAPHIC>",
"position": 10
},
{
"token": "和",
"start_offset": 12,
"end_offset": 13,
"type": "<IDEOGRAPHIC>",
"position": 11
},
{
"token": "睡",
"start_offset": 13,
"end_offset": 14,
"type": "<IDEOGRAPHIC>",
"position": 12
},
{
"token": "觉",
"start_offset": 14,
"end_offset": 15,
"type": "<IDEOGRAPHIC>",
"position": 13
},
{
"token": "我",
"start_offset": 16,
"end_offset": 17,
"type": "<IDEOGRAPHIC>",
"position": 14
},
{
"token": "喜",
"start_offset": 17,
"end_offset": 18,
"type": "<IDEOGRAPHIC>",
"position": 15
},
{
"token": "欢",
"start_offset": 18,
"end_offset": 19,
"type": "<IDEOGRAPHIC>",
"position": 16
},
{
"token": "伺",
"start_offset": 19,
"end_offset": 20,
"type": "<IDEOGRAPHIC>",
"position": 17
},
{
"token": "候",
"start_offset": 20,
"end_offset": 21,
"type": "<IDEOGRAPHIC>",
"position": 18
},
{
"token": "哥",
"start_offset": 21,
"end_offset": 22,
"type": "<IDEOGRAPHIC>",
"position": 19
},
{
"token": "哥",
"start_offset": 22,
"end_offset": 23,
"type": "<IDEOGRAPHIC>",
"position": 20
}
]
}
是一个基于Java开发的开源中文分词器,用于将中文文本拆分成单个词语(词项)是针对中文语言的特点和需求而设计的,可以有效处理中文分词的复杂性和多样性
IK分词器多个版本
特点 | 描述 |
---|---|
高效且灵活 | IK分词器采用了多种算法和数据结构,以提供高效的分词速度。 |
支持细粒度的分词,可以根据应用需求进行灵活的配置。 | |
分词准确性 | IK分词器使用了词典和规则来进行分词,可以准确地将句子拆分成词语。 |
还提供了词性标注功能,可以帮助识别词语的不同含义和用途。 | |
支持远程扩展词库 | IK分词器可以通过配置和加载外部词典,扩展分词的能力 |
用户可以根据实际需求,添加自定义的词典,提升分词准确性和覆盖范围。 | |
兼容性与集成性 | IK分词器兼容了Lucene和Elasticsearch等主流搜索引擎,可以方便地集成到这些系统中。 |
提供了相应的插件和配置选项,使得分词器的集成变得简单。 |
安装IK分词器
### 解压,上传到Elastic Search的plugins目录
[root@localhost plugins]# pwd
/usr/local/software/elk/elasticsearch-8.4.1/plugins
[root@localhost plugins]# ls
elasticsearch-analysis-ik-8.4.1
### 重启Elastic Search即可
# 找出elasticsearch的进程id (1453)
ps -ef | grep elasticsearch
# 杀死进程elasticsearch的进程id (1453)
kill -9 1453
# 启动Elastic Search
[es_user@localhost bin]$ su es_user
[es_user@localhost bin]$ cd /usr/local/software/elk/elasticsearch-8.4.1/bin
[es_user@localhost bin]$ ./elasticsearch &
# 关闭Elasticsearch节点
bin/elasticsearch -d
验证IK分词器是否生效
- ik_smart:
会做最粗粒度的拆分
- ik_max_word(常用):
会将文本做最细粒度的拆分
# 使用指定IK分词器查看分词后的存储效果
GET /_analyze
{
"analyzer": "ik_smart",
"text": "我是一头小象,我喜欢吃饭和睡觉,我喜欢伺候哥哥。"
}
# 使用指定IK分词器查看分词后的存储效果
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "我是一头小象,我喜欢吃饭和睡觉,我喜欢伺候哥哥。"
}
# ik_smart 输出结果
{
"tokens": [
{
"token": "我",
"start_offset": 0,
"end_offset": 1,
"type": "CN_CHAR",
"position": 0
},
{
"token": "是",
"start_offset": 1,
"end_offset": 2,
"type": "CN_CHAR",
"position": 1
},
{
"token": "一头",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 2
},
{
"token": "小象",
"start_offset": 4,
"end_offset": 6,
"type": "CN_WORD",
"position": 3
},
{
"token": "我",
"start_offset": 7,
"end_offset": 8,
"type": "CN_CHAR",
"position": 4
},
{
"token": "喜欢",
"start_offset": 8,
"end_offset": 10,
"type": "CN_WORD",
"position": 5
},
{
"token": "吃饭",
"start_offset": 10,
"end_offset": 12,
"type": "CN_WORD",
"position": 6
},
{
"token": "和",
"start_offset": 12,
"end_offset": 13,
"type": "CN_CHAR",
"position": 7
},
{
"token": "睡觉",
"start_offset": 13,
"end_offset": 15,
"type": "CN_WORD",
"position": 8
},
{
"token": "我",
"start_offset": 16,
"end_offset": 17,
"type": "CN_CHAR",
"position": 9
},
{
"token": "喜欢",
"start_offset": 17,
"end_offset": 19,
"type": "CN_WORD",
"position": 10
},
{
"token": "伺候",
"start_offset": 19,
"end_offset": 21,
"type": "CN_WORD",
"position": 11
},
{
"token": "哥哥",
"start_offset": 21,
"end_offset": 23,
"type": "CN_WORD",
"position": 12
}
]
}
# ik_max_word 输出结果
{
"tokens": [
{
"token": "我",
"start_offset": 0,
"end_offset": 1,
"type": "CN_CHAR",
"position": 0
},
{
"token": "是",
"start_offset": 1,
"end_offset": 2,
"type": "CN_CHAR",
"position": 1
},
{
"token": "一头",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 2
},
{
"token": "一",
"start_offset": 2,
"end_offset": 3,
"type": "TYPE_CNUM",
"position": 3
},
{
"token": "头",
"start_offset": 3,
"end_offset": 4,
"type": "COUNT",
"position": 4
},
{
"token": "小象",
"start_offset": 4,
"end_offset": 6,
"type": "CN_WORD",
"position": 5
},
{
"token": "我",
"start_offset": 7,
"end_offset": 8,
"type": "CN_CHAR",
"position": 6
},
{
"token": "喜欢吃",
"start_offset": 8,
"end_offset": 11,
"type": "CN_WORD",
"position": 7
},
{
"token": "喜欢",
"start_offset": 8,
"end_offset": 10,
"type": "CN_WORD",
"position": 8
},
{
"token": "吃饭",
"start_offset": 10,
"end_offset": 12,
"type": "CN_WORD",
"position": 9
},
{
"token": "和",
"start_offset": 12,
"end_offset": 13,
"type": "CN_CHAR",
"position": 10
},
{
"token": "睡觉",
"start_offset": 13,
"end_offset": 15,
"type": "CN_WORD",
"position": 11
},
{
"token": "我",
"start_offset": 16,
"end_offset": 17,
"type": "CN_CHAR",
"position": 12
},
{
"token": "喜欢",
"start_offset": 17,
"end_offset": 19,
"type": "CN_WORD",
"position": 13
},
{
"token": "伺候",
"start_offset": 19,
"end_offset": 21,
"type": "CN_WORD",
"position": 14
},
{
"token": "哥哥",
"start_offset": 21,
"end_offset": 23,
"type": "CN_WORD",
"position": 15
}
]
}
Query DSL
Query DSL(Domain-Specific Language)是一种用于构建搜索查询的强大的领域特定查询语言。类似关系数据库的SQL查询语法
,ES中用JSON结构化的方式定义、执行各种查询操作,在ES中进行高级搜索和过滤
- Query DSL提供了更多种类的查询和过滤语句,以满足不同的搜索需求。
- 可以根据具体的业务需求和数据结构,结合不同的查询方式来构建复杂的搜索和过滤操作
准备数据
# 创建索引
PUT /soulboy_shop_v1
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"title": {
"type": "keyword"
},
"summary": {
"type": "text"
},
"price": {
"type": "float"
}
}
}
}
# 导入数据
PUT /soulboy_shop_v1/_bulk
{ "index": { "_index": "soulboy_shop_v1" } }
{ "id": "1", "title": "Spring Boot","summary":"this is a summary Spring Boot video", "price": 9.99 }
{ "index": { "_index": "soulboy_shop_v1" } }
{ "id": "2", "title": "java","summary":"this is a summary java video", "price": 19.99 }
{ "index": { "_index": "soulboy_shop_v1" } }
{ "id": "3", "title": "Spring Cloud","summary":"this is a summary Spring Cloud video", "price": 29.99 }
{ "index": { "_index": "soulboy_shop_v1" } }
{ "id": "4", "title": "Spring_Boot", "summary":"this is a summary Spring_Boot video","price": 59.99 }
{ "index": { "_index": "soulboy_shop_v1" } }
{ "id": "5", "title": "SpringBoot","summary":"this is a summary SpringBoot video", "price": 0.99 }
GET /soulboy_shop_v1/_search
基本查询
基本语法
GET /索引库名/_search
{
"query":{
"查询类型":{
}
}
match
用于执行全文搜索,它会将搜索查询与指定字段中的文本进行匹配
# 模糊查询title包含elasticsearch的document
GET /my_index/_search
{
"query": {
"match": {
"title": "elasticsearch"
}
}
}
term
用于精确匹配一个指定字段的关键词,不进行分词处理。
# 精确匹配tags中含有"big data"的标签
GET /my_index/_search
{
"query": {
"term": {
"tags": "big data"
}
}
}
查询建议
- match在匹配时会对所查找的关键词进行分词,然后分词匹配查找;term会直接对关键词进行查找
- 一般业务里面需要模糊查找的时候,更多选择match,而精确查找时选择term查询
查询全部数据(match_all)
GET /soulboy_shop_v1/_search
{
"query": {
"match_all": {}
}
}
有条件查询数据
- match,对查询内容进行分词, 然后进行查询,多个词条之间是 or的关系
- 然后在与文档里面的分词进行匹配,匹配度越高分数越高越前面
# 有条件查询数据
GET /soulboy_shop_v1/_search
{
"query": {
"match": {
"summary": "Spring"
}
}
}
# 包括多个词 Spring or Java
GET /soulboy_shop_v1/_search
{
"query": {
"match": {
"summary": "Spring Java"
}
}
}
完整关键词查询
- term查询,不会将查询条件分词,直接与文档里面的分词进行匹配
- 虽然match也可以完成,但是match查询会多一步进行分词,浪费资源
获取指定字段
- 某些情况场景下,不需要返回全部字段,太废资源,可以指定source返回对应的字段
# 获取指定字段
GET /soulboy_shop_v1/_search
{
"_source":["price","title"],
"query": {
"term": {
"title": {
"value": "Spring Boot"
}
}
}
}
# 返回数据
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 2,
"successful": 2,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.2039728,
"hits": [
{
"_index": "soulboy_shop_v1",
"_id": "EVoeeJEBm34NyhWJFaXn",
"_score": 1.2039728,
"_source": {
"price": 9.99,
"title": "Spring Boot"
}
}
]
}
}
范围查询
符号 | 说明 |
---|---|
gte | 大于等于 |
gt | 大于 |
lte | 小于等于 |
lt | 小于 |
# 获取指定字段 5~100(包含头尾)
GET /soulboy_shop_v1/_search
{
"query": {
"range": {
"price": {
"gte": 5,
"lte": 100
}
}
}
}
分页、排序查询
分页查询
- 可以使用
from
和size
参数进行分页查询 - 可以指定要跳过的文档数量(
from
)和需要返回的文档数量(size
)
# 分页查询
GET /soulboy_shop_v1/_search
{
"size": 3,
"from": 0,
"query": {
"match_all": {}
}
}
查询结果排序
sort
字段可以进行排序desc
和asc
# 排序
GET /soulboy_shop_v1/_search
{
"size": 10,
"from": 0,
"sort": [
{
"price": "asc"
}
],
"query": {
"match_all": {}
}
}
布尔查询
- 通过组合多个查询条件,
使用布尔逻辑(与、或、非)进行复杂的查询操作
- 语法格式
- "must"关键字用于指定必须匹配的条件,即所有条件都必须满足
- "must_not"关键字指定必须不匹配的条件,即所有条件都不能满足
- "should"关键字指定可选的匹配条件,即至少满足一个条件
- "filter"关键字用于指定一个过滤条件,可以是一个具体的过滤器,如term、range等,也可以是一个嵌套的bool过滤器
# 语法
{
"query": {
"bool": {
"must": [
// 必须匹配的条件
],
"must_not": [
// 必须不匹配的条件
],
"should": [
// 可选匹配的条件
],
"filter": [
// 过滤条件
]
}
}
}
# 布尔查询(条件匹配+范围查询 同时满足)
GET /soulboy_shop_v1/_search
{
"query": {
"bool": {
"must": [
{ "match": { "summary": "Cloud" }},
{ "range": { "price": { "gte": 5 }}}
]
}
}
}
Filter(查询过滤)
- 来对搜索结果进行筛选和过滤,仅返回符合特定条件的文档,而不改变搜索评分
- Filter查询对结果进行缓存,提高查询性能,用于数字范围、日期范围、布尔逻辑、存在性检查等各种过滤操作。
- 过滤条件通常用于对结果进行筛选,并且比查询条件更高效
- 而bool查询可以根据具体需求
组合多个条件、过滤器和查询子句
语法格式
- "filter"关键字用于指定一个过滤条件,
可以是一个具体的过滤器,如term、range等,也可以是一个嵌套的bool过滤器
{
"query": {
"bool": {
"filter": {
// 过滤条件
}
}
}
}
准备数据
# 创建索引库
PUT /product
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"product_id": {
"type": "integer"
},
"product_name": {
"type": "text"
},
"category": {
"type": "keyword"
},
"price": {
"type": "float"
},
"availability": {
"type": "boolean"
}
} }
}
# 导入数据
* 通过一次 POST 请求实现批量插入
* 每个文档都由两部分组成:index,指令用于指定文档的元数据,product_id 为文档的唯一标识符
* 插入后查询 GET /product/_search
POST /product/_bulk
{ "index": { "_id": "1" } }
{ "product_id": 1, "product_name": "Product 1", "category": "books", "price": 19.99, "availability": true }
{ "index": { "_id": "2" } }
{ "product_id": 2, "product_name": "Product 2", "category": "electronics", "price": 29.99, "availability": true }
{ "index": { "_id": "3" } }
{ "product_id": 3, "product_name": "Product 3", "category": "books", "price": 9.99, "availability": false }
{ "index": { "_id": "4" } }
{ "product_id": 4, "product_name": "Product 4", "category": "electronics", "price": 49.99, "availability": true }
{ "index": { "_id": "5" } }
{ "product_id": 5, "product_name": "Product 5", "category": "fashion", "price": 39.99, "availability": true }
GET /product/_search
示例一 :使用 term
过滤器查询 category
为 books
的产品:
GET /product/_search
{
"query": {
"bool": {
"filter": {
"term": {
"category": "books"
}
}
}
}
}
示例二:使用 range
过滤器查询价格 price
在 30 到 50 之间的产品
GET /product/_search
{
"query": {
"bool": {
"filter": {
"range": {
"price": {
"gte": 30,
"lte": 50
}
}
}
}
}
}
多字段匹配、短语搜索
match高级用法,业务查询,需要在多个字段上进行文本搜索,用 multi_match
数据环境准备
# 创建索引库
PUT /product_v2
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"product_name": {
"type": "text"
},
"description": {
"type": "text"
},
"category": {
"type": "keyword"
}
}
}
}
# 批量插入数据
POST /product_v2/_bulk
{ "index": { "_index": "product_v2", "_id": "1" } }
{ "product_name": "iPhone 12", "description": "The latest iPhone model from Apple", "category": "electronics" }
{ "index": { "_index": "product_v2", "_id": "2" } }
{ "product_name": "Samsung Galaxy S21", "description": "High-performance Android smartphone", "category": "electronics" }
{ "index": { "_index": "product_v2", "_id": "3" } }
{ "product_name": "MacBook Pro", "description": "Powerful laptop for professionals", "category": "electronics" }
{ "index": { "_index": "product_v2", "_id": "4" } }
{ "product_name": "Harry Potter and the Philosopher's Stone", "description": "Fantasy novel by J.K. Rowling", "category": "books" }
{ "index": { "_index": "product_v2", "_id": "5" } }
{ "product_name": "The Great Gatsby", "description": "Classic novel by F. Scott Fitzgerald", "category": "books" }
语法格式
# query:需要匹配的查询文本
# fields:一个包含需要进行匹配的字段列表的数组
GET /index/_search
{
"query": {
"multi_match": {
"query": "要搜索的文本",
"fields": ["字段1", "字段2", ...]
}
}
}
示例:多字段搜索
- 在
product_name
和description
字段上执行了一个multi_match
查询 - 将查询文本设置为 "iPhone",对这两个字段进行搜索,并返回包含匹配到的文档,这个是OR的关系,会有最佳匹配
# 多字段搜索
GET /product_v2/_search
{
"query": {
"multi_match": {
"query": "iPhone",
"fields": ["product_name", "description"]
}
}
}
示例:短语搜索案
- 使用
match_phrase
查询在description
字段上执行了一个短语搜索将要搜索的短语设置为 "classic novel"。 - 使用
match_phrase
查询,Elasticsearch将会返回包含 "classic novel" 短语的文档
# match_phrase短语搜索(命中1条document)
GET /product_v2/_search
{
"query": {
"match_phrase": {
"description": "classic novel"
}
}
}
# match搜索,会进行分词(命中2条document)
GET /product_v2/_search
{
"query": {
"match": {
"description": "classic novel"
}
}
}
fuzzy模糊查询(日常单词拼写错误)
fuzzy
查询是Elasticsearch中提供的一种模糊匹配查询类型,用在搜索时容忍一些拼写错误或近似匹配- 使用
fuzzy
查询,可以根据指定的**编辑距离(即词之间不同字符的数量)**来模糊匹配查询词更改字符(box→fox) 删除字符(black→lack) 插入字符(sic→sick) 转置两个相邻字符(dgo→dog)
- fuzzy模糊查询是拼写错误的简单解决方案,但具有很高的 CPU 开销和非常低的精准度
- 用法和match基本一致,Fuzzy query的查询不分词
基本语法格式
字段 | 功能描述 |
---|---|
field_name | 要进行模糊匹配的字段名 |
value | 要搜索的词 |
fuzziness | 参数指定了模糊度,常见值如下 |
0,1,2 :* 指定数字,表示允许的最大编辑距离,较低的数字表示更严格的匹配,较高的数字表示更松散的匹配。fuziness的值,表示是针对每个词语而言的,而不是总的错误的数值 | |
auto :Elasticsearch根据词的长度自动选择模糊度;如果字符串的长度大于5,那 funziness 的值自动设置为2 ;如果字符串的长度小于2,那么 fuziness 的值自动设置为 0 |
GET /index/_search
{
"query": {
"fuzzy": {
"field_name": {
"value": "要搜索的词",
"fuzziness": "模糊度"
}
}
}
}
示例:模糊查询
# 指定模糊度2,更松散匹配 (this is a summary Spring Cloud video)(命中1条)
GET /soulboy_shop_v1/_search
{
"query": {
"fuzzy": {
"summary": {
"value": "clo",
"fuzziness": "2"
}
}
}
}
# 指定模糊度1,更严格匹配(查询不出来)
GET /soulboy_shop_v1/_search
{
"query": {
"fuzzy": {
"summary": {
"value": "clo",
"fuzziness": "1"
}
}
}
}
# 使用自动检查,1个单词拼写错误(this is a summary Spring Cloud video) (命中2条)
GET /soulboy_shop_v1/_search
{
"query": {
"fuzzy": {
"summary": {
"value": "Sprina",
"fuzziness": "auto"
}
}
}
}
搜索高亮显示
日常搜索产品的时候,会有关键词
显示不一样的颜色,方便用户直观看到区别
数据准备
#创建索引库
PUT /soulboy_high_light_test
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word"
},
"content": {
"type": "text",
"analyzer": "ik_max_word"
}
}
},
"settings": {
"number_of_shards": 2,
"number_of_replicas": 0
}
}
#插入数据
PUT /soulboy_high_light_test/_doc/1
{
"title": "豆瓣电影2028年最新好看的电影推荐",
"content": "每年都有新电影上线,2028年最新好看的电影有不少,豆瓣电影上线了《釜山行》,《行尸走肉》,《老王往事》精彩电影"
}
PUT /soulboy_high_light_test/_doc/2
{
"title": "写下你认为好看的电影有哪些",
"content": "每个人都看看很多电影,说下你近10年看过比较好的电影,比如《釜山行》,《行尸走肉》,《宙斯和老王的故事》"
}
Elastic Search搜索引擎如何做到高亮显示
- 在 ES 中,高亮语法用于在搜索结果中
突出显示与查询匹配的关键词
- 高亮显示是通过标签包裹匹配的文本来实现的,通常是
<em>
或其他 HTML 标签 - 基本用法:
在 highlight 里面填写要高亮显示的字段,可以填写多个
单条件查询高亮显示
highlight里面填写需要高亮的字段
# 单条件查询高亮显示
GET /soulboy_high_light_test/_search
{
"query": {
"match": {
"content": "电影"
}
},
"highlight": {
"fields": {
"content": {}
}
}
}
# 查询结果
{
"took": 193,
"timed_out": false,
"_shards": {
"total": 2,
"successful": 2,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 0.17888658,
"hits": [
{
"_index": "soulboy_high_light_test",
"_id": "1",
"_score": 0.17888658,
"_source": {
"title": "豆瓣电影2028年最新好看的电影推荐",
"content": "每年都有新电影上线,2028年最新好看的电影有不少,豆瓣电影上线了《釜山行》,《行尸走肉》,《老王往事》精彩电影"
},
"highlight": {
"content": [
"每年都有新<em>电影</em>上线,2028年最新好看的<em>电影</em>有不少,豆瓣<em>电影</em>上线了《釜山行》,《行尸走肉》,《老王往事》精彩<em>电影</em>"
]
}
},
{
"_index": "soulboy_high_light_test",
"_id": "2",
"_score": 0.144106,
"_source": {
"title": "写下你认为好看的电影有哪些",
"content": "每个人都看看很多电影,说下你近10年看过比较好的电影,比如《釜山行》,《行尸走肉》,《宙斯和老王的故事》"
},
"highlight": {
"content": [
"每个人都看看很多<em>电影</em>,说下你近10年看过比较好的<em>电影</em>,比如《釜山行》,《行尸走肉》,《宙斯和老王的故事》"
]
}
}
]
}
}
组合多条件查询
highlight里面填写需要高亮的字段
# 组合多条件查询
GET /soulboy_high_light_test/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"title": "电影"
}
},
{
"match": {
"content": "老王"
}
}
]
}
},
"highlight": {
"fields": {
"title": {},
"content": {}
}
}
}
# 查询结果
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 2,
"successful": 2,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 0.43035197,
"hits": [
{
"_index": "soulboy_high_light_test",
"_id": "1",
"_score": 0.43035197,
"_source": {
"title": "豆瓣电影2028年最新好看的电影推荐",
"content": "每年都有新电影上线,2028年最新好看的电影有不少,豆瓣电影上线了《釜山行》,《行尸走肉》,《老王往事》精彩电影"
},
"highlight": {
"title": [
"豆瓣<em>电影</em>2028年最新好看的<em>电影</em>推荐"
],
"content": [
"每年都有新电影上线,2028年最新好看的电影有不少,豆瓣电影上线了《釜山行》,《行尸走肉》,《<em>老王</em>往事》精彩电影"
]
}
},
{
"_index": "soulboy_high_light_test",
"_id": "2",
"_score": 0.36774224,
"_source": {
"title": "写下你认为好看的电影有哪些",
"content": "每个人都看看很多电影,说下你近10年看过比较好的电影,比如《釜山行》,《行尸走肉》,《宙斯和老王的故事》"
},
"highlight": {
"title": [
"写下你认为好看的<em>电影</em>有哪些"
],
"content": [
"每个人都看看很多电影,说下你近10年看过比较好的电影,比如《釜山行》,《行尸走肉》,《宙斯和<em>老王</em>的故事》"
]
}
}
]
}
}
修改高亮样式
使用highlight属性,可以增加属性,修改高亮样式
标签 | 说明 |
---|---|
pre_tags | 前置标签 |
post_tags | 后置标签 |
fields | 需要高亮的字段 |
# 修改字体颜色为黄色
GET /soulboy_high_light_test/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"title": "电影"
}
},
{
"match": {
"content": "老王"
}
}
]
}
},
"highlight": {
"pre_tags": "<font color='yellow'>",
"post_tags": "</font>",
"fields": [{"title":{}},{"content":{}}]
}
}
# 查询结果
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 2,
"successful": 2,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 0.43035197,
"hits": [
{
"_index": "soulboy_high_light_test",
"_id": "1",
"_score": 0.43035197,
"_source": {
"title": "豆瓣电影2028年最新好看的电影推荐",
"content": "每年都有新电影上线,2028年最新好看的电影有不少,豆瓣电影上线了《釜山行》,《行尸走肉》,《老王往事》精彩电影"
},
"highlight": {
"title": [
"豆瓣<font color='yellow'>电影</font>2028年最新好看的<font color='yellow'>电影</font>推荐"
],
"content": [
"每年都有新电影上线,2028年最新好看的电影有不少,豆瓣电影上线了《釜山行》,《行尸走肉》,《<font color='yellow'>老王</font>往事》精彩电影"
]
}
},
{
"_index": "soulboy_high_light_test",
"_id": "2",
"_score": 0.36774224,
"_source": {
"title": "写下你认为好看的电影有哪些",
"content": "每个人都看看很多电影,说下你近10年看过比较好的电影,比如《釜山行》,《行尸走肉》,《宙斯和老王的故事》"
},
"highlight": {
"title": [
"写下你认为好看的<font color='yellow'>电影</font>有哪些"
],
"content": [
"每个人都看看很多电影,说下你近10年看过比较好的电影,比如《釜山行》,《行尸走肉》,《宙斯和<font color='yellow'>老王</font>的故事》"
]
}
}
]
}
}
聚合查询
对大量数据聚合统计处理,类似Mysql数据库操作里面的group by 分组、sum、avg、max等函数处理。
是 Elasticsearch 中强大的功能之一,根据数据进行分组、过滤、计算和统计,提取有关数据集信息,进行数据分析
数据可视化大屏里面的饼状图、柱状图、折线图、仪表盘数据等都是聚合查询的关键应用
常见聚合用途和应用场景案例
用途 | 描述 |
---|---|
聚合指标(Aggregation Metrics) | Avg Aggregation:计算文档字段的平均值。 |
Sum Aggregation:计算文档字段的总和。 | |
Min Aggregation:找到文档字段的最小值。 | |
Max Aggregation:找到文档字段的最大值。 | |
聚合桶(Aggregation Buckets) | Terms Aggregation:基于字段值将文档分组到不同的桶中。 |
Date Histogram Aggregation:按日期/时间字段创建时间间隔的桶。 | |
Range Aggregation:根据字段值的范围创建桶。 | |
嵌套聚合(Nested Aggregations) | |
聚合过滤(Aggregation Filtering) |
指标聚合 metric
对数据集求最大、最小、和、平均值等指标的聚合
选项 | 说明 |
---|---|
index | 要执行聚合查询的索引名称 |
size | 设置为 0 来仅返回聚合结果,而不返回实际的搜索结果,这里将hits改为0表示返回的原始数据变为0 |
aggs | 指定聚合操作的容器 |
aggregation_name | 聚合名称,可以自定义 |
aggregation_type | 聚合操作的类型,例如 terms、avg、sum 等 |
aggregation_field | 聚合操作的目标字段,对哪些字段进行聚合 |
基本语法格式如下
GET /index/_search
{
"size": 0,
"aggs": {
"aggregation_name": {
"aggregation_type": {
"aggregation_field": "field_name"
// 可选参数
}
}
// 可以添加更多的聚合
}
}
桶聚合 bucketing
对数据集进行分组group by,然后在组上进行指标聚合,在 ES 中称为分桶,桶聚合bucketing
选项 | 说明 |
---|---|
index | 替换为要执行聚合查询的索引名称 |
size | 设置为 0 来仅返回聚合结果,而不返回实际的搜索结果,这里将hits改为0表示返回的原始数据变为0 |
aggs | 指定聚合操作的容器 |
aggregation_name | 聚合名称,可以自定义 |
bucket_type | 替换为特定的桶聚合类型(如 terms、date_histogram、range 等) |
bucket_option_name 和 bucket_option_value | 替换为特定桶聚合选项的名称和值。 |
sub_aggregation_name | 替换为子聚合的名称 |
sub_aggregation_type | 替换为特定的子聚合类型(如 sum、avg、max、min 等) |
sub_aggregation_option_name 和 sub_aggregation_option_value | 替换为特定子聚合选项的名称和值 |
基本语法格式如下
GET /index/_search
{
"size": 0,
"aggs": {
"aggregation_name": {
"bucket_type": {
"bucket_options": {
"bucket_option_name": "bucket_option_value",
...
},
"aggs": {
"sub_aggregation_name": {
"sub_aggregation_type": {
"sub_aggregation_options": {
"sub_aggregation_option_name": "sub_aggregation_option_value",
...
}
}
}
}
}
}
}
}
示例
数据环境准备
# 创建索引
PUT /sales
{
"mappings": {
"properties": {
"product": {
"type": "keyword"
},
"sales": {
"type": "integer"
}
}
}
}
# 批量插入数据
POST /sales/_bulk
{"index": {}}
{"product": "iPhone", "sales": 4}
{"index": {}}
{"product": "Samsung", "sales": 60}
{"index": {}}
{"product": "iPhone", "sales": 100}
{"index": {}}
{"product": "Samsung", "sales": 80}
{"index": {}}
{"product": "soulboy手机", "sales": 50}
{"index": {}}
{"product": "soulboy手机", "sales": 5000}
{"index": {}}
{"product": "soulboy手机", "sales": 200}
聚合查询:分别按照商品名称product
进行分组
# 分别按照商品名称(`product`)进行分组
GET /sales/_search
{
"aggs":{//聚合操作
"product_group":{//名称,随意起名
"terms":{//分组
"field":"product"//分组字段
}
}
}
}
# 查询结果
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 7,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "sales",
"_id": "F1ryeZEBm34NyhWJFqUf",
"_score": 1,
"_source": {
"product": "iPhone",
"sales": 4
}
},
{
"_index": "sales",
"_id": "GFryeZEBm34NyhWJFqUf",
"_score": 1,
"_source": {
"product": "Samsung",
"sales": 60
}
},
{
"_index": "sales",
"_id": "GVryeZEBm34NyhWJFqUf",
"_score": 1,
"_source": {
"product": "iPhone",
"sales": 100
}
},
{
"_index": "sales",
"_id": "GlryeZEBm34NyhWJFqUf",
"_score": 1,
"_source": {
"product": "Samsung",
"sales": 80
}
},
{
"_index": "sales",
"_id": "G1ryeZEBm34NyhWJFqUf",
"_score": 1,
"_source": {
"product": "soulboy手机",
"sales": 50
}
},
{
"_index": "sales",
"_id": "HFryeZEBm34NyhWJFqUf",
"_score": 1,
"_source": {
"product": "soulboy手机",
"sales": 5000
}
},
{
"_index": "sales",
"_id": "HVryeZEBm34NyhWJFqUf",
"_score": 1,
"_source": {
"product": "soulboy手机",
"sales": 200
}
}
]
},
"aggregations": {
"product_group": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "soulboy手机",
"doc_count": 3
},
{
"key": "Samsung",
"doc_count": 2
},
{
"key": "iPhone",
"doc_count": 2
}
]
}
}
}
聚合查询:查询结果将返回每个产品的名称和销售总量
计算每组的销售总量,使用了 terms
聚合和sum
聚合来实现
# 计算每组的销售总量,使用了 `terms`聚合和`sum` 聚合来实现
GET /sales/_search
{
"size": 0,
"aggs": {
"product_sales": {
"terms": {
"field": "product"
},
"aggs": {
"total_sales": {
"sum": {
"field": "sales"
}
}
}
}
}
}
# 查询结果
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 7,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"product_sales": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "soulboy手机",
"doc_count": 3,
"total_sales": {
"value": 5250
}
},
{
"key": "Samsung",
"doc_count": 2,
"total_sales": {
"value": 140
}
},
{
"key": "iPhone",
"doc_count": 2,
"total_sales": {
"value": 104
}
}
]
}
}
}
示例:指标聚合metric
- 对数据集求最大、最小、和、平均值等指标的聚合,称为 指标聚合 metric
- 比如 max、min、avg、sum等函数使用
示例:聚合查询max
- 假设有一个电商网站的销售记录索引,包含商品名称和销售价格字段
- 使用
max
聚合查询来获取产品价格的最高值。
# 数据准备
POST /sales_v1/_doc
{ "product_name": "手机", "price": 1000 }
POST /sales_v1/_doc
{ "product_name": "电视", "price": 1500 }
POST /sales_v1/_doc
{ "product_name": "小滴课堂老王的黑丝", "price": 4500 }
# 使用 max 聚合查询来获取产品价格的最高值。
GET /sales_v1/_search
{
"size": 0,
"aggs": {
"max_price": {
"max": {
"field": "price"
}
}
}
}
# 输出结果
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 6,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"max_price": {
"value": 4500
}
}
}
示例:聚合查询min
- 一个学生考试成绩索引,包含学生姓名和考试分数字段。
- 使用
min
聚合查询来获取学生的最低考试分数。
# 数据准备
POST /exam_scores/_doc
{ "student_name": "小滴课堂-大钊", "score" : 80 }
POST /exam_scores/_doc
{ "student_name": "老王", "score" : 90 }
POST /exam_scores/_doc
{ "student_name": "小滴课堂-D哥", "score" : 40 }
# 使用 min 聚合查询来获取学生的最低考试分数。
GET /exam_scores/_search
{
"size": 0,
"aggs": {
"min_score": {
"min": {
"field": "score"
}
}
}
}
# 输出结果
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 3,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"min_score": {
"value": 40
}
}
}
示例:聚合查询avg
- 数据准备(同上):一个学生考试成绩索引,包含学生姓名和考试分数字段。
- 使用
avg
聚合查询来计算学生的平均考试分数
# 使用avg聚合查询来计算学生的平均考试分数
GET /exam_scores/_search
{
"size": 0,
"aggs": {
"avg_score": {
"avg": {
"field": "score"
}
}
}
}
# 输出结果
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 3,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"avg_score": {
"value": 70
}
}
}
示例:聚合查询sum
- 假设有一个电商网站的销售记录索引,包含商品名称和销售数量字段。
- 使用
sum
聚合查询来计算销售记录的总销售数量。
# 数据准备
POST /sales_order/_doc
{ "product_name": "手机", "sales_count" : 100 }
POST /sales_order/_doc
{ "product_name": "电视", "sales_count" : 50 }
POST /sales_order/_doc
{ "product_name": "小滴课堂永久会员", "sales_count" : 999 }
# 使用 sum 聚合查询来计算销售记录的总销售数量
GET /sales_order/_search
{
"size": 0,
"aggs": {
"total_sales": {
"sum": {
"field": "sales_count"
}
}
}
}
# 输出结果
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 3,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"total_sales": {
"value": 1149
}
}
}
示例:桶聚合Terms
对数据集进行分组group by,然后在组上进行指标聚合,在 ES 中称为分桶,桶聚合bucketing
基本语法
# 解析
index: 替换为要执行聚合查询的索引名称。
aggregation_name: 替换为自定义的聚合名称。
bucket_type: 替换为特定的桶聚合类型(如 terms、date_histogram、range 等)。
bucket_option_name 和 bucket_option_value: 替换为特定桶聚合选项的名称和值。
sub_aggregation_name: 替换为子聚合的名称。
sub_aggregation_type: 替换为特定的子聚合类型(如 sum、avg、max、min 等)。
sub_aggregation_option_name 和 sub_aggregation_option_value: 替换为特定子聚合选项的名称和值
GET /index/_search
{
"size": 0,
"aggs": {
"aggregation_name": {
"bucket_type": {
"bucket_options": {
"bucket_option_name": "bucket_option_value",
...
},
"aggs": {
"sub_aggregation_name": {
"sub_aggregation_type": {
"sub_aggregation_options": {
"sub_aggregation_option_name": "sub_aggregation_option_value",
...
}
}
}
}
}
}
}
}
示例:分桶聚合查询 - Terms
假设有一个在线书店的图书销售记录索引,包含图书名称和销售数量字段。
# 创建索引库
PUT /book_sales
{
"mappings": {
"properties": {
"book_title": {
"type": "keyword"
},
"sales_count": {
"type": "integer"
}
}
},
"settings": {
"number_of_shards": 2,
"number_of_replicas": 0
}
}
# 批量插入数据
POST /book_sales/_bulk
{ "index": {} }
{ "book_title": "Elasticsearch in Action", "sales_count" : 100 }
{ "index": {} }
{ "book_title": "灵动课堂微服务最佳实践", "sales_count" : 50 }
{ "index": {} }
{ "book_title": "海量数据项目大课", "sales_count" : 80 }
{ "index": {} }
{ "book_title": "灵动课堂面试宝典", "sales_count" : 120 }
{ "index": {} }
{ "book_title": "数据结构与算法之美", "sales_count" : 90 }
{ "index": {} }
{ "book_title": "Python编程快速上手", "sales_count" : 70 }
{ "index": {} }
{ "book_title": "灵动课堂面试宝典", "sales_count" : 110 }
{ "index": {} }
{ "book_title": "灵动课堂Java核心技术", "sales_count" : 200 }
{ "index": {} }
{ "book_title": "深入理解计算机系统", "sales_count" : 150 }
{ "index": {} }
{ "book_title": "灵动课堂Java核心技术", "sales_count" : 80 }
# 使用 terms 聚合查询将图书按销售数量进行分桶,并获取每个分桶内的销售数量总和。
GET /book_sales/_search
{
"size": 0,
"aggs": {
"book_buckets": {
"terms": {
"field": "book_title",
"size": 10
},
"aggs": {
"total_sales": {
"sum": {
"field": "sales_count"
}
}
}
}
}
}
# 查询结果
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 2,
"successful": 2,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"book_buckets": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "灵动课堂Java核心技术",
"doc_count": 2,
"total_sales": {
"value": 280
}
},
{
"key": "灵动课堂面试宝典",
"doc_count": 2,
"total_sales": {
"value": 230
}
},
{
"key": "Elasticsearch in Action",
"doc_count": 1,
"total_sales": {
"value": 100
}
},
{
"key": "Python编程快速上手",
"doc_count": 1,
"total_sales": {
"value": 70
}
},
{
"key": "数据结构与算法之美",
"doc_count": 1,
"total_sales": {
"value": 90
}
},
{
"key": "海量数据项目大课",
"doc_count": 1,
"total_sales": {
"value": 80
}
},
{
"key": "深入理解计算机系统",
"doc_count": 1,
"total_sales": {
"value": 150
}
},
{
"key": "灵动课堂微服务最佳实践",
"doc_count": 1,
"total_sales": {
"value": 50
}
}
]
}
}
}
示例:桶聚合Date Histogram
将日期类型的字段按照固定的时间间隔进行分桶,并对每个时间间隔内的文档进行进一步的操作和计算
基本语法
# 解析
index:替换为要执行聚合查询的索引名称。
date_histogram_name:替换为自定义的 date_histogram 聚合名称。
date_field_name:替换为要聚合的日期类型字段名。
interval_expression:指定用于分桶的时间间隔。时间间隔可以是一个有效的日期格式(如 1d、1w、1M),也可以是一个数字加上一个时间单位的组合(如 7d 表示 7 天,1h 表示 1 小时)。
sub_aggregation:指定在每个日期桶内进行的子聚合操作。
sub_aggregation_type:替换单独子聚合操作的类型,可以是任何有效的子聚合类型。
GET /index/_search
{
"size": 0,
"aggs": {
"date_histogram_name": {
"date_histogram": {
"field": "date_field_name",
"interval": "interval_expression"
},
"aggs": {
"sub_aggregation": {
"sub_aggregation_type": {}
}
}
}
}
}
示例:分桶聚合查询 - Date Histogram
一个电商网站的订单索引,包含订单日期和订单金额字段。
需求:使用 date_histogram
聚合查询将订单按日期进行分桶,并计算每个分桶内的订单金额总和。
# 数据准备:一个电商网站的订单索引,包含订单日期和订单金额字段。
POST /order_history/_bulk
{ "index": {} }
{ "order_date": "2025-01-01", "amount" : 100 ,"book_title": "灵动课堂Java核心技术"}
{ "index": {} }
{ "order_date": "2025-02-05", "amount" : 150, "book_title": "灵动课堂面试宝典" }
{ "index": {} }
{ "order_date": "2025-03-02", "amount" : 500 ,"book_title": "灵动课堂Java核心技术"}
{ "index": {} }
{ "order_date": "2025-05-02", "amount" : 250 , "book_title": "灵动课堂面试宝典"}
{ "index": {} }
{ "order_date": "2025-05-05", "amount" : 10 ,"book_title": "灵动课堂微服务最佳实践"}
{ "index": {} }
{ "order_date": "2025-02-18", "amount" : 290 , "book_title": "灵动课堂微服务最佳实践"}
# 使用 date_histogram 聚合查询将订单按日期进行分桶,并计算每个分桶内的订单金额总和。
GET /order_history/_search
{
"size": 0,
"aggs": {
"sales_per_month": {
"date_histogram": {
"field": "order_date",
"calendar_interval": "month",
"format": "yyyy-MM"
},
"aggs": {
"total_sales": {
"sum": {
"field": "amount"
}
}
}
}
}
}
# 查询结果
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 6,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"sales_per_month": {
"buckets": [
{
"key_as_string": "2025-01",
"key": 1735689600000,
"doc_count": 1,
"total_sales": {
"value": 100
}
},
{
"key_as_string": "2025-02",
"key": 1738368000000,
"doc_count": 2,
"total_sales": {
"value": 440
}
},
{
"key_as_string": "2025-03",
"key": 1740787200000,
"doc_count": 1,
"total_sales": {
"value": 500
}
},
{
"key_as_string": "2025-04",
"key": 1743465600000,
"doc_count": 0,
"total_sales": {
"value": 0
}
},
{
"key_as_string": "2025-05",
"key": 1746057600000,
"doc_count": 2,
"total_sales": {
"value": 260
}
}
]
}
}
}
示例:桶聚合Range
将字段的值划分为不同的范围,并将每个范围内的文档分配给相应的桶,对这些范围进行各种操作和计算。
基本语法
# 解析
index:替换为要执行聚合查询的索引名称。
range_name:替换为自定义的 range 聚合名称。
field_name:替换为要聚合的字段名。
ranges:指定范围数组,每个范围使用 key、from 和 to 参数进行定义。
key:范围的唯一标识符。
from:范围的起始值(包含)。
to:范围的结束值(不包含)。
sub_aggregation:指定在每个范围内进行的子聚合操作。
sub_aggregation_type:替换单独子聚合操作的类型,可以是任何有效的子聚合类型。
GET /index/_search
{
"size": 0,
"aggs": {
"range_name": {
"range": {
"field": "field_name",
"ranges": [
{ "key": "range_key_1", "from": from_value_1, "to": to_value_1 },
{ "key": "range_key_2", "from": from_value_2, "to": to_value_2 },
...
]
},
"aggs": {
"sub_aggregation": {
"sub_aggregation_type": {}
}
}
}
}
}
示例:分桶聚合查询 - Range
# 数据准备:一个在线商店的商品索引,包括商品名称和价格字段
POST /product_v4/_bulk
{ "index": {} }
{ "product_name": "灵动课堂永久会员", "price" : 2000 }
{ "index": {} }
{ "product_name": "JVM专题课程", "price" : 200 }
{ "index": {} }
{ "product_name": "SpringBoot3.X最佳实践", "price" : 300 }
{ "index": {} }
{ "product_name": "高并发项目大课", "price" : 1500 }
{ "index": {} }
{ "product_name": "海量数据项目大课", "price" : 4120 }
{ "index": {} }
{ "product_name": "监控告警Prometheus最佳实践", "price" : 180 }
{ "index": {} }
{ "product_name": "全栈工程师学习路线", "price" : 250 }
{ "index": {} }
{ "product_name": "自动化测试平台大课", "price" : 4770 }
{ "index": {} }
{ "product_name": "灵动课堂-里昂分手最佳实践", "price" : 400 }
{ "index": {} }
{ "product_name": "灵动课堂-秃秃会所按摩往事", "price" : 150 }
# 使用 range 聚合查询将商品按价格范围进行分桶,并计算每个分桶内的商品数量。 key不写会根据范围自动生成
GET /product_v4/_search
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 100 }, //*-100.0
{ "from": 100, "to": 200 }, //100.0-200.0
{ "from": 200 } //200.0-*
]
}
}
}
}
# 查询结果
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"price_ranges": {
"buckets": [
{
"key": "*-100.0",
"to": 100,
"doc_count": 0
},
{
"key": "100.0-200.0",
"from": 100,
"to": 200,
"doc_count": 2
},
{
"key": "200.0-*",
"from": 200,
"doc_count": 8
}
]
}
}
}
SpringBoot3整合ElasticSearch8
SpringBoot3环境准备和注意事项
后端业务开发里面,应用程序如果需要接入搜索引擎,都离不开整合客户端
环境说明
本地 JDK17安装(SpringBoot3.X要求JDK17),没相关环境的可以去Oracle官网安装下JDK17,项目开发,快速创建
https://start.spring.io/
依赖包导入
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
ES8客户端整合SpringBoot3
ElasticSearch是搜索引擎,作为服务端程序,提供了HTTP的Restful接口接入;因此多个不同的语言都可以轻松接入ES搜索功能
ES官方针对java推出多个客户端
版本 | 描述 | 备注 |
---|---|---|
Java Low Level REST Client | 基于低级别的 REST 客户端,通过发送原始 HTTP 请求与 Elasticsearch 进行通信。自己拼接好的字符串,并且自己解析返回的结果;兼容所有的Elasticsearch版本 | 有继续迭代维护 |
Java High Level REST Client | 基于低级别 REST 客户端,提供了更高级别的抽象,简化了与 Elasticsearch 的交互。提供了更易用的 API,封装了底层的请求和响应处理逻辑,提供了更友好和可读性更高的代码。自动处理序列化和反序列化 JSON 数据,适用于大多数常见的操作,如索引、搜索、聚合等。对于较复杂的高级功能和自定义操作,可能需要使用低级别 REST 客户端或原生的 Elasticsearch REST API | 7.1版本标记过期 |
Java API Client | 新版的java API Client是一个用于与Elasticsearch服务器进行通信的Java客户端库,封装了底层的Transport通信,并提供了同步和异步调用、流式和函数式调用等方法 | 8.X版本开始推荐使用 |
SpringBoot如何整合Elastic Search
方案选择 | 描述 |
---|---|
Elasticsearch Api Client | Elasticsearch 官方提供的高级客户端库 |
Spring Data Elasticsearch | 基于 Spring Data 的标准化数据访问技术,简化了与 Elasticsearch 的集成, 提供了丰富的 CRUD 操作和查询方法,简化了数据访问,包括自动化的索引管理和映射,Spring Data Elasticsearch 对于一些高级功能和复杂查询可能不够灵活,需要额外定制处理 |
- 方案一:Elasticsearch Api Client
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.5.3</version>
</dependency>
- 方案二:Spring Data Elasticsearch
是一个用于简化数据访问和持久化的开发框架,提供了一组统一的 API 和抽象。
与各种数据存储技术(如关系型数据库、NoSQL 数据库、Elasticsearch 等)进行交互变得更加容易
Spring Data核心模块
col1 | col2 |
---|---|
Spring Data JPA | 用于与关系型数据库进行交互,基于 JPA(Java Persistence API)标准,提类似于 Repository 的接口,通过继承这些接口并声明方法,自动生成常见的数据CRUD |
Spring Data MongoDB | 用于与 MongoDB NoSQL 数据库进行交互,提供一种类似于 Repository 的接口,通过继承这些接口并声明方法,自动生成常见的数据CRUD |
Spring Data Redis | 用于与 Redis 键值存储数据库进行交互,提供 Repository 的接口,通过继承这些接口并声明方法,自动生成常见的数据CRUD |
Spring Data Elasticsearch | 用于与 Elasticsearch 搜索引擎进行交互,提供 Repository 的接口,通过继承这些接口并声明方法,自动生成常见的数据CRUD |
SpringBoot3项目整合SpringData框架
<!--这个starter里面就是依赖 spring-data-elasticsearch-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
- 增加配置
spring.application.name=soulboy-es-project spring.elasticsearch.uris=http://192.168.10.68:9200
ElasticsearchTemplate
什么是ElasticsearchTemplate
- 是 Spring Data Elasticsearch 提供的一个核心类,是 ElasticsearchClient 的一个具体实现
- 用于在 Spring Boot 中操作 Elasticsearch 进行数据的存取和查询
- 提供了一组方法来执行各种操作,如
保存、更新、删除和查询文档,执行聚合操作等
ElasticsearchTemplate 常用API
方法 | 描述 |
---|---|
save(Object) | 保存一个对象到 Elasticsearch 中 |
index(IndexQuery) | 使用 IndexQuery 对象执行索引操作 |
delete(String, String) | 删除指定索引和类型的文档 |
get(String, String) | 获取指定索引和类型的文档 |
update(UpdateQuery) | 使用 UpdateQuery 对象执行更新操作 |
search(SearchQuery, Class) | 执行搜索查询,并将结果映射为指定类型的对象 |
count(SearchQuery, Class) | 执行搜索查询,并返回结果的计数 |
ElasticsearchTemplate 常见注解(都是属于spring data elasticsearch)
注解名 | 注解属性功能说明 |
---|---|
@Id | 指定主键 |
@Document | 指定实体类和索引对应关系 |
indexName:索引名称 | |
@Field | 指定普通属性 |
type:对应Elasticsearch中属性类型,使用FiledType枚举快速获取 | |
text:类型能被分词 | |
keywords:不能被分词 | |
index:是否创建索引,作为搜索条件时index必须为true | |
analyzer:指定分词器类型 | |
format:指定数据的格式 |
示例
- 创建DTO
package com.soulboy.soulboy_es_project.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.time.LocalDateTime;
@Document(indexName = "video")
public class VideoDTO {
@Id
@Field(type = FieldType.Text, index = false)
private Long id;
@Field(type = FieldType.Text)
private String title;
@Field(type = FieldType.Text)
private String description;
@Field(type = FieldType.Keyword)
private String category;
@Field(type = FieldType.Integer)
private Integer duration;
@Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second)
private LocalDateTime createTime;
public VideoDTO(){}
public VideoDTO(Long id, String title, String description, Integer duration,String category) {
this.id = id;
this.title = title;
this.description = description;
this.duration = duration;
this.createTime = LocalDateTime.now();
this.category = category;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public Integer getDuration() {
return duration;
}
public void setDuration(Integer duration) {
this.duration = duration;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "VideoDTO{" +
"id=" + id +
", title='" + title + '\'' +
", description='" + description + '\'' +
", category='" + category + '\'' +
", duration=" + duration +
", createTime=" + createTime +
'}';
}
}
- 创建测试方法
package com.soulboy.soulboy_es_project;
import com.soulboy.soulboy_es_project.model.VideoDTO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.IndexOperations;
@SpringBootTest
class SoulboyEsProjectApplicationTests {
@Autowired
private ElasticsearchTemplate restTemplate;
/**
* 判断索引是否存在索引
*/
@Test
void existsIndex() {
IndexOperations indexOperations = restTemplate.indexOps(VideoDTO.class);
boolean exists = indexOperations.exists();
System.out.println(exists);
}
/**
* 创建索引
*/
@Test
void createIndex() {
// spring data es所有索引操作都在这个接口
IndexOperations indexOperations = restTemplate.indexOps(VideoDTO.class);
// 是否存在,存在则删除
if(indexOperations.exists()){
indexOperations.delete();
}
// 创建索引
indexOperations.create();
//设置映射: 在正式开发中,几乎不会使用框架创建索引或设置映射,这是架构或者管理员的工作,不适合使用代码实现
restTemplate.indexOps(VideoDTO.class).putMapping();
}
/**
* 删除索引
*/
@Test
void deleteIndex() {
IndexOperations indexOperations = restTemplate.indexOps(VideoDTO.class);
boolean delete = indexOperations.delete();
System.out.println(delete);
}
}
- 以上创建Mapping的方式不靠谱(会遇到数据类型的不一致)
# 创建Mapping
PUT /video
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"title": {
"type": "text"
},
"description": {
"type": "text"
},
"category": {
"type": "keyword"
},
"duration": {
"type": "integer"
},
"createTime": {
"type": "date"
}
}
}
}
GET /video/_mapping
- 查询video索引库
# 查询video索引库
GET /video/_mapping
GET /video/_search
Document操作
- 新增文档
/**
* 新增文档
*/
@Test
void insert(){
VideoDTO videoDTO = new VideoDTO();
videoDTO.setId(1L);
videoDTO.setTitle("灵动课堂架构大课和Spring Cloud");
videoDTO.setCreateTime(LocalDateTime.now());
videoDTO.setDuration(100);
videoDTO.setCategory("后端");
videoDTO.setDescription("这个是综合大型课程,包括了jvm,redis,新版spring boot3.x,架构,监控,性能优化,算法,高并发等多方面内容");
VideoDTO saved = restTemplate.save(videoDTO);
System.out.println(saved);
}
- 根据主键查询
/**
* 根据主键查询
* 查询结果:VideoDTO{id=1, title='灵动课堂架构大课和Spring Cloud', description='这个是综合大型课程,包括了jvm,redis,新版spring boot3.x,架构,监控,性能优化,算法,高并发等多方面内容', category='后端', duration=100, createTime=2024-08-25T13:55:05}
*/
@Test
void searchById(){
VideoDTO videoDTO = restTemplate.get("1", VideoDTO.class);
assert videoDTO != null;
System.out.println(videoDTO);
}
- 根据id删除
/**
* 根据id删除
*/
@Test
void deleteById() {
String delete = restTemplate.delete("1", VideoDTO.class);
System.out.println(delete);
}
- 更新文档
/**
* 更新文档
*/
@Test
void update(){
VideoDTO videoDTO = new VideoDTO();
videoDTO.setId(1L);
videoDTO.setTitle("灵动课堂架构大课和Spring Cloud V2");
videoDTO.setCreateTime(LocalDateTime.now());
//更新视频时长
videoDTO.setDuration(102);
videoDTO.setCategory("后端");
videoDTO.setDescription("这个是综合大型课程,包括了jvm,redis,新版spring boot3.x,架构,监控,性能优化,算法,高并发等多方面内容");
VideoDTO saved = restTemplate.save(videoDTO);
System.out.println(saved);
}
- 批量插入
/**
* 批量插入
*/
@Test
void batchInsert() {
List<VideoDTO> list = new ArrayList<>();
list.add(new VideoDTO(2L, "老王录制的按摩课程", "主要按摩和会所推荐", 123, "后端"));
list.add(new VideoDTO(3L, "冰冰的前端性能优化", "前端高手系列", 100042, "前端"));
list.add(new VideoDTO(4L, "海量数据项目大课", "超哥的后端+大数据综合课程", 5432345, "后端"));
list.add(new VideoDTO(5L, "灵动课堂永久会员", "可以看海量专题课程,IT技术持续充电平台", 6542, "后端"));
list.add(new VideoDTO(6L, "大钊-前端低代码平台", "高效开发底层基础平台,效能平台案例", 53422, "前端"));
list.add(new VideoDTO(7L, "自动化测试平台大课", "微服务架构下的spring cloud架构大课,包括jvm,效能平台", 6542, "后端"));
Iterable<VideoDTO> result = restTemplate.save(list);
System.out.println(result);
}
Query搜索(NativeQuery)
Query是Spring Data Elasticsearch的接口,有多种具体实现。
新版的搜索语法案例,查询采用新版的lambda表达式语法,更简洁。
条件 | 描述 |
---|---|
CriteriaQuery | 创建Criteria来搜索数据,而无需了解 Elasticsearch,查询的语法或基础知识,允许用户通过简单地连接和组合,指定搜索文档必须满足的对象来构建查询 |
StringQuery | 将Elasticsearch查询作为JSON字符串,更适合对Elasticsearch查询的语法比较了解的人,也更方便使用kibana或postman等客户端工具行进调试 |
NativeQuery | 复杂查询或无法使用CriteriaAPI 表达的查询时使用的类,例如在构建查询和使用聚合的场景 |
示例一:搜索全部
/**
* 查询所有
* 输出结果:[VideoDTO{id=2, title='老王录制的按摩课程', description='主要按摩和会所推荐', category='后端', duration=123, createTime=2024-08-25T14:00:30}, VideoDTO{id=3, title='冰冰的前端性能优化', description='前端高手系列', category='前端', duration=100042, createTime=2024-08-25T14:00:30}, VideoDTO{id=4, title='海量数据项目大课', description='超哥的后端+大数据综合课程', category='后端', duration=5432345, createTime=2024-08-25T14:00:30}, VideoDTO{id=5, title='灵动课堂永久会员', description='可以看海量专题课程,IT技术持续充电平台', category='后端', duration=6542, createTime=2024-08-25T14:00:30}, VideoDTO{id=6, title='大钊-前端低代码平台', description='高效开发底层基础平台,效能平台案例', category='前端', duration=53422, createTime=2024-08-25T14:00:30}, VideoDTO{id=7, title='自动化测试平台大课', description='微服务架构下的spring cloud架构大课,包括jvm,效能平台', category='后端', duration=6542, createTime=2024-08-25T14:00:30}]
*/
@Test
void searchAll(){
SearchHits<VideoDTO> search = restTemplate.search(Query.findAll(), VideoDTO.class);
List<SearchHit<VideoDTO>> searchHits = search.getSearchHits();
// 获得searchHits,进行遍历得到content
List<VideoDTO> videoDTOS = new ArrayList<>();
searchHits.forEach(hit -> {
videoDTOS.add(hit.getContent());
});
System.out.println(videoDTOS);
}
示例二:匹配搜索 -- NativeQuery
/**
* 匹配搜索:match查询 (NativeQuery)
* 输出结果: [VideoDTO{id=7, title='自动化测试平台大课', description='微服务架构下的spring cloud架构大课,包括jvm,效能平台', category='后端', duration=6542, createTime=2024-08-25T14:00:30}]
*/
@Test
void matchQuery(){
//构建查询条件
Query query = NativeQuery.builder().withQuery(q ->
q.match(m -> m
.field("description") //字段
.query("spring") //值
)).build();
//得到查询结果
SearchHits<VideoDTO> searchHits = restTemplate.search(query, VideoDTO.class);
// 获得searchHits,进行遍历得到content
List<VideoDTO> videoDTOS = new ArrayList<>();
searchHits.forEach(hit -> {
videoDTOS.add(hit.getContent());
});
System.out.println(videoDTOS);
}
示例三:分页搜索 -- NativeQuery
/**
* 分页查询
* 查询结果:[VideoDTO{id=2, title='老王录制的按摩课程', description='主要按摩和会所推荐', category='后端', duration=123, createTime=2024-08-25T14:00:30}, VideoDTO{id=3, title='冰冰的前端性能优化', description='前端高手系列', category='前端', duration=100042, createTime=2024-08-25T14:00:30}, VideoDTO{id=4, title='海量数据项目大课', description='超哥的后端+大数据综合课程', category='后端', duration=5432345, createTime=2024-08-25T14:00:30}]
* 查询结果:[2,3,4]
*/
@Test
void pageSearch() {
//构建查询条件(设置分页)
Query query = NativeQuery.builder().withQuery(Query.findAll())
.withPageable(Pageable.ofSize(3).withPage(0)).build();
//得到查询结果
SearchHits<VideoDTO> searchHits = restTemplate.search(query, VideoDTO.class);
// 获得searchHits,进行遍历得到content
List<VideoDTO> videoDTOS = new ArrayList<>();
searchHits.forEach(hit -> {
videoDTOS.add(hit.getContent());
});
System.out.println(videoDTOS);
}
示例四:搜索排序 -- NativeQuery
withSort() 需要传入 Sort 对象
,.by代表根据一个字段进行排序
方法 | 描述 |
---|---|
.ascending() | 默认的,正序排序 |
.descending() | 倒叙排序 |
/**
* 排序查询,根据时长降序排列
* 查询结果:[VideoDTO{id=4, title='海量数据项目大课', description='超哥的后端+大数据综合课程', category='后端', duration=5432345, createTime=2024-08-25T14:00:30}, VideoDTO{id=3, title='冰冰的前端性能优化', description='前端高手系列', category='前端', duration=100042, createTime=2024-08-25T14:00:30}, VideoDTO{id=6, title='大钊-前端低代码平台', description='高效开发底层基础平台,效能平台案例', category='前端', duration=53422, createTime=2024-08-25T14:00:30}, VideoDTO{id=5, title='灵动课堂永久会员', description='可以看海量专题课程,IT技术持续充电平台', category='后端', duration=6542, createTime=2024-08-25T14:00:30}, VideoDTO{id=7, title='自动化测试平台大课', description='微服务架构下的spring cloud架构大课,包括jvm,效能平台', category='后端', duration=6542, createTime=2024-08-25T14:00:30}, VideoDTO{id=2, title='老王录制的按摩课程', description='主要按摩和会所推荐', category='后端', duration=123, createTime=2024-08-25T14:00:30}]
*/
@Test
void sortSearch() {
//构建查询条件.设置分页.配置降序
Query query = NativeQuery.builder().withQuery(Query.findAll())
.withPageable(Pageable.ofSize(10).withPage(0))
.withSort(Sort.by("duration").descending()).build();
//得到查询结果
SearchHits<VideoDTO> searchHits = restTemplate.search(query, VideoDTO.class);
// 获得searchHits,进行遍历得到content
List<VideoDTO> videoDTOS = new ArrayList<>();
searchHits.forEach(hit -> {
videoDTOS.add(hit.getContent());
});
System.out.println(videoDTOS);
}
Query搜索(StringQuery)
什么是StringQuery
- 将Elasticsearch查询作为JSON字符串,
更适合对Elasticsearch查询的语法比较了解的人
- 也更方便使用kibana或postman等
客户端工具行进调试
示例一:布尔must查询 -- StringQuery
布尔must查询,搜索标题有架构关键词
,原始DSL查询有 spring关键字
,时长范围是10~6000
之间的document
- 原始DSL查询
# 原始DSL查询: 布尔must查询 -- StringQuery
GET /video/_search
{
"query": {
"bool": {
"must": [{
"match": {
"title": "架构"
}
}, {
"match": {
"description": "spring"
}
}, {
"range": {
"duration": {
"gte": 10,
"lte": 6000
}
}
}]
}
}
}
### 输出结果
{
"took": 729,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 4.841636,
"hits": [
{
"_index": "video",
"_id": "1",
"_score": 4.841636,
"_source": {
"_class": "com.soulboy.soulboy_es_project.model.VideoDTO",
"id": 1,
"title": "灵动课堂架构大课和Spring Cloud",
"description": "这个是综合大型课程,包括了jvm,redis,新版spring boot3.x,架构,监控,性能优化,算法,高并发等多方面内容",
"category": "后端",
"duration": 100,
"createTime": "2024-08-25T15:25:29"
}
}
]
}
}
- 布尔must查询 -- StringQuery
/**
* 布尔must查询:搜索标题有"架构"关键词,描述有"spring"关键字,时长范围是"10~6000"之间的document
* 查询结果:[VideoDTO{id=1, title='灵动课堂架构大课和Spring Cloud', description='这个是综合大型课程,包括了jvm,redis,新版spring boot3.x,架构,监控,性能优化,算法,高并发等多方面内容', category='后端', duration=100, createTime=2024-08-25T15:25:29}]
*/
@Test
void stringQuery() {
//构建原始DSL查询的字符串
String dsl = """
{"bool":{"must":[{"match":{"title":"架构"}},{"match":{"description":"spring"}},{"range":{"duration":{"gte":10,"lte":6000}}}]}}
""";
//构建查询条件
Query query = new StringQuery(dsl);
//得到查询结果
List<SearchHit<VideoDTO>> searchHitList = restTemplate.search(query, VideoDTO.class).getSearchHits();
// 获得searchHits,进行遍历得到content
List<VideoDTO> videoDTOS = new ArrayList<>();
searchHitList.forEach(hit -> {
videoDTOS.add(hit.getContent());
});
System.out.println(videoDTOS);
}
聚合搜索
- 方案一(重点,万能):
可以使用原始DSL进行处理
- 方案二(API会受到版本变化影响):
使用NativeQuery完成聚合搜
数据准备
示例一:聚合搜索 -- NativeQuery(方案二)
统计不同分类下的视频数量
/**
* 聚合查询 -- NativeQuery(方案二)
* 需求:统计不同分类下的视频数量
* 输出结果:
*/
@Test
void aggQuery() {
//构建查询条件 size() 指定字段大小
Query query = NativeQuery.builder().withAggregation("category_group", Aggregation.of(a -> a
.terms(ta -> ta.field("category").size(10)))).build();
//得到查询结果
SearchHits<VideoDTO> searchHits = restTemplate.search(query, VideoDTO.class);
System.out.println(searchHits);
//获取聚合数据
ElasticsearchAggregations aggregationsContainer = (ElasticsearchAggregations) searchHits.getAggregations();
Map<String, ElasticsearchAggregation> aggregationsMap = aggregationsContainer.aggregationsAsMap();
//获取对应名称的聚合
ElasticsearchAggregation categoryGroup = aggregationsMap.get("category_group");
Buckets<StringTermsBucket> buckets = categoryGroup.aggregation().getAggregate().sterms().buckets();
//打印聚合信息
buckets.array().forEach(bucket -> {
System.out.println("组名:"+bucket.key().stringValue() + ", 值" + bucket.docCount());
});
// 获得searchHits,进行遍历得到content
List<VideoDTO> videoDTOS = new ArrayList<>();
searchHits.forEach(hit -> {
videoDTOS.add(hit.getContent());
});
System.out.println(videoDTOS);
}
# 控制台输出
组名:后端,vaue=5
组名:前端,value=2
[VideoDTO{id=1, title='灵动课堂架构大课和Spring Cloud', description='这个是综合大型课程,包括了jvm,redis,新版spring boot3.x,架构,监控,性能优化,算法,高并发等多方面内容', category='后端', duration=100, createTime=2024-08-25T17:44:27},VideoDTO{id=2, title='老王录制的按摩课程', description='主要按摩和会所推荐', category='后端', duration=123, createTime=2024-08-25T14:00:30}, VideoDTO{id=3, title='冰冰的前端性能优化', description='前端高手系列', category='前端', duration=100042, createTime=2024-08-25T14:00:30}, VideoDTO{id=4, title='海量数据项目大课', description='超哥的后端+大数据综合课程', category='后端', duration=5432345, createTime=2024-08-25T14:00:30}, VideoDTO{id=5, title='灵动课堂永久会员', description='可以看海量专题课程,IT技术持续充电平台', category='后端', duration=6542, createTime=2024-08-25T14:00:30}, VideoDTO{id=6, title='大钊-前端低代码平台', description='高效开发底层基础平台,效能平台案例', category='前端', duration=53422, createTime=2024-08-25T14:00:30}, VideoDTO{id=7, title='自动化测试平台大课', description='微服务架构下的spring cloud架构大课,包括jvm,效能平台', category='后端', duration=6542, createTime=2024-08-25T14:00:30}]
性能优化
官方数据Elastic Search最高的性能可以达到,PB级别数据秒内相应
1PB=1024TB = 1024GB \* 1024GB
ES数量常规是亿级别为起点,之所以达不到官方的数据,多数是团队现有技术水平不够和业务场景不一样
Elastic Search常见性能优化
- 硬件资源优化
优化策略 | 描述 |
---|---|
内存分配 | 将足够的堆内存分配给Elasticsearch进程,以减少垃圾回收的频率,ElasticSearch推荐的最大JVM堆空间是30~32G, 所以分片最大容量推荐限制为30GB,30G heap 大概能处理的数据量 10T,如果内存很大如128G,可在一台机器上运行多个ES节点,比如业务的数据能达到200GB, 推荐最多分配7到8个分片 |
存储器选择 | 使用高性能的存储器,如SSD,以提高索引和检索速度,SSD的读写速度更快,适合高吞吐量的应用场景。 |
CPU和网络资源 | 根据预期的负载需求,配置合适的CPU和网络资源,以确保能够处理高并发和大数据量的请求 |
- 分片和副本优化
优化策略 | 描述 |
---|---|
合理设置分片数量 | 过多的分片会增加CPU和内存的开销,因此要根据数据量、节点数量和性能需求来确定分片的数量。一般建议每个节点上不超过20个分片 |
考虑副本数量 | 根据可用资源、数据可靠性和负载均衡等因素,设置合适的副本数量,至少应设置一个副本,以提高数据的冗余和可用性。不是副本越多,检索性能越高,增加副本数量会消耗额外的存储空间和计算资源 |
- 索引和搜索优化
优化策略 | 描述 |
---|---|
映射和数据类型 | 根据实际需求,选择合适的数据类型和映射设置避免不必要的字段索引 ,尽可能减少数据在硬盘上的存储空间。 |
分词和分析器 | 根据实际需求,选择合适的分词器和分析器,以优化搜索结果。了解不同分析器的性能特点,根据业务需求进行选择 |
查询和过滤器 | 使用合适的查询类型和过滤器,以减少不必要的计算和数据传输;尽量避免全文搜索和正则表达式等开销较大的查询操作。 |
- 缓存和缓冲区优化
优化策略 | 描述 |
---|---|
缓存大小 | 在Elasticsearch的JVM堆内存中配置合适的缓存大小,以加速热数据的访问,可以根据节点的角色和负载需求来调整缓存设置。 |
索引排序字段 | 选择合适的索引排序字段,以提高排序操作的性能,对于经常需要排序的字段,可以为其创建索引,或者选择合适的字段数据类型。 |
- 监控和日志优化
优化策略 | 描述 |
---|---|
监控集群性能 | 使用Elasticsearch提供的监控工具如Elastic Stack的Elasticsearch监控、X-Pack或其他第三方监控工具.实时监控集群的健康状态、吞吐量、查询延迟和磁盘使用情况等关键指标。 |
- 集群规划和部署
优化策略 | 描述 |
---|---|
多节点集群 | 使用多个节点组成集群,以提高数据的冗余和可用性。多节点集群还可以分布负载和增加横向扩展的能力。 |
节点类型和角色 | 根据节点的硬件配置和功能需求,将节点设置为合适的类型和角色.如数据节点、主节点、协调节点等,以实现负载均衡和高可用性。 |
- 性能测试和优化
优化策略 | 描述 |
---|---|
压力测试 | 使用性能测试工具模拟真实的负载,评估集群的性能极限和瓶颈。根据测试结果,优化硬件资源、配置参数和查询操作等。找出硬件的最佳ROI |
日常性能调优 | 过监控指标和日志分析,定期评估集群的性能表现,及时调整和优化配置,以满足不断变化的需求 。 |
- 升级和版本管理
优化策略 | 描述 |
---|---|
计划升级 | 定期考虑升级Elasticsearch版本,以获取新功能、性能改进和安全修复。在升级过程中,确保备份数据并进行合理的测试。 |
版本管理 | 跟踪Elasticsearch的发行说明和文档,了解新版本的特性和已知问题,并根据实际需求选择合适的版本。 |