Elasticsearch

0.使用场景

数据库的模糊搜索功能单一,匹配条件非常苛刻,必须恰好包含用户搜索的关键字。

而在搜索引擎中,用户输入出现个别错字,或者用拼音搜索、同义词搜索都能正确匹配到数据。

综上,在面临海量数据的搜索,或者有一些复杂搜索需求的时候,推荐使用专门的搜索引擎来实现搜索功能。

image-20240517164744539

0.1 全文搜索

Elasticsearch 凭借其强大、可扩展和快速的搜索功能,在全文搜索场景中表现出色。

它通常用于支持大型网站和应用程序的搜索功能,允许用户执行复杂的查询并获得近乎实时的响应

0.2 实时分析

Elasticsearch 能够实时执行分析,因此适用于跟踪实时数据(如用户活动、交易或传感器输出)的仪表盘。这种能力使企业能够根据最新信息及时做出决策。

0.3 机器学习

通过在 X-Pack 中添加机器学习功能,Elasticsearch 可以自动检测数据中的异常、模式和趋势。这对于预测分析、个性化和提高运营效率非常有用。

0.4 地理数据应用

Elasticsearch 通过地理空间索引和搜索功能支持地理数据。这对于需要管理和可视化地理信息(如地图和基于位置的服务)的应用非常有用,使执行邻近搜索和基于位置的数据可视化成为可能。

0.5 日志和事件数据分析

企业使用 Elasticsearch 聚合、监控和分析各种来源的日志和事件数据。它是 ELK 堆栈(Elasticsearch、Logstash、Kibana)的关键组件,该堆栈常用于管理系统和应用程序日志,以发现问题并监控系统健康状况。

0.6 安全信息和事件管理(SIEM)

Elasticsearch 可用作 SIEM 的工具,帮助企业实时分析安全事件。这对于检测、分析和响应安全事件和漏洞至关重要。

上述每个用例都利用了 Elasticsearch 的优势(如可扩展性、速度和灵活性)来处理不同的数据类型和复杂的查询,为数据驱动型应用提供了重要价值。

1.Elasticsearch(ES)

ES是一款非常强大的开源搜索引擎,可以帮助我们从海量数据中快速找到需要的内容。

Elasticsearch结合Kibana,Logstash,beats是一整套技术栈,被叫做==ELK==。经常用来做日志收集、系统监控和状态分析等等:

image-20240430171212212

1.1 安装

1.1.1 安装elasticsearch

通过下面的Docker命令即可安装单机版本的elasticsearch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#先在tar所在目录下打开cmd
docker load -i es.tar

#创建一个网络【不然kibana不能连接es,踩坑了!!】
docker network create elastic

#黑马安装:
docker run -d \
--name es \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \ #配置jvm的内存
-e "discovery.type=single-node" \ #配置运行模式【单点模式/集群模式】
-v es-data:/usr/share/elasticsearch/data \ #挂载
-v es-plugins:/usr/share/elasticsearch/plugins \
--privileged \
--network hm-net \
-p 9200:9200 \ #访问http端口
-p 9300:9300 \ #集群使用
elasticsearch:7.12.1

#csdn安装:
docker run -d --name es -e ES_JAVA_OPTS="-Xms512m -Xmx512m" -e "discovery.type=single-node" --privileged --network elastic -p 9200:9200 -p 9300:9300 elasticsearch:7.12.1

启动之后访问http://localhost:9200/就可以看到elasticsearch信息:

image-20240507204417602

1.1.2 安装Kibana

通过下面的Docker命令,即可部署Kibana:

1
2
3
4
5
6
7
8
9
10
11
12
13
#先在tar所在目录下打开cmd
docker load -i kibana.tar

#黑马安装:
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \ #es的地址,这里的es要和es配置docker的时候--name一致
--network=hm-net \ #网络和es一个网络
-p 5601:5601 \
kibana:7.12.1 #要保证和es版本一致!!!

#csdn安装:
docker run -d --name kibana -e ELASTICSEARCH_HOSTS=http://es:9200 --network elastic -p 5601:5601 kibana:7.12.1

启动之后访问http://localhost:5601/就可以通过kibana数据化访问elasticsearch:

image-20240507204635028

可以点击右上角Dev tools,进入开发工具页面:

image-20240507204914788

点击之后:

image-20240507205135009

2.倒排索引

elasticsearch的高性能搜索表现,因为底层的倒排索引技术解决的就是根据==部分词条模糊匹配==的问题。【Innodb底层就是用倒排索引做的全文索引】

2.1 正向索引(精准匹配)

我们有一张名为tb_goods的表:

image-20240507210036497

其中,id字段已经创建了索引(底层使用b+树)所以根据id搜索的速度会非常快。但是其他字段例如title只在叶子结点上存在。

比如用户的sql语句为:

1
2
3
select *
from tb_goods
where title like '%手机%';

搜索大概流程如图:

image-20240507212400849

说明:

  • 1)检查到搜索条件为like '%手机%',需要找到title中包含手机的数据
  • 2)逐条遍历每行数据(每个叶子节点),比如第1次拿到id为1的数据
  • 3)判断数据中的title字段值是否符合条件
  • 4)如果符合则放入结果集,不符合则丢弃
  • 5)回到步骤1

综上,根据id搜索条件为精确匹配时,可以走索引,查询效率较高。而当搜索条件为模糊匹配时,由于索引无法生效,导致从索引查询退化为全表扫描,效率很差。

因此,正向索引适合于根据索引字段的精确搜索,不适合基于部分词条的模糊匹配。

