目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

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更加专注一件事,那就是数据转换,格式化,等处理工作

Beats

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
DatabaseIndex(7.X版本前有Type,对比数据库中的表,新版取消了)
TableIndex
RowDocument
ColumnField

分片(shards)

  • 数据量特大,没有足够大的硬盘空间来一次性存储,且一次性搜索那么多的数据,响应跟不上
  • ES提供把数据进行分片存储,这样方便进行拓展和提高吞吐

image.png

副本(replicas)

分片的拷贝,当主分片不可用的时候,副本就充当主分片进行使用;索引分片的备份,shard和replica一般存储在不同的节点上,用来提高高可靠性
image.png

元数据

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"

正排索引的结构示意图如下:

DocumentIDTitleContent
1Apple iPhone 12The latest iPhone model
2Samsung Galaxy S21Powerful Android smartphone
3Microsoft Surface LaptopThin 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"

倒排索引的结构示意图如下:

TermDocuments
Apple1,3
iPhone1
121
latest1
Samsung2
Galaxy2
S212
Powerful2
Android2
smartphone2
Microsoft3
Surface3
Laptop3
Thin3
lightweight3

索引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分词等,还支持自定义分词器

image.png

### 标点符号切分
标点符号会被删除,并将连字符分隔为两个独立的词。
例如,"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
    }
  ]
}

IK分词器

是一个基于Java开发的开源中文分词器,用于将中文文本拆分成单个词语(词项)是针对中文语言的特点和需求而设计的,可以有效处理中文分词的复杂性和多样性 IK分词器多个版本

image.png

特点描述
高效且灵活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 DSLDomain-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
      }
    }
  }
}

分页、排序查询

分页查询

  • 可以使用 fromsize 参数进行分页查询
  • 可以指定要跳过的文档数量(from)和需要返回的文档数量(size
# 分页查询
GET /soulboy_shop_v1/_search
{
  "size": 3,
  "from": 0,
  "query": {
    "match_all": {}
  }
}

查询结果排序

  • sort字段可以进行排序 descasc
# 排序
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 过滤器查询 categorybooks 的产品:

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_namedescription 字段上执行了一个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"
      }
    }
  }
}

搜索高亮显示

日常搜索产品的时候,会有关键词显示不一样的颜色方便用户直观看到区别

image.png

数据准备

#创建索引库
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 中强大的功能之一,根据数据进行分组过滤计算统计提取有关数据集信息,进行数据分析

数据可视化大屏里面的饼状图柱状图折线图仪表盘数据等都是聚合查询的关键应用

image.png

常见聚合用途和应用场景案例

用途描述
聚合指标(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/

image.png

依赖包导入

<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搜索功能
image.png

ES官方针对java推出多个客户端

  Java API Client 官方地址

版本描述备注
Java Low Level REST Client基于低级别的 REST 客户端,通过发送原始 HTTP 请求与 Elasticsearch 进行通信。自己拼接好的字符串,并且自己解析返回的结果;兼容所有的Elasticsearch版本有继续迭代维护
Java High Level REST Client基于低级别 REST 客户端,提供了更高级别的抽象,简化了与 Elasticsearch 的交互。提供了更易用的 API,封装了底层的请求和响应处理逻辑,提供了更友好和可读性更高的代码。自动处理序列化和反序列化 JSON 数据,适用于大多数常见的操作,如索引、搜索、聚合等。对于较复杂的高级功能和自定义操作,可能需要使用低级别 REST 客户端或原生的 Elasticsearch REST API7.1版本标记过期
Java API Client新版的java API Client是一个用于与Elasticsearch服务器进行通信的Java客户端库,封装了底层的Transport通信,并提供了同步异步调用流式函数式调用等方法8.X版本开始推荐使用

SpringBoot如何整合Elastic Search

方案选择描述
Elasticsearch Api ClientElasticsearch 官方提供的高级客户端库
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

Spring Data框架

  是一个用于简化数据访问持久化的开发框架,提供了一组统一的 API 和抽象

  与各种数据存储技术(如关系型数据库、NoSQL 数据库、Elasticsearch 等)进行交互变得更加容易

Spring Data核心模块

col1col2
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 表达的查询时使用的类,例如在构建查询和使用聚合的场景

image.png

示例一:搜索全部

/**
	 * 查询所有
	 * 输出结果:[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查询的语法比较了解的人
  • 也更方便使用kibanapostman客户端工具行进调试

示例一:布尔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数量常规是亿级别为起点,之所以达不到官方的数据,多数是团队现有技术水平不够和业务场景不一样

image.png

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的发行说明和文档,了解新版本的特性和已知问题,并根据实际需求选择合适的版本。

        


作者:Soulboy