elasticsearch入门

docker 安装 elasticsearch、kibana

elasticsearch

创建存储目录

1
2
mkdir dockerEs
mkdir data

拉取镜像,启动

1
2
3
4
5
6
7
8
9
10
11
docker pull docker.elastic.co/elasticsearch/elasticsearch:8.10.4
docker run -d \
--name elasticsearch \
-p 9200:9200 \
-p 9300:9300 \
-e "discovery.type=single-node" \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "xpack.security.enabled=true" \
-e "ELASTIC_PASSWORD=elastic" \
-v /Users/qichuanhan/NoIconApplications/dockerEs/data:/usr/share/elasticsearch/data \
docker.elastic.co/elasticsearch/elasticsearch:8.10.4

kibana

kibana启动必须要一个新用户,不能使用elastic,因为elastic权限太高,如果使用kibana启动时会有提示然后启动不成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 创建用户
curl -X POST "http://localhost:9200/_security/user/kibana_user" \
-u elastic:elastic \
-H "Content-Type: application/json" \
-d '{
"password": "elastic",
"roles": ["kibana_system"],
"full_name": "Kibana Service User"
}'

# 查看用户
curl -X GET "http://localhost:9200/_security/user/kibana_user" \
-u elastic:elastic
1
2
3
4
5
6
7
8
9
10
docker pull kibana:8.10.4

docker run -d \
--name kibana \
-p 5601:5601 \
-e "ELASTICSEARCH_HOSTS=http://192.168.64.1:9200" \
-e "ELASTICSEARCH_USERNAME=kibana_user" \
-e "ELASTICSEARCH_PASSWORD=elastic" \
-e "I18N_LOCALE=zh-CN" \
kibana:8.10.4

概述

https://www.elastic.co/cn/elasticsearch/

https://www.elastic.co/cn/elastic-stack/

Elastic-stack是一个开源的数据分析和可视化平台,由Elasticsearch、Logstash、Kibana和Beats组成

它在各种应用场景中被广泛用于实时数据分析、搜索和可视化。

  • ElasticSearch

    • 一个分布式实时搜索和分析引擎,高性能和可伸缩性闻名,能够快速地存储、搜索和分析大量结构化和非结构化数据
    • 基于java开发,它是Elastic Stack的核心组件,提供分布式数据存储和搜索能力。
  • Logstash

    • 是一个用于数据收集、转换和传输的数据处理引擎,支持从各种来源(如文件、日志、数据库等)收集数据
    • 基于java开发,并对数据进行结构化、过滤和转换,然后将数据发送到Elasticsearch等目标存储或分析系统。
  • Kibana

    • 基于node.js开发,数据可视化和仪表盘工具,连接到Elasticsearch,通过简单易用的用户界面创建各种图表、图形和仪表盘
    • 帮助用户快速探索和理解数据,并进行强大的数据分析和可视化
  • Beats

    • 是轻量级的数据收集器,用于收集和发送各种类型的数据到Elasticsearch或Logstash
    • Beats提供了多种插件和模块,用于收集和传输日志、指标数据、网络数据和安全数据等
    • 注意
      • beats是用来优化logstash的,因为logstash消耗的性能比较多
      • 如果只是单纯的为了收集日志,使用logstash就有点大材小用了,另外有点浪费资源
      • 而beats是轻量级的用来收集日志的, 而logstash更加专注一件事,那就是数据转换,格式化,等处理工作

总结

  • Elastic Stack的架构主要是将各个组件连接起来形成数据处理和分析的流程
  • 数据可以通过Logstash的输入插件进行收集和处理,然后通过输出插件将数据发送到Elasticsearch进行存储和索引
  • Kibana可与Elasticsearch进行连接,提供灵活的可视化界面,将数据分析结果以图表、仪表盘等形式展示出来
  • Beats作为数据收集器,可以直接收集数据并将其发送到Elasticsearch或Logstash。
  • 整个Elastic Stack的架构是高度可扩展和灵活的,可以根据特定需求进行定制和扩展
  • 它被广泛应用于日志分析、实时监控、安全分析、商业智能和搜索等领域,为用户提供强大的数据处理和分析能力

什么是ElasticSearch

  • 是⼀个开源,是⼀个基于Apache Lucene库构建的Restful搜索引擎. Elasticsearch是在Solr之后⼏年推出的。

  • 它提供了⼀个分布式,多租户能⼒的全⽂搜索引擎,具有HTTP Web界⾯(REST)和⽆架构JSON⽂档

  • Elasticsearch的官⽅客户端库提供Java,Groovy,PHP,Ruby,Perl,Python,.NET和Javascript。

应用场景

  • 搜索引擎

    • 可以快速而准确地搜索大量的结构化和非结构化数据,用于构建搜索引擎、电商网站搜索等。
  • 实时日志分析

    • 提供了强大的实时索引功能和聚合功能,非常适合实时日志分析。
  • 数据分析和可视化

    • Elasticsearch与Kibana(Elastic Stack的一个组件)结合使用,可以进行复杂的数据分析和可视化。
  • 实时推荐系统

    • 基于Elasticsearch的实时搜索和聚合功能,可以用于构建实时推荐系统。
    • 根据用户的行为和兴趣,动态地推荐相关的内容和产品。
  • 电商商品搜索和过滤

    • Elasticsearch具有强大的搜索和过滤能力,使其成为构建电商网站的商品搜索和过滤引擎的理想选择。
    • 可以快速搜索和过滤商品属性、价格范围、地理位置等。
  • 地理空间数据分析

    • Elasticsearch内置了地理空间数据类型和查询功能,可以对地理位置进行索引和搜索
    • 适合于构建地理信息系统(GIS)、位置服务和地理数据分析应用。
  • 核心概念快速上手

    • 在新版Elasticsearch中,文档document就是一行记录(json),而这些记录存在于索引库(index)中, 索引名称必须是小写
    Mysql数据库 Elastic Search
    Database 7.X版本前有Type,对比数据库中的表,新版取消了
    Table Index
    Row Document
    Column Field
    • 元数据

      • Elasticsearch中以 “ _” 开头的属性都成为元数据,都有自己特定的意思
    • 分片shards

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

      • 分片的拷贝,当主分片不可用的时候,副本就充当主分片进行使用
      • 索引分片的备份,shard和replica一般存储在不同的节点上,用来提高高可靠性
      • 案例
        • 假如Elasticsearch中的每个索引分配3个主分片和1个副本
        • 如果集群中至少有两个节点,索引将会有3个主分片和另外3个复制分片(1个完全拷贝)这样每个索引总共有6个分片
主机A 主机B
分片数据集一(一~一千万) 分片数据集一副本(一~一千万)
分片数据集二(一千万~两千万) 分片数据集二副本(一千万~两千万)
分片数据集三(两千万~三千万) 分片数据集三副本(两千万~三千万)