而倒排索引恰好解决的就是根据部分词条模糊匹配的问题。

2.2 倒排索引(模糊匹配)

2.2.1 基本概念

倒排索引中两个重要的概念:

  • 文档(Document):用来搜索的数据,每一条数据就是一个文档【一个网页,一个商品信息】

  • 词条(Term):对文档数据/用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条【我是中国人,就可以分为:我,是,中国人,国人,人这几个词条】

2.2.2 创建流程

创建倒排索引是对正向索引的一种特殊处理和应用,流程如下:

  • 将每个文档的数据利用分词算法根据语义拆分得到一个个词条

  • 创建表,表中每行数据:{词条,词条所在文档id,词条位置}

  • 因为词条唯一性,可以给词条创建正向索引(唯一索引)

此时形成的这张以词条为索引的表就是倒排索引表:

image-20240507214322180

2.2.3 搜索流程

以搜索”华为手机”为例,如图:

image-20240507215033610

流程描述:

1)用户输入条件"华为手机"进行搜索。

2)对用户输入条件分词,得到词条:华为手机

3)拿着词条在倒排索引中查找(由于词条有唯一索引,查询效率很高),即可得到包含词条的文档id:1、2、3

4)拿着文档id到正向索引中查找具体文档即可(由于id也有索引,查询效率也很高)

==根据条件先分词,每个词条去倒排索引查询【词条有唯一索引】找到对应文档id,根据文档id到正向索引【id有索引】查询具体文档(一条数据)==

2.2.4 两者对比

  • 正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程
  • 倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程

两者优缺点:

正向索引 倒排索引
优点 1.可以给多个字段创建索引
2.根据索引字段搜索和排序速度非常快
部分词条查询效率高【创建唯一索引】
缺点 部分词条查询效率不高,只能全表扫描 1.只能给词条创建索引,而不是字段
2.无法根据字段做排序

3.基础概念

3.1 文档(一行数据)和字段(一个列)

elasticsearch是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式存储在elasticsearch中:

image-20240507230531294

因此, 数据库中一行数据 <==> ES中一个JSON文档;

而数据库中每行数据都包含很多列,这些列就转换为JSON文档中的字段(Field)

3.2 索引(数据库的表)和映射(数据库表结构约束)

随着业务发展,需要在es中存储的文档也会越来越多,比如有商品的文档,用户的文档,订单的文档等等;

image-20240508223520776

所有文档都散乱存放显然非常混乱,也不方便管理。

因此,我们要将==类型相同的文档==(一行数据)集中在一起管理,称为索引(Index)

image-20240508223847616

因此,==索引(类型相同的很多行文档) <—->数据库中的表==

数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。

因此,索引库中就有==映射(mapping),是索引中文档的字段约束信息,类似表的结构约束==

3.3 Mysql和Elasticsearch对比

image-20240508225648423

注意:mysql的语法就是sql,而es的语法是dsl【提供json风格的请求语句,用来操作es进行crud】

  • Mysql:擅长事务类型操作,可以确保数据的安全和一致性
  • Elasticsearch:擅长海量数据的搜索、分析、计算

因此在企业中,往往是两者结合使用:

  • 对安全性要求较高的写操作,使用mysql实现
  • 对查询性能要求较高的搜索需求,使用elasticsearch实现
  • 两者再基于某种方式,实现数据的同步,保证一致性

image-20240508231244479

3.4 数据一致性

1
2
3
4
5
方法一:同步双写,课程上架的时候数据写入Mysql,同步也写入ES
方法二:异步双写,课程上架的时候数据写入Mysql,发送消息给MQ,MQ通知ES更新 【项目使用】
方法三:定时同步,对于数据库新增的时候,定时批量/全量同步到ES
方法四:基于Logstash输入输出插件
方法五:基于cancal数据库增量日志解析工具,伪装主从数据库进行同步
策略 优点 缺点
同步双写 - 简单易实现
- 实时性高
- 代码侵入性强
- 存在不一致的风险
- 可能影响系统性能
异步双写(MQ方式) - 解耦数据写入操作
- 通过消息队列提升性能和扩展性
- 系统复杂度增加
- 可能存在消息丢失的风险
- 引入了消息中间件的依赖
定期同步 - 实现简单
- 无需改变现有业务逻辑
- 实时性差
- 可能给数据库带来额外压力
基于Binlog实时同步 - 无代码侵入
- 实时性较好
- 业务逻辑与数据同步解耦
- 构建Binlog系统复杂
- 可能存在MQ延时风险
使用Canal监听Binlog同步数据到ES - 基于MySQL的Binlog,实现数据的实时同步
- 减少系统耦合
- 需要维护额外的Canal服务

4.IK分词器(ikun)

Elasticsearch的关键就是倒排索引,而倒排索引依赖于对文档内容的分词情况(分词好那就效率高),而分词则需要高效、精准的分词算法,IK分词器就是这样一个中文分词算法

4.1 安装IK分词器

方案一:在线安装

1
docker exec -it es ./bin/elasticsearch-plugin  install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip

方案二:离线安装

首先,查看之前安装的elasticsearch容器的plugins数据卷目录:

1
docker volume inspect es-plugins

image-20240507221107758

可以看到elasticsearch的插件挂载到了/var/lib/docker/volumes/es-plugins/_data这个目录。我们需要把IK分词器上传至这个目录

image-20240507221628934

4.2 使用IK分词器

4.2.1 官方标准分词器(standard)