总结

  • 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”
    • 正排索引的结构示意图如下
    1
    2
    3
    4
    5
    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
    • 正排索引的优势在于可以快速的查找某个文档里包含哪些词项。但是 正排不适用于查找包含某个词项的文档有哪些
    • 在数据库系统中,将正排索引类比为表格的结构,每一行是一个记录,每一列是一个字段
    学生ID 姓名 年龄 成绩
    1 lisi 20 90
    2 zhansan 18 85
    3 Carol 19 92
    4 David 21 88
    5 Ellen 22 91
  • 倒排索引(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”
      • 倒排索引的结构示意图如下:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      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
    • 案例二

      • 假设我们有以下三个文档
        • 文档1:我喜欢吃苹果
        • 文档2:我喜欢吃橙子和苹果
        • 文档3:我喜欢吃香蕉
      • 倒排索引的结构示意图如下
        • 关键词 “我”:文档1、文档2、文档3
        • 关键词 “喜欢”:文档1、文档2、文档3
        • 关键词 “吃”:文档1、文档2、文档3
        • 关键词 “苹果”:文档1、文档2
        • 关键词 “橙子”:文档2
        • 关键词 “香蕉”:文档3
      • 通过倒排索引,可以快速地找到包含特定关键词的文档或数据记录
    • 倒排索引记录了每个词语出现在哪些文档中,通过这种方式,我们可以非常快速地得知每个关键词分别出现在哪些文档中。

    • 当我们需要搜索包含特定关键词的文档时,可以直接查找倒排索引,找到包含该关键词的文档

  • 总结

    • 正排索引和倒排索引的结构和用途不同
    • 正排索引用于快速访问和提取文档的内容
    • 倒排索引用于快速定位和检索包含特定词语的文档

搜索引擎的分词

  • 什么是搜索引擎的分词

    • 在Elasticsearch 8.X中,分词(tokenization)是将文本内容拆分成独立的单词或词项(tokens)的过程
    • 分词是搜索引擎在建立索引和执行查询时的关键步骤,将文本拆分成单词,并构建倒排索引,可以实现更好的搜索和检索效果。
    • 案例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    假设我们有两个产品标题:

    "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)来处理文本内容
    • 分词器通常由一个或多个标记器组成,用于定义分词的具体规则
      • 以下是分词的一般过程:
        • 标记化(Tokenization):
          • 分词的第一步是将文本内容拆分成单个标记(tokens),标记可以是单词、数字、特殊字符等。
          • 标记化过程由标记器(tokenizer)执行,标记器根据一组规则将文本切分为标记。
        • 过滤(Filtering):
          • 标记化后,标记会进一步被过滤器(filters)处理。
          • 过滤器执行各种转换和操作,如转换为小写、去除停用词(stop words),词干提取(stemming),同义词扩展等。
        • 倒排索引(Inverted Indexing):
          • 分词处理完成后,Elasticsearch使用倒排索引(inverted index)来存储分词结果。
          • 倒排索引是一种数据结构,通过将标记与其所属文档进行映射,快速确定包含特定标记的文档。
        • 查询匹配:
          • 当执行查询时,查询的文本也会进行分词处理。
          • Elasticsearch会利用倒排索引来快速查找包含查询标记的文档,并计算相关性得分。
    • 常见的分词器,如Standard分词器、Simple分词器、Whitespace分词器、IK分词等,还支持自定义分词器
  • 默认的Standard分词器的分词规则

    • 标点符号切分:
      • 标点符号会被删除,并将连字符分隔为两个独立的词。
      • 例如,”Let’s go!” 会被切分为 “Let”, “s”, “go”。
    • 小写转换:
      • 所有的文本会被转换为小写形式。
      • 例如,”Hello World” 会被切分为 “hello”, “world”。
    • 停用词过滤:
      • 停用词(stop words)是在搜索中没有实际意义的常见词,如 “a”, “an”, “the” 等。
      • 停用词会被过滤掉,不会作为独立的词进行索引和搜索。
    • 词干提取:
      • 通过应用Porter2词干提取算法,将单词还原为其原始形式。
      • 例如,running -> run、swimming -> swim、jumped -> jump
    • 词分隔:
      • 按照空格将文本切分成词项(tokens)。

kibana 开发者工具(类似postman)

进入http://localhost:5601

使用elasticsearch的用户名密码登录,然后点击三条杠 显示出左侧抽屉菜单栏,选择management下的开发工具。

1
2
#查看集群健康情况
GET /_cluster/health

点击执行按钮,就可以执行了。

HTTP API

1
2
3
4
5
6
7
8
9
10
11
#查看集群健康情况
GET /_cluster/health

#查看分片情况
GET /_cat/shards?v=true&pretty

#查看节点分布情况
GET /_cat/nodes?v=true&pretty

#查看索引列表
GET /_cat/indices?v=true&pretty

索引操作(index)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

#创建索引
PUT /<index_name>
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
}
}

PUT /shop
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 1
}
}

#查看索引是否存在( 结果是200 和 404)
HEAD /<index_name>

HEAD /shop

#获取索引
GET /<index_name>

GET /shop

#更新索引设置
PUT /<index_name>/_settings
{
"settings": {
"number_of_replicas": 2
}
}

PUT /shop/_settings
{
"settings": {
"number_of_replicas": 2
}
}

# 删除索引
DELETE /<index_name>

DELETE /shop

文档操作(document)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#查询文档
GET /shop/_doc/1

# 新增文档(需要指定ID)
PUT /shop/_doc/1
{
"id":5555,
"title":"xxx",
"pv":144
}

# 新增文档(不指定ID),会自动生成id
POST /shop/_doc
{
"id":123,
"title":"xxxx",
"pv":244
}

# 修改(put和post都行,需要指定id)
PUT /shop/_doc/1
{
"id":999,
"title":"xxv2",
"pv":999,
"uv":55
}


POST /shop/_doc/1
{
"id":999,
"title":"xxxv3",
"pv":999,
"uv":559
}

# 搜索
GET /shop/_search

#字段解释
#took字段表示该操作的耗时(单位为毫秒)。
#timed_out字段表示是否超时。
#hits字段表示搜到的记录,数组形式。
#total:返回记录数,本例是1条。
#max_score:最高的匹配程度,本例是1.0

# 删除数据
DELETE /shop/_doc/1

#批量插入
#通过一次 POST 请求实现批量插入
#每个文档都由两部分组成:`index` 指令用于指定文档的元数据,`product_id` 为文档的唯一标识符
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 }

mapping操作

什么是Mapping

  • 类似于数据库中的表结构定义 schema,
  • 定义索引中的字段的名称,字段的数据类型,比如字符串、数字、布尔等