我们在Kibana的DevTools上来测试分词器,首先测试Elasticsearch官方提供的标准分词器:

image-20240507221852165

我们可以看到,标准分词器只能1个字作为一个1个词条,无法正取对中文做分词

4.2.2 IK分词器(ik_smart智能语义切分)

这种情况下,可以智能的将词语切分。但是像程序员这种词可以拆分为程序员,程序,员。这个分词器无法实现

image-20240507222246345

4.2.3 IK分词器(ik_max_word最细粒度切分)

这种情况下,可以在4.2.2的前提下继续细分【程序员这种词可以拆分为程序员,程序,员】

image-20240507222545813

4.3 扩展词典

随着互联网的发展,“造词运动”也越发的频繁。出现了很多新的词语,在原有的词汇列表中并不存在。比如:“泰裤辣”,“传智播客” 等

image-20240507222659451

所以想要正确分词,IK分词器的词库也需要不断地更新,IK分词器提供了扩展词汇的功能:

我们在ik-config文件夹下的IkAnalyzer.cfg.xml文件添加拓展词典和停用词典,这样我们再调用的时候,宋亚翔和传智播客就可以被认为是一个词语作为词条

image-20240507224408412

==基础操作(对索引库和文档基础操作)==

==方式一:通过ES手动创建–很繁琐==

1.索引库操作(数据库表)

index类似数据库表,映射类似表的结构。我们要向es中存储数据,必须先创建索引(数据库表)和映射(数据库定义)

1.1 Mapping映射属性

Mapping是对索引库的文档设置约束,常见的mapping属性包括:

image-20240520150601809

1
2
3
4
5
6
7
8
9
10
11
12
{
"age": 21,
"weight": 52.1,
"isMarried": false,
"info": "黑马程序员Java讲师",
"email": "zy@itcast.cn",
"score": [99.1, 99.5, 98.9],
"name": {
"firstName": "云",
"lastName": "赵"
}
}

对应的每个字段映射(Mapping):

image-20240520151047898

1.2 索引库的CRUD

由于Elasticsearch采用的是Restful风格的API,因此其请求方式和路径相对都比较规范,而且请求参数也都采用JSON风格。

我们直接基于Kibana的DevTools来编写请求做测试,由于有语法提示,会非常方便。

1.2.1 创建索引库和映射

基本语法

  • 请求方式:PUT
  • 请求路径:/索引库名,可以自定义
  • 请求参数:mapping映射

格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PUT /索引库名称
{
"mappings": {
"properties": {
"字段名":{
"type": "text",
"analyzer": "ik_smart"
},
"字段名2":{
"type": "keyword",
"index": "false"
},
"字段名3":{
"properties": {
"子字段": {
"type": "keyword"
}
}
},
// ...略
}
}
}

image-20240520151047898

1.2.2 查询索引库

基本语法

  • 请求方式:GET
  • 请求路径:/索引库名
  • 请求参数:无

image-20240520151340133

1.2.3 修改索引库(只能修改新字段)

倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping

虽然==无法修改mapping中已有字段,却允许添加新的字段到mapping==,因为不会对倒排索引产生影响。因此修改索引库能做的就是向索引库中添加新字段,或者更新索引库的基础属性。

基本语法

  • 请求方式:PUT
  • 请求路径:/索引库名
  • 请求参数:/_mapping

image-20240520151647929

1.2.4 删除索引库

基本语法:

  • 请求方式:DELETE
  • 请求路径:/索引库名
  • 请求参数:无

image-20240520151416751

==1.2.5 索引库操作总结==

索引库操作:

  • 创建索引库:PUT /索引库名{“mappings”:{“properties”:{部分新字段信息}}}}
  • 查询索引库:GET /索引库名
  • 删除索引库:DELETE /索引库名
  • 修改索引库【添加字段】:PUT /索引库名/_mapping{“properties”:{部分新字段信息}}

可以看到,对索引库的操作基本遵循的Restful的风格,因此API接口非常统一,方便记忆。

2.文档操作(一行数据)

有了索引库,接下来就可以向索引库中添加数据。而ElasticSearch数据就是JSON风格的文档。

2.1 新增文档

语法:

1
2
3
4
5
6
7
8
9
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
},
}

例如,目前要新增id=1的文档:

image-20240520153641479

2.2 查询文档

语法:

1
GET /{索引库名称}/_doc/{id}

例如,查询id=1的文档:

image-20240520153813733

2.3 删除文档

语法:

1
DELETE /{索引库名}/_doc/id值

例如,删除id=1的文档:

image-20240520153911052

2.4 修改文档

修改有两种方式:

  • 全量修改:直接覆盖原来的文档【会删除旧文档,添加新文档(如果没有就直接删除)】
  • 局部修改:修改文档中的部分字段

2.4.1 全量修改

全量修改是覆盖原来的文档,其本质是两步操作:

  • 根据指定的id删除文档
  • 新增一个相同id的文档

注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。

语法:

1
2
3
4
5
6
PUT /{索引库名}/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}

image-20240520154216989

2.4.2 局部修改

局部修改是只修改指定id匹配的文档中的部分字段。【注意:局部修改是POST

语法:

1
2
3
4
5
6
POST /{索引库名}/_update/文档id
{
"doc": {
"字段名": "新的值",
}
}

image-20240520154237114

2.5 批处理

类似于Mysql数据库,可以进行多条数据一次性操作【感觉很麻烦,主要是可读性很差】

批处理采用==POST请求==,基本语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST _bulk
# 1.修改 --如果文档id存在就覆盖,不存在就创建
# index代表新增操作 _index表示索引库名 _id表示要操作的文档id
{ "index" : { "_index" : "test", "_id" : "1" } }
# 代表新增的文档内容
{ "field1" : "value1" }

# 2.删除
{ "delete" : { "_index" : "test", "_id" : "2" } }

# 3.新增 --如果文档id存在就报错
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }

# 4.更新
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }

==2.6 文档操作总结==

相对于索引库创建,大致就是中间多了一个_doc路径,修改文档类似于修改索引库比较特殊。

  • 创建文档:POST /{索引库名}/_doc/文档id { json文档 }
  • 查询文档:GET /{索引库名}/_doc/文档id
  • 删除文档:DELETE /{索引库名}/_doc/文档id
  • 修改文档:
    • 全量修改:PUT /{索引库名}/_doc/文档id { json文档 }
    • 局部修改:POST /{索引库名}/_update/文档id { "doc": {字段}}

==索引库操作和文档操作对比:==

image-20240521150017482

5和6步骤主要是在网页端进行设置,因此提出了一个Java的客户端—==JavaRestClient==

==方式二:通过Java实现—不用繁琐的手动创建==

1.JavaRestClient

提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是==组装DSL语句==,通过http请求发送给ES。

由于ES目前最新版本是8.8,提供了全新版本的客户端,老版本的客户端已经被标记为过时。而我们采用的是7.12版本,因此只能使用老版本客户端:

image-20240520155549341

然后选择7.12版本,HighLevelRestClient版本:

image-20240520155613826

==1.1 初始化RestClient==

在Elasticsearch提供的API中,与Elasticsearch一切交互都封装在一个名为RestHighLevelClient的类中,必须先完成这个对象的初始化,建立与elasticsearch的连接。

1.1.1 引入RestHighLevelClient依赖

item-service模块中引入esRestHighLevelClient依赖:

1
2
3
4
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>

1.1.2 覆盖ES版本

因为SpringBoot默认的ES版本是7.17.10,所以我们需要覆盖默认的ES版本:

1
2
3
4
5
6
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<!--覆盖成7.12.1-->
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>

1.1.3 初始化RestHighLevelClient

初始化的代码如下:

1
2
3
4
5
6
RestHighLevelClient client = new RestHighLevelClient(
//使用RestClient的builder方法创建
RestClient.builder(
HttpHost.create("http://192.168.xxx.xxx:9200")
)
);

==1.2 商品Mapping映射==

我们针对购物车数据库进行分析:

image-20240520172813812

我们可以对购物车的所有字段进行分析,判断哪些字段必须添加到ElasticSearch中,判断哪些字段必须添加搜索功能。从而进行新建索引库和映射:

image-20240520171754450

在网页上的代码如下:

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
PUT /items
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "ik_max_word"
},
"price":{
"type": "integer"
},
"stock":{
"type": "integer"
},
"image":{
"type": "keyword",
"index": false
},
"category":{
"type": "keyword"
},
"brand":{
"type": "keyword"
},
"sold":{
"type": "integer"
},
"commentCount":{
"type": "integer",
"index": false
},
"isAD":{
"type": "boolean"
},
"updateTime":{
"type": "date"
}
}
}
}

1.3 索引库操作

创建索引库的JavaAPI和Restful接口API对比:

1.3.1 创建索引库

image-20240520173351287

具体代码如下:

image-20240521135017531

创建索引库:

image-20240521135105629

1.3.2 删除索引库

具体代码如下:

image-20240521135115905

1.3.3 查询索引库

具体代码如下:

==1.3.4 索引库操作总结==

JavaRestClient操作elasticsearch的流程基本类似。核心是client.indices()方法来获取索引库的操作对象。

索引库操作的基本步骤:

  • 1.初始化RestHighLevelClient类对象client【创建客户端】

  • 2.创建XxxIndexRequest对象request【XXX是CreateGetDelete

  • 3.准备请求参数request.source()方法【Create时需要参数,其他情况不需要】

  • 4.发送请求client.indices().xxx()方法【xxx是createexistsdelete

1.4 文档操作

1.4.1 新增文档

  • 1.创建Request对象,这里是IndexRequest,因为添加文档就是创建倒排索引的过程
  • 2.准备请求参数,本例中就是Json文档
  • 3.发送请求【client.index()方法就好了】

image-20240521142712455

1.4.2 查询文档

与之前的流程类似,代码大概分2步:

  • 创建Request对象
  • 准备请求参数,这里是无参,【直接省略】
  • 发送请求
  • 解析结果【因为结果在_source部分内】

image-20240521142844007

可以看到,响应结果是一个JSON,其中文档放在一个_source属性中,因此解析就是拿到_source,反序列化为Java对象即可

1.4.3 删除文档

与查询相比,仅仅是请求方式从DELETE变成GET,可以想象Java代码应该依然是2步走:

  • 1)准备Request对象,因为是删除,这次是DeleteRequest对象。要指定索引库名和id
  • 2)准备参数,无参,直接省略
  • 3)发送请求。因为是删除,所以是client.delete()方法

image-20240521143043972

1.4.4 修改文档

修改我们讲过两种方式:

  • 全量修改:本质是先根据id删除,再新增【与1.4.1一致】
  • 局部修改:修改文档中的指定字段值

在RestClient的API中,全量修改与新增的API完全一致,判断依据是ID:

  • 如果新增时,ID已经存在,则修改
  • 如果新增时,ID不存在,则新增