Dynamic Mapping(动态映射)

  • 用于在索引文档时自动检测和定义字段的数据类型
  • 当我们向索引中添加新文档时,Elasticsearch会自动检测文档中的各个字段,并根据它们的值来尝试推断字段类型
  • 常见的字段类型包括文本(text)、关键词(keyword)、日期(date)、数值(numeric)等
  • 动态映射具备自动解析和创建字段的便利性,但在某些情况下,由于字段类型的不确定性,动态映射可能会带来一些问题
  • 例如字段解析错误、字段类型不一致等,如果对字段类型有明确的要求,最好在索引创建前通过显式映射定义来指定字段类型

ElasticSearch常见的数据类型

  • 在 ES 7.X后有两种字符串类型:Text 和 Keyword
    • Text类型:用于全文搜索的字符串类型,支持分词和索引建立
    • Keyword类型:用于精确匹配的字符串类型,不进行分词,适合用作过滤和聚合操作。
  • Numeric类型:包括整数类型(long、integer、short、byte)和浮点数类型(double、float)。
  • Date类型:用于存储日期和时间的类型。
  • Boolean类型:用于存储布尔值(true或false)的类型。
  • Binary类型:用于存储二进制数据的类型。
  • Array类型:用于存储数组或列表数据的类型。
  • Object类型:用于存储复杂结构数据的类型

最高频使用的数据类型

  • text字段类型

    • text类型主要用于全文本搜索,适合存储需要进行全文本分词的文本内容,如文章、新闻等。
    • text字段会对文本内容进行分词处理,将文本拆分成独立的词项(tokens)进行索引
    • 分词的结果会建立倒排索引,使搜索更加灵活和高效。
    • text字段在搜索时会根据分词结果进行匹配,并计算相关性得分,以便返回最佳匹配的结果。
  • keyword字段类型

    • keyword类型主要用于精确匹配和聚合操作,适合存储不需要分词的精确值,如ID、标签、关键字等。
    • keyword字段不会进行分词处理,而是将整个字段作为一个整体进行索引和搜索
    • 这使得搜索只能从精确的值进行匹配,而不能根据词项对内容进行模糊检索。
    • keyword字段适合用于过滤和精确匹配,同时可以进行快速的基于精确值的聚合操作。
  • 总结

    • 在选择text字段类型和keyword字段类型时,需要根据具体的需求进行权衡和选择:

    • 如果需要进行全文本检索,并且希望根据分词结果计算相关性得分,以获得最佳的匹配结果,则选择text字段类型。

    • 如果需要进行精确匹配、排序或聚合操作,并且不需要对内容进行分词,则选择keyword字段类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# 查看索引的mapping
GET /<index_name>/_mapping

GET /my_index/_mapping

# 指定索引的mapping
PUT /my_index
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"title": {
"type": "text"
},
"price": {
"type": "float"
}
}
}
}

# 创建索引并插入文档
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": "xxx Elasticsearch Introduction",
"tags": ["search", "big data", "distributed system", "xx"],
"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/_search
{
"query": {
"match": {
"title": "Elasticsearch"
}
}
}

GET /my_index/_search
{
"query": {
"match": {
"tags": "data"
}
}
}

分词器API

分词器名称 主要特点描述
standard 默认分词器。按词切分,处理大多数语言,会转小写并过滤标点符号。
simple 遇到非字母字符(数字、空格等)就切分,并转小写。
whitespace 简单地按空格进行切分,不转小写。
stop simple 的基础上,增加过滤停用词(如 “a”, “the”, “is”)的功能。
keyword 不进行分词,将整个输入内容作为一个单独的词项输出。
pattern 使用正则表达式来分割文本(默认按非字符 \W+ 分割)。
language 针对特定语言(如 english, french)的分词器,遵循特定语言的规则。
fingerprint 用于去重检测。它会去重、排序然后组合词项生成一个”指纹”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#如何查看ES分词存储效果?
GET /_analyze
{
"analyzer": "分词器名称",
"text": "待分析的文本"
}

#字段是text类型
POST /my_index/_analyze
{
"field": "title",
"text": "This is some text to analyze"
}

#字段是text类型
POST /my_index/_analyze
{
"field": "title",
"text": "今天我在课堂学习数学"
}


#字段是keyword类型
POST /my_index/_analyze
{
"field": "tags",
"text": "This is some text to analyze"
}


#字段是keyword类型
POST /my_index/_analyze
{
"field": "tags",
"text": ["This is","课堂","Spring Boot" ]
}

#每个分词结果对象包含

#分词后的单词(token)
#开始位置(start_offset)
#结束位置(end_offset)
#类型(type)
#ALPHANUM是一种数据类型,表示一个字符串字段只包含字母和数字,并且不会进行任何其他的分词或处理
#它会忽略字段中的任何非字母数字字符(如标点符号、空格等),只保留字母和数字字符
#单词在原始文本中的位置(position)

Query DSL

什么是Query DSL

  • Query DSL(Domain-Specific Language)是一种用于构建搜索查询的强大的领域特定查询语言
  • 类似我们关系性数据库的SQL查询语法,
  • ES中用JSON结构化的方式定义和执行各种查询操作,在ES中进行高级搜索和过滤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 基本语法
GET /索引库名/_search
{
"query":{
"查询类型":{

}
}

# match 查询:用于执行全文搜索,它会将搜索查询与指定字段中的文本进行匹配
{
"query": {
"match": {
"title": "elasticsearch"
}
}
}

# `term` 查询:用于精确匹配一个指定字段的关键词,不进行分词处理。
{
"query": {
"term": {
"category": "books"
}
}
}

数据准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 数据准备
PUT /shop_v1
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"title": {
"type": "keyword"
},
"summary": {
"type": "text"
},
"price": {
"type": "float"
}
}
}
}

PUT /shop_v1/_bulk
{ "index": { "_index": "shop_v1" } }
{ "id": "1", "title": "Spring Boot","summary":"this is a summary Spring Boot video", "price": 9.99 }
{ "index": { "_index": "shop_v1" } }
{ "id": "2", "title": "java","summary":"this is a summary java video", "price": 19.99 }
{ "index": { "_index": "shop_v1" } }
{ "id": "3", "title": "Spring Cloud","summary":"this is a summary Spring Cloud video", "price": 29.99 }
{ "index": { "_index": "shop_v1" } }
{ "id": "4", "title": "Spring_Boot", "summary":"this is a summary Spring_Boot video","price": 59.99 }
{ "index": { "_index": "shop_v1" } }
{ "id": "5", "title": "SpringBoot","summary":"this is a summary SpringBoot video", "price": 0.99 }

match查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 查询全部数据(match_all) 是一种简单的查询,匹配索引中的所有文档
GET /shop_v1/_search
{
"query": {
"match_all": {}
}
}