这里不再赘述,我们主要关注局部修改的API即可

image-20240521143147541

与之前类似,也是三步走:

  • 1)准备Request对象。【这次是修改,所以是UpdateRequest
  • 2)准备参数。【也就是JSON文档,里面包含要修改的字段】
  • 3)更新文档。【这里调用client.update()方法】

1.4.5 批量导入文档

7.4.1-7.4.4的单条处理通过BulkRequest解决。因此BulkRequest中提供了add方法,用以添加其它CRUD的请求:

image-20240521144140401

具体代码:

image-20240521143955532

==1.4.6 文档操作总结==

文档操作的基本步骤:

  • 1.初始化RestHighLevelClient类对象client【创建客户端】
  • 2.创建XxxRequest对象request【Xxx是IndexUpdateDeleteBulk
  • 3.准备请求参数request.source()方法(IndexUpdateBulk时需要)
  • 4.发送请求client.Xxx()方法【Xxx是indexgetupdatedeletebulk
  • 5.解析结果(Get查询时需要,数据在_source内部)

上述的操作都是围绕id来进行的,只能进行简单查询不符合我们所预期的搜索

==进阶操作(DSL查询,更高级的查询)==

Elasticsearch提供基于JSON的DSL(Domain Specific Language)语句来定义查询条件,其JavaAPI就是在组织DSL条件。

Elasticsearch的查询可以分为两大类:

  • 叶子查询(Leaf query clauses):一般是在特定的字段里查询特定值,属于简单查询,很少单独使用。
  • 复合查询(Compound query clauses):以逻辑方式组合多个叶子查询/更改叶子查询的行为方式。

在查询以后,还可以对查询的结果做处理,包括:

排序:按照1个或多个字段值做排序

分页:根据from和size做分页,类似MySQL

高亮:对搜索结果中的关键字添加特殊样式,使其更加醒目

聚合:对搜索结果做数据统计以形成报表

==后续内容总结图:==

image-20240522164945976

==方式一:通过手动创建–DSL查询==

1.快速入门

查询的语法结构:

1
2
3
4
5
6
7
8
GET /{索引库名}/_search   #_search是固定路径,不能修改
{
"query": {
"查询类型": {
// .. 查询条件
}
}
}

例如,我们以最简单的无条件查询为例【查询类型=match_all】:

1
2
3
4
5
6
7
8
GET /items/_search  #_search是固定路径,不能修改
{
"query": {
"match_all": { #查询类型=match_all
#match_all无条件,所以条件位置不写即可
}
}
}

image-20240521154302865

虽然是match_all,但是响应结果中并不会包含索引库中的所有文档,而是仅有10条。这是因为处于安全考虑,elasticsearch设置了默认的查询页数

2.查询—-①叶子查询

image-20240521154435190

2.1 全文检索–(分词)

全文检索的种类也很多,详情可以参考官方文档

2.1.1 match–检索一个字段

1
2
3
4
5
6
7
8
GET /{索引库名}/_search
{
"query": {
"match": {
"字段名": "搜索条件"
}
}
}

举例:

image-20240521162042810

2.1.2 multi_match–检索多个字段

1
2
3
4
5
6
7
8
9
GET /{索引库名}/_search
{
"query": {
"multi_match": {
"query": "搜索条件",
"fields": ["字段1", "字段2"]
}
}
}

举例:

image-20240521162042810

2.2 精确查询–(不分词)

精确查询,英文是Term-level query,顾名思义,词条级别的查询。也就是说不会对用户输入的搜索条件再分词,而是作为一个词条,与搜索的字段内容精确值匹配。因此推荐查找keyword、数值、日期、boolean类型的字段。例如:

  • id
  • price
  • 城市
  • 地名
  • 人名

等等,作为一个整体才有含义的字段。

详情可以查看官方文档

2.2.1 term–根据词条精确匹配

1
2
3
4
5
6
7
8
9
10
GET /{索引库名}/_search
{
"query": {
"term": {
"字段名": {
"value": "搜索条件"
}
}
}
}

举例:

image-20240521163826141

2.2.2 range–根据数值范围查询

1
2
3
4
5
6
7
8
9
10
11
GET /{索引库名}/_search
{
"query": {
"range": {
"字段名": {
"gte": {最小值},
"lte": {最大值}
}
}
}
}

举例:

image-20240521163921559

2.3 地理(geo)查询

未涉及,用到了补充

3.查询—-②复合查询

image-20240521164256532

其他符合查询和相关语法可以参考官方文档

3.1 布尔查询– 与/或/非

bool查询,即布尔查询。就是利用逻辑运算来组合一个或多个查询子句的组合。bool查询支持的逻辑运算有:

  • must:必须匹配每个子查询,类似“与” 【输入框的搜索条件肯定要参与相关性算分】
  • should:选择性匹配子查询,类似“或”
  • must_not:必须不匹配,不参与算分,类似“非”
  • filter:必须匹配,不参与算分 【其他的过滤条件就可以不参与算分】

bool查询的语法如下:

image-20240521170512412

出于性能考虑,与搜索关键字无关的查询尽量采用must_not或filter逻辑运算,避免参与相关性算分

3.2 function_score查询–人为修改相关性算分

3.2.1 相关性算分介绍

当我们利用match进行叶子查询,文档结果会根据与搜索词条的关联度打分_score),返回结果时按照分值降序排列

例如,我们搜索”手机”,结果如下:

image-20240521171322014

从Elasticsearch5.1开始,采用的相关性打分算法是BM25算法,其公式如下:

image-20240521171415660

基于这套公式,就可以判断出某个文档用户搜索的关键字之间的关联度,还是比较准确的。但是,在实际业务需求中,常常会有竞价排名的功能。不是相关度越高排名越靠前,而是掏的钱多的排名靠前。

例如在百度中搜索Java培训,排名靠前的就是广告推广

image-20240521171538540

要想人为控制相关性算法【添加一个过滤条件,增加一个算分函数得到一个值,然后和原始相关性算分运算一下得到新的】,就需要利用Elasticsearch的function_score查询

3.2.2 function_score介绍

function score 查询中包含四部分内容:

  • 1.原始查询条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分(query score)
  • 2.过滤条件:filter部分,符合该条件的文档才会重新算分
  • 3.算分函数:符合filter条件的文档要根据这个函数做运算,得到的函数算分(function score),有四种函数
    • weight:函数结果=常量
    • field_value_factor:函数结果=文档中的某个字段值
    • random_score:函数结果=随机数
    • script_score:自定义算分函数算法
  • 4.运算模式:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:
    • multiply:相乘
    • replace:用function score原始算分替换query score
    • 其它,例如:sum、avg、max、min

==【大致就是在原有BM25算法得到相关性算分,然后根据符合filter条件的文档根据算分函数得到一个值,最后两者进行一些运算方式得到最终值】==

function score的运行流程如下:

  • 1)根据原始条件查询搜索文档,并且计算相关性算分,称为原始算分(query score)
  • 2)根据过滤条件,过滤文档
  • 3)符合过滤条件的文档,基于算分函数,得到函数算分(function score)–算分函数=①常量②文档中某字段值③随机数④自定义
  • 4)将原始算分(query score)和函数算分(function score)基于运算模式[各种]做运算,得到最终结果,作为相关性算分。

举例:给IPhone这个品牌的手机算分提高十倍,分析如下:

  • 过滤条件:品牌必须为IPhone
  • 算分函数:常量weight,值为10
  • 算分模式:相乘multiply

对应代码:

image-20240521172952514

4.排序和分页

1.默认排序:Elasticsearch支持对搜索结果根据相关度算分(_score)进行排序,按照分值降序排列。

2.指定字段排序:Elasticsearch支持对keyword类型,数值类型,地理坐标类型,日期类型等。

4.1 指定排序

基本语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
GET /indexName/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"排序字段": {
"order": "排序方式asc和desc"
}
}
]
}

image-20240522160121221

4.2 分页

elasticsearch 【默认】只返回==top10数据==。而如果要查询更多数据就需要修改分页参数了

elasticsearch中通过修改fromsize参数来控制要返回的分页结果:

  • from:从第几个文档开始
  • size:总共查询几个文档

类似于mysql中的limit ?, ?

基本语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /items/_search
{
"query": {
"match_all": {}
},
"from": 0, // 分页开始的位置,默认为0
"size": 10, // 每页文档数量,默认10
"sort": [
{
"price": {
"order": "desc"
}
}
]
}

image-20240522160315919

elasticsearch的数据一般会采用分片存储,也就是把一个索引中的数据分成N份,存储到不同节点上。这种存储方式比较有利于数据扩展,但给分页带来了一些麻烦。

举例如下:

如果一个索引库有100000条数据,分别存储到4个分片中,每个分片有25000条数据。现在每页查询10条,查询第99页。那分页查询条件如下:

1
2
3
4
5
6
7
8
9
10
GET /items/_search
{
"from": 990, // 从第990条开始查询
"size": 10, // 每页查询10条
"sort": [
{
"price": "asc"
}
]
}

从语句分析来讲:要查询的是第990-1000名数据。

从实现思路分析来讲:①将所有数据排序,找出前1000名②取出其中990-1000的部分

这样来看操作很复杂,因为每个片的数据不是顺序存储的,只能所有拿到一起再重新排序,才能找到最终前1000名截取出990-1000数据

image-20240522160808390

因此,Elasticsearch对普通分页会有一个设置:(from+size)<10000

4.3 深度分页(解决普通分页)

针对深度分页,elasticsearch提供了两种解决方案:

  • search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
  • scroll:原理将排序后的文档id形成快照,保存下来,基于快照做分页。官方已经不推荐使用。

以search after为例:

image-20240522161437320

优点:没有查询上限,支持深度分页【更智能,无上限】

缺点:只能向后逐页查询,不能随机翻页【一页一页查询】

场景:数据大规模顺序迁移、手机滚动查询【一页一页】

适用建议:

  • 大多数情况下,我们采用普通分页就可以了。查看百度、京东等网站,会发现其分页都有限制。例如百度最多支持77页,每页不足20条。京东最多100页,每页最多60条。

  • 因此,一般我们采用限制分页深度的方式即可,无需实现深度分页。

5.高亮显示

5.1 高亮原理

我们在百度,京东搜索时,关键字会变成红色,比较醒目,这叫高亮显示:

image-20240522162022778

css样式肯定是前端实现页面的时候写好的,但是前端编写页面的时候是不知道页面要展示什么数据的,不可能给数据加标签。而服务端实现搜索功能,要是有elasticsearch做分词搜索,是知道哪些词条需要高亮的。

因此词条的高亮标签肯定是由服务端提供数据的时候已经加上的

5.2 高亮操作

高亮的思路就是:

  • 用户输入搜索关键字搜索数据
  • 服务端根据搜索关键字到Elasticsearch搜索,并给搜索结果中的关键字词条添加html标签
  • 前端提前给约定好的html标签添加CSS样式