#有条件查询数据
# match,对查询内容进行分词, 然后进行查询,多个词条之间是or的关系
# 然后在与文档里面的分词进行匹配,匹配度越高分数越高越前面
GET /shop_v1/_search
{
"query": {
"match": {
"summary": "Spring"
}
}
}

#包括多个词
GET /shop_v1/_search
{
"query": {
"match": {
"summary": "Spring Java"
}
}
}

term查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 完整关键词查询
# term查询,不会将查询条件分词,直接与文档里面的分词进行匹配
# 虽然match也可以完成,但是match查询会多一步进行分词,浪费资源
#keyword类型字段,ES不进行分词
GET /shop_v1/_search
{
"query": {
"term": {
"title": {
"value": "Spring Boot"
}
}
}
}

获取指定字段

1
2
3
4
5
6
7
8
9
10
11
12
13
#获取指定字段 
#某些情况场景下,不需要返回全部字段,太废资源,可以指定source返回对应的字段
GET /shop_v1/_search
{
"_source":["price","title"],
"query": {
"term": {
"title": {
"value": "Spring Boot"
}
}
}
}

range查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#`range` 查询

# 用于根据范围条件进行查询,例如指定价格在一定区间内的商品
#范围符号
# gte:大于等于
# gt:大于
# lte:小于等于
# lt:小于
GET /shop_v1/_search
{
"query": {
"range": {
"price": {
"gte": 5,
"lte": 100
}
}
}
}

分页

1
2
3
4
5
6
7
8
9
10
11
#分页查询
#可以使用 `from` 和 `size` 参数进行分页查询
#可以指定要跳过的文档数量(`from`)和需要返回的文档数量(`size`)
GET /shop_v1/_search
{
"size": 10,
"from": 0,
"query": {
"match_all": {}
}
}

排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#查询排序
#`sort`字段可以进行排序 `desc` 和 `asc`
GET /shop_v1/_search
{
"size": 10,
"from": 0,
"sort": [
{
"price": "asc"
}
],
"query": {
"match_all": {}
}
}

bool 查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#`bool` 查询

# 通过组合多个查询条件,使用布尔逻辑(与、或、非)进行复杂的查询操作
# 语法格式
#"must"关键字用于指定必须匹配的条件,即所有条件都必须满足
# "must_not"关键字指定必须不匹配的条件,即所有条件都不能满足
# "should"关键字指定可选的匹配条件,即至少满足一个条件
# filter 关键字 来对搜索结果进行筛选和过滤,仅返回符合特定条件的文档,而不改变搜索评分
{
"query": {
"bool": {
"must": [
// 必须匹配的条件
],
"must_not": [
// 必须不匹配的条件
],
"should": [
// 可选匹配的条件
],
"filter": [
// 过滤条件
]
}
}
}

GET /shop_v1/_search
{
"query": {
"bool": {
"must": [
{ "match": { "summary": "Cloud" }},
{ "range": { "price": { "gte": 5 }}}
]
}
}
}

# Filter查询对结果进行缓存,提高查询性能,用于数字范围、日期范围、布尔逻辑、存在性检查等各种过滤操作。
#语法格式
#"filter"关键字用于指定一个过滤条件,可以是一个具体的过滤器,如term、range等,也可以是一个嵌套的bool过滤器
# 过滤条件通常用于对结果进行筛选,并且比查询条件更高效
# 而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` 为文档的唯一标识符

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
{
"query": {
"bool": {
"filter": {
"term": {
"category": "books"
}
}
}
}
}

# 条件过滤
GET /product/_search
{
"query": {
"bool": {
"filter": {
"range": {
"price": {
"gte": 30,
"lte": 50
}
}
}
}
}
}

多字段搜索与短语搜索数据准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 数据准备
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" }

多字段搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#多字段搜索匹配
#业务查询,需要在多个字段上进行文本搜索,用 multi_match
#在 match的基础上支持对多个字段进行文本查询匹配
GET /index/_search
{
"query": {
"multi_match": {
"query": "要搜索的文本",
"fields": ["字段1", "字段2", ...]
}
}
}

# query:需要匹配的查询文本。
# fields:一个包含需要进行匹配的字段列表的数组。

#示例:
#在 `product_name` 和 `description` 字段上执行了一个`multi_match`查询
#将查询文本设置为 "iPhone",对这两个字段进行搜索,并返回包含匹配到的文档,这个是OR的关系,会有最佳匹配

GET /product_v2/_search
{
"query": {
"multi_match": {
"query": "iPhone",
"fields": ["product_name", "description"]
}
}
}

短语搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#短语搜索匹配
#是Elasticsearch中提供的一种高级匹配查询类型,用于执行精确的短语搜索
#相比于`match`查询,`match_phrase`会在匹配时考虑到单词之间的顺序和位置
GET /index/_search
{
"query": {
"match_phrase": {
"field_name": {
"query": "要搜索的短语"
}
}
}
}

# field_name:要进行匹配的字段名。
# query:要搜索的短语。

# 示例:
# 使用`match_phrase`查询在`description`字段上执行了一个短语搜索将要搜索的短语设置为 "classic novel"。
# 使用`match_phrase`查询,Elasticsearch将会返回包含 "classic novel" 短语的文档
#match_phrase短语搜索
GET /product_v2/_search
{
"query": {
"match_phrase": {
"description": "classic novel"
}
}
}

#match搜索,会进行分词
GET /product_v2/_search
{
"query": {
"match": {
"description": "classic novel"
}
}
}

单词拼写错误-fuzzy模糊查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
什么是fuzzy模糊匹配

* `fuzzy`查询是Elasticsearch中提供的一种模糊匹配查询类型,用在搜索时容忍一些拼写错误或近似匹配
* 使用`fuzzy`查询,可以根据指定的编辑距离(即词之间不同字符的数量)来模糊匹配查询词
* 拓展:编辑距离
* 是将一个术语转换为另一个术语所需的一个字符更改的次数。
* 比如
* 更改字符(box→fox)
* 删除字符(black→lack)
* 插入字符(sic→sick)
* 转置两个相邻字符(dgo→dog)
* fuzzy模糊查询是拼写错误的简单解决方案,但具有很高的 CPU 开销和非常低的精准度
* 用法和match基本一致,Fuzzy query的查询不分词

GET /index/_search
{
"query": {
"fuzzy": {
"field_name": {
"value": "要搜索的词",
"fuzziness": "模糊度"
}
}
}
}