基本语法:

image-20240522162432699

注意:

  • 搜索必须有查询条件,而且是叶子查询的全文检索类型的查询条件(有分词),例如match
  • 参与高亮的字段必须是text类型的字段
  • 默认情况下参与高亮的字段要与搜索字段一致,除非添加:required_field_match=false

6.聚合

聚合(aggregations)可以让我们极其方便的实现对数据的统计、分析、运算。例如:

  • 什么品牌的手机最受欢迎?
  • 这些手机的平均价格、最高价格、最低价格?
  • 这些手机每月的销售情况如何?

实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现≈实时搜索效果。

聚合分类

image-20240523143246188

==【注意】:参与聚合的字段必须是Keyword、数值、日期、布尔的类型的字段(这些字段一般不分词)==

举例:

具体位置解释:

image-20240523145507262

==DSL手写规则总结==

image-20240522164945976

==方式二:通过Java实现—RestClient查询==

==0.总体对照分析==

查询数据

我们可以分三步拼凑DSL语句和发起请求获取相应结果:

image-20240522172046658

其中2.组织DSL参数的步骤中source()方法下面对应的查询/高亮/分页/排序/聚合:
image-20240522172832347

在查询方面我们直接可以通过QueryBuilders类调用对应的叶子查询/复杂查询

image-20240522172921305

解析数据

我们可以通过响应结果和Elasticsearch页面返回结果获取具体细节: 【可以扩展很多,但其实就是对照DSL查询结果写

image-20240522173851593

黑马的图:

image-20240522173920457

整体步骤

文档搜索的基本步骤是:

  1. 创建SearchRequest对象实例request
  2. 准备request.source(),也就是DSL语句【这个位置可以创建查询,分页,排序,聚合,高亮等操作】
    1. QueryBuilders来构建查询条件
    2. 传入request.source()query()方法
  3. 发送请求,得到结果
  4. 解析结果(参考DSL查询得到的JSON结果,从外到内,逐层解析)

1.查询

上述手动创建DSL查询的时候讲过查询的分类:
image-20240523135626365

1.1 叶子查询

1.1.1 全文检索

image-20240523135752868

1.1.2 精确查询

image-20240523135825592

1.2 复合查询

image-20240523135928266

1.3 举例

举例:

image-20240523141521261

具体代码如下:

image-20240523141510018

2.分页和排序

2.1 基础API

image-20240523142727445

3.高亮

  • 条件同样是在request.source()中指定,只不过高亮条件要基于HighlightBuilder来构造
  • 高亮响应结果与搜索的文档结果不在一起,需要单独解析

3.1 基础API

image-20240523142500909

3.2 获取高亮值

每一条hits信息原始数据在_source部分,而高亮部分在同级highlight内部:

image-20240523142428052

在整体代码的位置:

image-20240523142651360

代码解读:

  • 从结果中获取_sourcehit.getSourceAsString(),这部分是非高亮结果,json字符串。还需要反序列为ItemDoc对象
  • 获取高亮结果。hit.getHighlightFields(),返回值是一个Map,key是高亮字段名称,值是HighlightField对象,代表高亮值
  • Map中根据高亮字段名称,获取高亮字段值对象HighlightField
  • HighlightField中获取Fragments,并且转为字符串。这部分就是真正的高亮字符串了
  • 最后:用高亮的结果替换ItemDoc中的非高亮结果

4.聚合

我们以品牌聚合为例:

4.1 基础API

image-20240523150059473

在Java代码中位置:

image-20240523150224095

4.2 获取桶结果

image-20240523150857573

在Java代码中位置:

image-20240523150938582

==Elasticsearch学习总结==

==1.基本使用思路:==

1.创建索引库和映射 –有了类似于数据库的表和表定义

2.对文档进行CRUD –有了类似于数据库的一行行数据

3.在对应位置进行复杂的DSL查询 –我们可以进行高级的查询,分页,排序,高亮,聚合操作

==2.如果写Java代码的话:==

1.pom.xml导入RestHighLevelClient依赖,然后在父工程覆盖ES版本,初始化RestHighLevelClient依赖

image-20240523155157771

2.分析Mysql哪些字段需要搜索和必须存在,然后Elasticsearch在网页上进行手动设定索引库映射

image-20240523155251252

3.对索引库进行操作【不过我认为网页上更方便】 —到这一步类似于完成了数据库的表和表定义

image-20240523155356214

4.对文档操作【不过我认为网页上更方便】 —到这一步类似于有了数据库的数据

image-20240523155503652

5.在具体位置就可以进行复杂的DSL查询【可以进行查询,分页,排序,高亮,聚合等操作】