解析:
* `field_name`:要进行模糊匹配的字段名。
* `value`:要搜索的词
* `fuzziness`参数指定了模糊度,常见值如下
- `0,1,2`
- 指定数字,表示允许的最大编辑距离,较低的数字表示更严格的匹配,较高的数字表示更松散的匹配
- fuziness的值,表示是针对每个词语而言的,而不是总的错误的数值
- `AUTO`:Elasticsearch根据词的长度自动选择模糊度
- 如果字符串的长度大于5,那 funziness 的值自动设置为2
- 如果字符串的长度小于2,那么 fuziness 的值自动设置为 0

# 指定模糊度2,更松散匹配
GET /shop_v1/_search
{
"query": {
"fuzzy": {
"summary": {
"value": "clo",
"fuzziness": "2"
}
}
}
}

# 指定模糊度1,更严格匹配
GET /shop_v1/_search
{
"query": {
"fuzzy": {
"summary": {
"value": "clo",
"fuzziness": "1"
}
}
}
}

# 使用自动检查,1个单词拼写错误
GET /shop_v1/_search
{
"query": {
"fuzzy": {
"summary": {
"value": "Sprina",
"fuzziness": "auto"
}
}
}
}

搜索高亮显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
Elastic Search搜索引擎如何做到高亮显示
* 在 ES 中,高亮语法用于在搜索结果中突出显示与查询匹配的关键词
* 高亮显示是通过标签包裹匹配的文本来实现的,通常是 `<em>` 或其他 HTML 标签
* 基本用法:在 highlight 里面填写要高亮显示的字段,可以填写多个

PUT /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 /high_light_test/_doc/1
{
"title": "课堂2028年最新好看的电影推荐",
"content": "每年都有新电影上线,2028年最新好看的电影有不少,课堂上线了《架构大课》,《低代码平台》,《老王往事》精彩电影"
}


PUT /high_light_test/_doc/2
{
"title": "写下你认为好看的电影有哪些",
"content": "每个人都看看很多电影,说下你近10年看过比较好的电影,比如《架构大课》,《海量数据项目大课》,《冰冰和老王的故事》"
}

#单条件查询高亮显示

GET /high_light_test/_search
{
"query": {
"match": {
"content": "电影"
}
},
"highlight": {
"fields": {
"content": {}
}
}
}

#组合多条件查询,highlight里面填写需要高亮的字段
GET /high_light_test/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"title": "课堂"
}
},
{
"match": {
"content": "老王"
}
}
]
}
},
"highlight": {
"fields": {
"title": {},
"content": {}
}
}
}

match查询,使用highlight属性,可以增加属性,修改高亮样式

* pre_tags:前置标签
* post_tags:后置标签
* fields:需要高亮的字段

GET /high_light_test/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"title": "课堂"
}
},
{
"match": {
"content": "老王"
}
}
]
}
},
"highlight": {
"pre_tags": "<font color='yellow'>",
"post_tags": "</font>",
"fields": [{"title":{}},{"content":{}}]
}
}

聚合数据准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 创建索引
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": "手机", "sales": 50}
{"index": {}}
{"product": "手机", "sales": 5000}
{"index": {}}
{"product": "手机", "sales": 200}

指标聚合

对数据集求最大、最小、和、平均值等指标的聚合,称为 指标聚合 metric

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
GET /index/_search
{
"size": 0,
"aggs": {
"aggregation_name": {
"aggregation_type": {
"aggregation_field": "field_name"
// 可选参数
}
}
// 可以添加更多的聚合
}
}

# 解析
index:要执行聚合查询的索引名称。
size: 设置为 0 来仅返回聚合结果,而不返回实际的搜索结果,这里将hits改为0表示返回的原始数据变为0
aggs:指定聚合操作的容器。

aggregation_name:聚合名称,可以自定义。
aggregation_type:聚合操作的类型,例如 terms、avg、sum 等。
aggregation_field:聚合操作的目标字段,对哪些字段进行聚合

聚合指标(Aggregation Metrics):

  • Avg Aggregation:计算文档字段的平均值。
  • Sum Aggregation:计算文档字段的总和。
  • Min Aggregation:找到文档字段的最小值。
  • Max Aggregation:找到文档字段的最大值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
max 数据准备
POST /sales_v1/_doc
{ "product_name": "a", "price": 1000 }

POST /sales_v1/_doc
{ "product_name": "b", "price": 1500 }

POST /sales_v1/_doc
{ "product_name": "b", "price": 4500 }

GET /sales_v1/_search
{
"size": 0,
"aggs": {
"max_price": {
"max": {
"field": "price"
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
min 数据准备
POST /exam_scores/_doc
{ "student_name": "a", "score" : 80 }

POST /exam_scores/_doc
{ "student_name": "b", "score" : 90 }

POST /exam_scores/_doc
{ "student_name": "c", "score" : 40 }

GET /exam_scores/_search
{
"size": 0,
"aggs": {
"min_score": {
"min": {
"field": "score"
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
GET /exam_scores/_search
{
"size": 0,
"aggs": {
"avg_score": {
"avg": {
"field": "score"
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
sum 数据准备
POST /sales_order/_doc
{ "product_name": "a", "sales_count" : 100 }

POST /sales_order/_doc
{ "product_name": "b", "sales_count" : 50 }

POST /sales_order/_doc
{ "product_name": "c", "sales_count" : 999 }


GET /sales_order/_search
{
"size": 0,
"aggs": {
"total_sales": {
"sum": {
"field": "sales_count"
}
}
}
}

桶聚合

对数据集进行分组group by,然后在组上进行指标聚合,在 ES 中称为分桶,桶聚合bucketing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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",
...
}
}
}
}
}
}
}
}
#解析
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: 替换为特定子聚合选项的名称和值

聚合桶(Aggregation Buckets):

  • Terms Aggregation:基于字段值将文档分组到不同的桶中。
  • Date Histogram Aggregation:按日期/时间字段创建时间间隔的桶。
  • Range Aggregation:根据字段值的范围创建桶。

按照商品名称(product)进行分组:

1
2
3
4
5
6
7
8
9
10
GET /sales/_search
{
"aggs":{//聚合操作
"product_group":{//名称,随意起名
"terms":{//分组
"field":"product"//分组字段
}
}
}
}

计算每组的销售总量,使用了 terms 聚合和 sum 聚合来实现,查询结果将返回每个产品的名称和销售总量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /sales/_search
{
"size": 0,
"aggs": {
"product_sales": {
"terms": {
"field": "product"
},
"aggs": {
"total_sales": {
"sum": {
"field": "sales"
}
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
数据准备
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": "a", "sales_count" : 50 }
{ "index": {} }
{ "book_title": "b", "sales_count" : 80 }
{ "index": {} }
{ "book_title": "c", "sales_count" : 120 }
{ "index": {} }
{ "book_title": "d", "sales_count" : 90 }
{ "index": {} }
{ "book_title": "e", "sales_count" : 70 }
{ "index": {} }
{ "book_title": "f", "sales_count" : 110 }
{ "index": {} }
{ "book_title": "h", "sales_count" : 200 }
{ "index": {} }
{ "book_title": "i", "sales_count" : 150 }
{ "index": {} }
{ "book_title": "g", "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"
}
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#Date Histogram
#将日期类型的字段按照固定的时间间隔进行分桶,并对每个时间间隔内的文档进行进一步的操作和计算
GET /index/_search
{
"size": 0,
"aggs": {
"date_histogram_name": {
"date_histogram": {
"field": "date_field_name",
"interval": "interval_expression"
},
"aggs": {
"sub_aggregation": {
"sub_aggregation_type": {}
}
}
}
}
}

#解析
index:替换为要执行聚合查询的索引名称。
date_histogram_name:替换为自定义的 date_histogram 聚合名称。
date_field_name:替换为要聚合的日期类型字段名。
interval_expression:指定用于分桶的时间间隔。时间间隔可以是一个有效的日期格式(如 1d、1w、1M),也可以是一个数字加上一个时间单位的组合(如 7d 表示 7 天,1h 表示 1 小时)。
sub_aggregation:指定在每个日期桶内进行的子聚合操作。
sub_aggregation_type:替换单独子聚合操作的类型,可以是任何有效的子聚合类型。


POST /order_history/_bulk
{ "index": {} }
{ "order_date": "2025-01-01", "amount" : 100 ,"book_title": "a"}
{ "index": {} }
{ "order_date": "2025-02-05", "amount" : 150, "book_title": "b" }
{ "index": {} }
{ "order_date": "2025-03-02", "amount" : 500 ,"book_title": "a"}
{ "index": {} }
{ "order_date": "2025-05-02", "amount" : 250 , "book_title": "b"}
{ "index": {} }
{ "order_date": "2025-05-05", "amount" : 10 ,"book_title": "c"}
{ "index": {} }
{ "order_date": "2025-02-18", "amount" : 290 , "book_title": "c"}


# 使用 `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"
}
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# Range
# 将字段的值划分为不同的范围,并将每个范围内的文档分配给相应的桶,对这些范围进行各种操作和计算
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": {}
}
}
}
}
}

#解析
index:替换为要执行聚合查询的索引名称。
range_name:替换为自定义的 range 聚合名称。
field_name:替换为要聚合的字段名。
ranges:指定范围数组,每个范围使用 key、from 和 to 参数进行定义。
key:范围的唯一标识符。
from:范围的起始值(包含)。
to:范围的结束值(不包含)。
sub_aggregation:指定在每个范围内进行的子聚合操作。
sub_aggregation_type:替换单独子聚合操作的类型,可以是任何有效的子聚合类型。

POST /product_v4/_bulk
{ "index": {} }
{ "product_name": "a", "price" : 2000 }
{ "index": {} }
{ "product_name": "b", "price" : 200 }
{ "index": {} }
{ "product_name": "c", "price" : 300 }
{ "index": {} }
{ "product_name": "d", "price" : 1500 }
{ "index": {} }
{ "product_name": "e", "price" : 4120 }
{ "index": {} }
{ "product_name": "f", "price" : 180 }
{ "index": {} }
{ "product_name": "h", "price" : 250 }
{ "index": {} }
{ "product_name": "i", "price" : 4770 }
{ "index": {} }
{ "product_name": "g", "price" : 400 }
{ "index": {} }
{ "product_name": "k", "price" : 150 }

# 使用 `range` 聚合查询将商品按价格范围进行分桶,并计算每个分桶内的商品数量。
GET /product_v4/_search
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 100 },
{ "from": 100, "to": 200 },
{ "from": 200 }
]
}
}
}
}

深分页

1
2
3
4
5
6
7
8
GET /shop_v1/_search
{
"size": 10,
"from": 0,
"query": {
"match_all": {}
}
}

Es 分页查询的原理跟mysql是一致的,都是遍历所有匹配的文档直到达到指定的起始点(from),然后返回从这一点开始的size个文档。

但是es 不支持from + size 的和不能超过es索引的index.max_result_window设置,默认为10000。这意味着如果你设置from为9900,size为100,查询将会成功。但如果from为9900,size为101,则会失败。

scroll

Scroll api 在es中的主要目的是为了能够遍历大量的数据,它通常用于数据导出或者进行大规模的数据分析。可以用于处理大量数据的深度分页问题。

1
2
3
4
5
6
7
GET /shop_v1/_search?scroll=1m
{
"size": 10,
"query": {
"match_all": {}
}
}

如上方式初始化一个带有scroll参数的搜索请求。这个请求返回一个scroll ID, 用于后续的滚动。scroll参数指定了scroll的有效期,例如1m表示一分钟。

接下来就可以使用返回的scroll ID 来获取下一批数据。每次请求也会更新scroll ID的有效期。

1
2
3
4
5
GET /_search/scroll
{
"scroll":"1m",
"scroll_id":"your_scroll_id"
}

重复以上操作直到到达想要的页数。比如第10页,则需要执行9次滚动操作,然后第10次请求将返回第10页的数据。

1
2
3
4
5
# 所有结果获取完毕后,手动清理 scroll_id
DELETE /_search/scroll
{
"scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAC..."
}

特点:

1.避免重复排序:

在传统的分页方式中,每次分页请求都需要对所有匹配的数据进行排序,以确定分页的起点。scroll避免了这种重复排序,因为它保持了一个游标。

2.稳定视图:

Scroll提供了对数据的“稳定视图”。当你开始一个scroll时,es会保持搜索时刻换数据快照,这意味着即使数据随后被修改,返回的结果仍然是一致的。

3.减少资源消耗:

由于不需要重复排序,scroll减少了对CPU和内存的消耗,特别是对于大数据集。

Scroll非常适合于处理需要访问大量数据但不需要快速响应的场景,如数据导出、备份或大规模数据分析。

但是使用scroll api 进行分页并不高效,因为你需要先获取对所有前面页的数据。scroll api 主要用于遍历整个索引或大量数据,而不是用于快速访问特定页数的数据。

search_after

search_after是es中用于实现深度分页的一种机制。与传统的分页方法(from和size参数)不同,search_after允许你基于上一次查询的结果 来获取下一批数据,这在处理大量数据时特别有效。

在第一次查询时,你需要定义一个排序规则。不需要指定search_after参数:

1
2
3
4
5
6
7
8
9
10
11
GET /shop_v1/_search
{
"size": 10,
"query": {
"match_all": {}
},
"sort":[
{"timestamp":"asc"},
{"id":"asc"}
]
}

这个查询按timestamp字段排序,并在相同timestamp的情况下按_id排序。

在后续的查询中,使用上一次查询结果中最后一条记录的排序值。