image-20240523155632499

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. 0.使用场景
    1. 1.1. 0.1 全文搜索
    2. 1.2. 0.2 实时分析
    3. 1.3. 0.3 机器学习
    4. 1.4. 0.4 地理数据应用
    5. 1.5. 0.5 日志和事件数据分析
    6. 1.6. 0.6 安全信息和事件管理(SIEM)
  2. 2. 1.Elasticsearch(ES)
    1. 2.1. 1.1 安装
      1. 2.1.1. 1.1.1 安装elasticsearch
      2. 2.1.2. 1.1.2 安装Kibana
  3. 3. 2.倒排索引
    1. 3.1. 2.1 正向索引(精准匹配)
    2. 3.2. 2.2 倒排索引(模糊匹配)
      1. 3.2.1. 2.2.1 基本概念
      2. 3.2.2. 2.2.2 创建流程
      3. 3.2.3. 2.2.3 搜索流程
      4. 3.2.4. 2.2.4 两者对比
  4. 4. 3.基础概念
    1. 4.1. 3.1 文档(一行数据)和字段(一个列)
    2. 4.2. 3.2 索引(数据库的表)和映射(数据库表结构约束)
    3. 4.3. 3.3 Mysql和Elasticsearch对比
    4. 4.4. 3.4 数据一致性
  5. 5. 4.IK分词器(ikun)
    1. 5.1. 4.1 安装IK分词器
    2. 5.2. 4.2 使用IK分词器
      1. 5.2.1. 4.2.1 官方标准分词器(standard)
      2. 5.2.2. 4.2.2 IK分词器(ik_smart智能语义切分)
      3. 5.2.3. 4.2.3 IK分词器(ik_max_word最细粒度切分)
    3. 5.3. 4.3 扩展词典
  6. 6. ==基础操作(对索引库和文档基础操作)==
  7. 7. ==方式一:通过ES手动创建–很繁琐==
  8. 8. 1.索引库操作(数据库表)
    1. 8.1. 1.1 Mapping映射属性
    2. 8.2. 1.2 索引库的CRUD
      1. 8.2.1. 1.2.1 创建索引库和映射
      2. 8.2.2. 1.2.2 查询索引库
      3. 8.2.3. 1.2.3 修改索引库(只能修改新字段)
      4. 8.2.4. 1.2.4 删除索引库
      5. 8.2.5. ==1.2.5 索引库操作总结==
  9. 9. 2.文档操作(一行数据)
    1. 9.1. 2.1 新增文档
    2. 9.2. 2.2 查询文档
    3. 9.3. 2.3 删除文档
    4. 9.4. 2.4 修改文档
      1. 9.4.1. 2.4.1 全量修改
      2. 9.4.2. 2.4.2 局部修改
    5. 9.5. 2.5 批处理
    6. 9.6. ==2.6 文档操作总结==
  10. 10. ==方式二:通过Java实现—不用繁琐的手动创建==
  11. 11. 1.JavaRestClient
    1. 11.1. ==1.1 初始化RestClient==
      1. 11.1.1. 1.1.1 引入RestHighLevelClient依赖
      2. 11.1.2. 1.1.2 覆盖ES版本
      3. 11.1.3. 1.1.3 初始化RestHighLevelClient
    2. 11.2. ==1.2 商品Mapping映射==
    3. 11.3. 1.3 索引库操作
      1. 11.3.1. 1.3.1 创建索引库
      2. 11.3.2. 1.3.2 删除索引库
      3. 11.3.3. 1.3.3 查询索引库
      4. 11.3.4. ==1.3.4 索引库操作总结==
    4. 11.4. 1.4 文档操作
      1. 11.4.1. 1.4.1 新增文档
      2. 11.4.2. 1.4.2 查询文档
      3. 11.4.3. 1.4.3 删除文档
      4. 11.4.4. 1.4.4 修改文档
      5. 11.4.5. 1.4.5 批量导入文档
      6. 11.4.6. ==1.4.6 文档操作总结==
  12. 12. ==进阶操作(DSL查询,更高级的查询)==
  13. 13. ==方式一:通过手动创建–DSL查询==
  14. 14. 1.快速入门
  15. 15. 2.查询—-①叶子查询
    1. 15.1. 2.1 全文检索–(分词)
      1. 15.1.1. 2.1.1 match–检索一个字段
      2. 15.1.2. 2.1.2 multi_match–检索多个字段
    2. 15.2. 2.2 精确查询–(不分词)
      1. 15.2.1. 2.2.1 term–根据词条精确匹配
      2. 15.2.2. 2.2.2 range–根据数值范围查询
    3. 15.3. 2.3 地理(geo)查询
  16. 16. 3.查询—-②复合查询
    1. 16.1. 3.1 布尔查询– 与/或/非
    2. 16.2. 3.2 function_score查询–人为修改相关性算分
      1. 16.2.1. 3.2.1 相关性算分介绍
      2. 16.2.2. 3.2.2 function_score介绍
  17. 17. 4.排序和分页
    1. 17.1. 4.1 指定排序
    2. 17.2. 4.2 分页
    3. 17.3. 4.3 深度分页(解决普通分页)
  18. 18. 5.高亮显示
    1. 18.1. 5.1 高亮原理
    2. 18.2. 5.2 高亮操作
  19. 19. 6.聚合
  20. 20. ==DSL手写规则总结==
  21. 21. ==方式二:通过Java实现—RestClient查询==
  22. 22. ==0.总体对照分析==
    1. 22.1. 查询数据
    2. 22.2. 解析数据
    3. 22.3. 整体步骤
  23. 23. 1.查询
    1. 23.1. 1.1 叶子查询
      1. 23.1.1. 1.1.1 全文检索
      2. 23.1.2. 1.1.2 精确查询
    2. 23.2. 1.2 复合查询
    3. 23.3. 1.3 举例
  24. 24. 2.分页和排序
    1. 24.1. 2.1 基础API
  25. 25. 3.高亮
    1. 25.1. 3.1 基础API
    2. 25.2. 3.2 获取高亮值
  26. 26. 4.聚合
    1. 26.1. 4.1 基础API
    2. 26.2. 4.2 获取桶结果
  27. 27. ==Elasticsearch学习总结==
  28. 28. ==1.基本使用思路:==
  29. 29. ==2.如果写Java代码的话:==
,