1
2
3
4
5
6
7
8
9
10
11
12
GET /shop_v1/_search
{
"size": 10,
"query": {
"match_all": {}
},
"sort":[
{"timestamp":"asc"},
{"id":"asc"}
],
"search_after":[1609459200000, 10000]
}

在这个例子中,search_after数组包含了timestamp和_id的值,对应于上一次查询结果的最后一条记录。

search_after可以有效解决深度分页问题,原因如下:

1.避免重复排序:

与传统的分页方式不同,search_after不需要处理每个分页请求中所有先前页面上的数据。这大大减少了处理数据的工作量。

2.提高查询效率:

由于不需要重复计算和跳过大量先前页面上的数据,search_after方法能显著提高查询效率,尤其是在访问数据集靠前靠后部分的数据时。

但是这个方案有一些局限,一方面需要有一个全局唯一的字段用来排序,另外虽然一次分页查询时不需要处理先前页面中的数据,但实际需要依赖上一个页面中的查询结果。

使用场景 实现方式 优点 缺点
传统分页 适用于小数据集和用户界面中的标准分页,如网站上的列表分页。 通过指定from(起始位置)和size(页面大小)来实现分页。 实现简单,适用于小数据集,易于理解和使用。 不适用于深度分页。当from值很大时,性能急剧下降。Elasticsearch默认限制from + size不超过10000。
Scroll 适用于大规模数据的导出、备份或处理,而不是实时用户请求。 初始化一个scroll请求,然后使用返回的scroll id来连续地获取后续数据。 可以有效处理大量数据。提供了数据快照,保证了查询过程中数据的一致性。 不适合实时请求。初始化scroll会占用更多资源,因为它在后端维护了数据的状态。
Search_after 适用于深度分页和大数据集的遍历。 基于上一次查询结果的排序值来获取下一批数据。 解决了深度分页的性能问题。更适合于处理大数据量,尤其是当需要顺序遍历整个数据集时。 不适用于随机页访问。需要精确的排序机制,并在每次请求中维护状态。

对于小型数据集和需要随机页面访问的标准分页场景,传统的分页是最简单和最直接的选择。

对于需要处理大量数据但不需要随机页面访问的场景,尤其是深度分页,search_after提供了更好的性能和更高的效率。

当需要处理非常大的数据集并且对数据一致性有要求时(如数据导出或备份),Scroll API是一个更好的选择。

IK分词器

默认的Standard分词器对中文支持不是很友好。

什么是IK分词器

  • 注意:Elastic Search版本和IK分词器版本需要对应

  • 特点

    • 高效且灵活
      • IK分词器采用了多种算法和数据结构,以提供高效的分词速度。
      • 支持细粒度的分词,可以根据应用需求进行灵活的配置。
    • 分词准确性
      • IK分词器使用了词典和规则来进行分词,可以准确地将句子拆分成词语。
      • 还提供了词性标注功能,可以帮助识别词语的不同含义和用途。
    • 支持远程扩展词库
      • IK分词器可以通过配置和加载外部词典,扩展分词的能力
      • 用户可以根据实际需求,添加自定义的词典,提升分词准确性和覆盖范围。
    • 兼容性与集成性
      • IK分词器兼容了Lucene和Elasticsearch等主流搜索引擎,可以方便地集成到这些系统中。
      • 提供了相应的插件和配置选项,使得分词器的集成变得简单。

IK有两种颗粒度的拆分

  • ik_smart: 会做最粗粒度的拆分

  • ik_max_word(常用): 会将文本做最细粒度的拆分

1
2
3
4
5
6
7
8
9
10
11
12
GET /_analyze
{
"text":"今天星期一,我今天去课堂学习spring cloud",
"analyzer":"ik_smart"
}


GET /_analyze
{
"text":"今天星期一,我今天去课堂学习spring cloud",
"analyzer":"ik_max_word"
}

es(直接在主机上)安装IK

1
2
3
4
5
6
7
8
9
#解压,上传到Elastic Search的plugins目录
#重启Elastic Search即可


#关闭Elasticsearch节点
bin/elasticsearch -d

#检查进程是否已经停止
ps -ef | grep elasticsearch

es(docker)安装IK

方法一:容器内直接安装

1
2
3
4
5
6
7
docker exec -it <your-es-container-name> /bin/bash

./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v8.10.4/elasticsearch-analysis-ik-8.10.4.zip

./bin/elasticsearch-plugin install https://get.infini.cloud/elasticsearch/analysis-ik/8.10.4

docker restart <your-es-container-name>

方法二:Dockerfile 构建自定义镜像

1
2
3
4
5
6
7
8
9
FROM docker.elastic.co/elasticsearch/elasticsearch:8.10.4

# 安装IK分词器
RUN ./bin/elasticsearch-plugin install --batch https://get.infini.cloud/elasticsearch/analysis-ik/8.10.4

# 解决权限问题
USER root
RUN chown -R elasticsearch:elasticsearch /usr/share/elasticsearch/plugins/ik/
USER elasticsearch
1
2
# 打镜像
docker build -t es-8.10.4-with-ik .

验证

1
2
3
4
5
6
7
8
9
10
11
GET /_analyze
{
"analyzer": "ik_smart",
"text": "待分析的文本"
}

GET /_analyze
{
"analyzer": "ik_max_word",
"text": "待分析的文本"
}

spring boot 集成

ES官方针对java推出多个客户端进行接入ES,也分两种

  • 更旧版的ES会用TransportClient(7.0版本标记过期

  • Java Low Level REST Client(有继续迭代维护

    • 基于低级别的 REST 客户端,通过发送原始 HTTP 请求与 Elasticsearch 进行通信。
    • 自己拼接好的字符串,并且自己解析返回的结果;兼容所有的Elasticsearch版本
  • Java High Level REST Client(7.1版本标记过期

    • 基于低级别 REST 客户端,提供了更高级别的抽象,简化了与 Elasticsearch 的交互。
    • 提供了更易用的 API,封装了底层的请求和响应处理逻辑,提供了更友好和可读性更高的代码。
    • 自动处理序列化和反序列化 JSON 数据,适用于大多数常见的操作,如索引、搜索、聚合等。
    • 对于较复杂的高级功能和自定义操作,可能需要使用低级别 REST 客户端或原生的 Elasticsearch REST API
  • Java API Client(8.X版本开始推荐使用

    • Elasticsearch在7.1版本之前使用的Java客户端是Java REST Client
    • 从7.1版本开始Elastic官方将Java REST Client标记为弃用(deprecated),推荐使用新版Java客户端Java API Client
    • 新版的java API Client是一个用于与Elasticsearch服务器进行通信的Java客户端库
    • 封装了底层的Transport通信,并提供了同步和异步调用、流式和函数式调用等方法
    • 官网文档地址

SpringBoot如何整合Elastic Search

  • 方案一:使用 Elasticsearch 官方提供的高级客户端库 - Elasticsearch Api Client
1
2
3
4
5
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.5.3</version>
</dependency>
  • 方案二:使用 Spring Data Elasticsearch

    基于 Spring Data 的标准化数据访问技术,简化了与 Elasticsearch 的集成。

​ 提供了丰富的 CRUD 操作和查询方法,简化了数据访问,包括自动化的索引管理和映射

​ Spring Data Elasticsearch 对于一些高级功能和复杂查询可能不够灵活,需要额外定制处理

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
1
2
3
4
5
6
7
8
spring:
application:
name: springboot-es-test
elasticsearch:
uris:
- http://localhost:9200
username: elastic
password: elastic
  • 什么是ElasticsearchTemplate

    • 是 Spring Data Elasticsearch 提供的一个核心类,是 ElasticsearchClient 的一个具体实现
    • 用于在 Spring Boot 中操作 Elasticsearch 进行数据的存取和查询
    • 提供了一组方法来执行各种操作,如保存、更新、删除和查询文档,执行聚合操作等
  • ElasticsearchTemplate 的一些常用方法

    • 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指定实体类和索引对应关系

      1
      indexName:索引名称
    • @Field指定普通属性

      1
      2
      3
      4
      5
      6
      7
      8
      9
      type 对应Elasticsearch中属性类型,使用FiledType枚举快速获取。

      text 类型能被分词

      keywords 不能被分词

      index 是否创建索引,作为搜索条件时index必须为true

      analyzer 指定分词器类型。

什么是新版的ElasticSearch的Query接口

  • Query是Spring Data Elasticsearch的接口,有多种具体实现

    • CriteriaQuery
      • 创建Criteria来搜索数据,而无需了解 Elasticsearch 查询的语法或基础知识
      • 允许用户通过简单地连接和组合,指定搜索文档必须满足的对象来构建查询
    • StringQuery
      • 将Elasticsearch查询作为JSON字符串,更适合对Elasticsearch查询的语法比较了解的人
      • 也更方便使用kibana或postman等客户端工具行进调试
    • NativeQuery
      • 复杂查询或无法使用CriteriaAPI 表达的查询时使用的类,例如在构建查询和使用聚合的场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import lombok.Data;
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;

@Data
@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;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239

@SpringBootTest
class SpringbootEsTestApplicationTests {

@Autowired
private ElasticsearchTemplate restTemplate;

@Autowired
private ElasticsearchOperations elasticsearchOperations;

/**
* 判断索引是否存在索引
*/
@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);
}

@Test
void insert(){
VideoDTO videoDTO = new VideoDTO();
videoDTO.setId(1L);
videoDTO.setTitle("ddddddd");
videoDTO.setCreateTime(LocalDateTime.now());
videoDTO.setDuration(100);
videoDTO.setCategory("后端");
videoDTO.setDescription("bbbb");

VideoDTO saved = restTemplate.save(videoDTO);
System.out.println(saved);
}

@Test
void update(){
VideoDTO videoDTO = new VideoDTO();
videoDTO.setId(1L);
videoDTO.setTitle("a.png");
videoDTO.setCreateTime(LocalDateTime.now());
videoDTO.setDuration(102);
videoDTO.setCategory("后端");
videoDTO.setDescription("a.png");

VideoDTO saved = restTemplate.save(videoDTO);
System.out.println(saved);
}

@Test
void batchInsert() {
List<VideoDTO> list = new ArrayList<>();
list.add(new VideoDTO(2L, "a", "aaaa", 123, "后端"));
list.add(new VideoDTO(3L, "b", "bbb", 100042, "前端"));
list.add(new VideoDTO(4L, "c", "cccc", 5432345, "后端"));
list.add(new VideoDTO(5L, "d", "dddd", 6542, "后端"));
list.add(new VideoDTO(6L, "e", "eeee", 53422, "前端"));
list.add(new VideoDTO(7L, "f", "ffff", 6542, "后端"));


Iterable<VideoDTO> result = restTemplate.save(list);
System.out.println(result);
}

@Test
void searchById(){
VideoDTO videoDTO = restTemplate.get("3", VideoDTO.class);
assert videoDTO != null;
System.out.println(videoDTO);
}

@Test
void deleteById() {
String delete = restTemplate.delete("2", VideoDTO.class);
System.out.println(delete);
}

/**
* 查询所有
*/
@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);
}

/**
* match查询
*/
@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);
}

/**
* 分页查询
*/
@Test
void pageSearch() {
System.out.println("开始-------------------");

NativeQuery query = NativeQuery.builder().withQuery(q -> q
.matchPhrase(m -> m
.field("description") //字段
.query("*spring*") //值
))
.withPageable(Pageable.ofSize(3).withPage(0)).build();
System.out.println(query.getQuery());

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);
System.out.println("结束-------------------");
}

/**
* 排序查询,根据时长降序排列
*/
@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);
}

@Test
void stringQuery() {

//搜索标题有 架构 关键词,描述有 spring关键字,时长范围是 10~6000之间的
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);
}

/**
* 聚合查询
*/
@Test
void aggQuery() {
Query query = NativeQuery.builder()
.withAggregation("category_group", Aggregation.of(a -> a
.terms(ta -> ta.field("category").size(2))))
.build();

SearchHits<VideoDTO> searchHits = restTemplate.search(query, VideoDTO.class);

//获取聚合数据
ElasticsearchAggregations aggregationsContainer = (ElasticsearchAggregations) searchHits.getAggregations();
Map<String, ElasticsearchAggregation> aggregations = Objects.requireNonNull(aggregationsContainer).aggregationsAsMap();

//获取对应名称的聚合
ElasticsearchAggregation aggregation = aggregations.get("category_group");
Buckets<StringTermsBucket> buckets = aggregation.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);
}

}

日志的打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 分页查询
*/
@Test
void pageSearch() {
System.out.println("开始-------------------");

NativeQuery query = NativeQuery.builder().withQuery(q -> q
.matchPhrase(m -> m
.field("description") //字段
.query("*spring*") //值
))
.withPageable(Pageable.ofSize(3).withPage(0)).build();
System.out.println(query.getQuery());

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);
System.out.println("结束-------------------");
}
1
2
3
4
5
6
7
// 可以打印出query的dsl语句
System.out.println(query.getQuery());
// 可以打印出分页的dsl语句
System.out.println(query.getPageable());
// Query: {"match_phrase":{"description":{"query":"*spring*"}}}
// Page request [number: 0, size 3, sort: UNSORTED]
// 其他以此类推

elasticsearch入门
http://hanqichuan.com/2025/09/26/监控/elasticsearch入门/
作者
韩启川
发布于
2025年9月26日
许可协议