【ES从入门到实战】完整合集版,带思维导图

在这里插入图片描述

@[toc]

简介

elasticsearch

在这里插入图片描述

全文搜索属于最常见的需求,开源的 Elasticsearch 是目前全文搜索引擎的首选。它可以快速地存储、搜索和分析海量数据。
维基百科、Stack Overflow、Github 都采用它。
Elastic 的底层是开源库 Lucene。
但是,你没法直接用 Lucene,必须自己写代码去调用它的接口。
Elastic 是 Lucene 的封装,提供了 REST API 的操作接口,开箱即用。
REST API:天然的跨平台。

一、基本概念

1、Index(索引)

动词,相当于 MySQL 中的 insert;
名词,相当于 MySQL 中的 Database

2、Type(类型)

在 Index(索引)中,可以定义一个或多个类型;
类似于 MySQL 中的 Table;每一种类型的数据放在一起。

3、Document(文档)

保存在某个索引(Index)下,某种类型(Type)的一个数据(Document),文档是 JSON 格式的,
Document 就像是 MySQL 中的某个 Table 里面的内容。

4、倒排索引机制

记录
红海 1,2,3,4,5
行动 1,2,3
探索 2,5
特别 3,5
记录篇 4
特工 5

分词:将整句分拆为单词

保存的记录

  • 1-红海行动
  • 2-探索红海行动
  • 3-红海特别行动
  • 4-红海记录篇
  • 5-特工红海特别探索

检索
1)、红海特工行动?
2)、红海行动?

相关性得分


二、Docker 安装

1、下载镜像文件

下载elasticsearch

docker pull elasticsearch:7.4.2 # 存储和检索数据
在这里插入图片描述

下载kibana

docker pull kibana:7.4.2 # 可视化检索数据
在这里插入图片描述

注意:elasticsearch 要和 kibana 的版本保持一致!

2、创建实例

1. ElasticSearch

mkdir -p /mydata/elasticsearch/config # 在mydata文件夹下创建es的config文件夹,将docker中es的配置挂载在外部,当我们在linux虚拟机中修改es的配置文件时,就会同时修改docker中的es的配置
mkdir -p /mydata/elasticsearch/data #在mydata文件夹下创建es的data文件夹
echo "http.host:0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml # [http.host:0.0.0.0]允许任何远程机器访问es,并将其写入es的配置文件中

chmod -R 777 /mydata/elasticsearch/ # 保证权限问题
在这里插入图片描述
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx128m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2

# docker run --name elasticsearch 创建一个es容器并起一个名字;
# -p 9200:9200 将linux的9200端口映射到docker容器的9200端口,用来给es发送http请求
# -p 9300:9300 9300是es在分布式集群状态下节点之间的通信端口  \ 换行符
# -e 指定一个参数,当前es以单节点模式运行
# *注意,ES_JAVA_OPTS非常重要,指定开发时es运行时的最小和最大内存占用为64M和128M,否则就会占用全部可用内存
# -v 挂载命令,将虚拟机中的路径和docker中的路径进行关联
# -d 后台启动服务
在这里插入图片描述

安装完 elasticsearch 后我们来启动一下,会发现使用docker ps命令查看启动的容器时没有找到我们的 es,这是因为目前 es 的配置文件的权限导致的,因此我们还需要修改一下 es 的配置文件的权限:

[root@10 config]# cd ../
[root@10 elasticsearch]# ls
config  data  plugins
[root@10 elasticsearch]# cll
bash: cll: command not found
[root@10 elasticsearch]# ll
total 0
drwxr-xr-x. 2 root root 31 May 21 14:55 config
drwxr-xr-x. 2 root root  6 May 21 14:52 data
drwxr-xr-x. 2 root root  6 May 21 15:14 plugins
[root@10 elasticsearch]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES
53c0e82ded18        redis               "docker-entrypoint.s…"   6 weeks ago         Up 4 hours          0.0.0.0:6379->6379/tcp              redis
e1c1b5a6012e        mysql:5.7           "docker-entrypoint.s…"   6 weeks ago         Up 4 hours          0.0.0.0:3306->3306/tcp, 33060/tcp   mysql
[root@10 elasticsearch]# chmod -R 777 /mydata/elasticsearch/
[root@10 elasticsearch]# ll
total 0
drwxrwxrwx. 2 root root 31 May 21 14:55 config
drwxrwxrwx. 2 root root  6 May 21 14:52 data
drwxrwxrwx. 2 root root  6 May 21 15:14 plugins

在这里插入图片描述

修改完文件权限后,我们使用docker start elasticsearch再次启动 es,使用docker ps命令查看后发现容器还是没有启动,这是问什么呢?
我们使用docker logs elasticsearch看一下 es 的启动日志:

[root@10 elasticsearch]# docker logs elasticsearch
OpenJDK 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
2020-05-21 15:14:13,179 main ERROR No Log4j 2 configuration file found. Using default configuration (logging only errors to the console), or user programmatically provided configurations. Set system property 'log4j2.debug' to show Log4j 2 internal initialization logging. See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2
Exception in thread "main" SettingsException[Failed to load settings from [elasticsearch.yml]]; nested: ParsingException[Failed to parse object: expecting token of type [START_OBJECT] but found [VALUE_STRING]];
        at org.elasticsearch.common.settings.Settings$Builder.loadFromStream(Settings.java:1097)
        at org.elasticsearch.common.settings.Settings$Builder.loadFromPath(Settings.java:1070)
        at org.elasticsearch.node.InternalSettingsPreparer.prepareEnvironment(InternalSettingsPreparer.java:83)
        at org.elasticsearch.cli.EnvironmentAwareCommand.createEnv(EnvironmentAwareCommand.java:95)
        at org.elasticsearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:86)
        at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:125)
        at org.elasticsearch.cli.Command.main(Command.java:90)
        at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:115)
        at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:92)
Caused by: ParsingException[Failed to parse object: expecting token of type [START_OBJECT] but found [VALUE_STRING]]
        at org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken(XContentParserUtils.java:78)
        at org.elasticsearch.common.settings.Settings.fromXContent(Settings.java:617)
        at org.elasticsearch.common.settings.Settings.access$400(Settings.java:82)

上述错误是由于我之前配置elasticsearch.yml文件的时候k-v键值对配置错误导致的,查看 yml 文件会发现我配置的内容是这样的:

http.host:0.0.0.0

而实际上k-v键值对之间应该有空格,注意 yml 配置文件中key: value格式冒号后面要跟一个空格。否则就会导致上面的错误。
因此需要修改一下elasticsearch.yml文件,修改为:

http.host: 0.0.0.0

修改并保存之后再次使用docker start elasticsearch启动 es,使用docker ps命令产看后可以看到我的 es 容器已经启动起来了:

在这里插入图片描述

在浏览器地址栏访问http://192.168.56.10:9200/,可以看到 es 启动成功后返回类似下面的数据:

在这里插入图片描述

注意192.168.56.10是我的linux虚拟机的地址,读者需要根据自己的虚拟机地址来进行访问

2, Kibana

安装可视化界面

docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.56.10:9200 -p 5601:5601 \
-d kibana:7.4.2

注意,一定要将192.168.56.10修改为自己的虚拟机地址

在这里插入图片描述

安装完成后在浏览器地址栏访问http://192.168.56.10:5601/,可以看到 kibana 已经启动成功:

在这里插入图片描述

选择yes或no都可以:


在这里插入图片描述

使用我们自己的数据:


在这里插入图片描述

安装成功的界面:


在这里插入图片描述

注意:如果访问http://192.168.56.10:5601/时出现下面的提示,可以稍等一会,可能是 kibana 还没有启动成功

在这里插入图片描述

也可以使用docker logs kibana来查看一下 kibana 的启动日志,下面的日志表示 kibana 启动正常:

在这里插入图片描述

三、初步检索

对 ES 的所有请求都被封装成了 REST API,因此我们可以使用 postman 来访问它。

使用 postman 或者在浏览器地址栏输入请求路径http://192.168.56.10:9200/_cat/xxx

1、_cat

  • GET /_cat/nodes:查看所有节点
/_cat/nodes
  • GET /_cat/health:查看es健康状况
/_cat/health
  • GET /_cat/master:查看主节点
/_cat/master
  • GET /_cat/indices:查看所有索引 ;相当于 MySQL 的 show databases;
/_cat/indices

2、索引一个文档(对应成Mysql就是保存一条记录)

保存一个数据,保存在哪个索引的哪个类型下,指定用哪个唯一标识PUT customer/external/1
在 customer 索引下的 external 类型下保存 1 号数据为

PUT customer/external/1
{
"name":"lohn Doe"
}
PUTPOST 都可以;
POST 新增。如果不指定id,会自动生成 id。指定 id 就会修改这个数据,并新增版本号;
PUT 可以新增也可以修改。PUT 必须指定 id;由于 PUT 需要指定 id,我们一般都用来做修改;

在 postman 地址栏中输入 http://192.168.56.10:9200/customer/external/1,使用 put 方法,输入参数体:

{
"name":"lohn Doe"
}

可以看到创建记录成功:


在这里插入图片描述

再一次发送请求后得到如下结果:

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 2, //注意版本号
    "result": "updated",//注意结果是 update
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 1, //注意序列号
    "_primary_term": 1
}

所以 put 方法既可以用来新增,也可以用来更新。

在 postman 中使用 post 方法发送 http://192.168.56.10:9200/customer/external/ 请求,注意没有带 id,使用的还是上面 put 方法中的参数,
可以看到创建记录成功,es 帮我们生成了一个id:

在这里插入图片描述

当我们使用这个 id 再一次发送 post 请求时,就会变成更新操作:
在这里插入图片描述

所以 post 方法不带 id 时是新增,带 id 不存在时也是新增,带 id 且数据存在时是更新操作。

那么问题来了,put 和 post 方法有啥区别呢?如果使用 put 方法不带 id 发送请求行不行?


在这里插入图片描述

可以看到使用 put 方法不带 id 请求会报错,也就是说 put 是不允许不带 id 请求的,而 post 是允许的。

3、查询文档

3.1、get查询数据

GET customer/external/1
结果:
{
"_index": "customer", //在哪个索引
"_type": "external", //在哪个类型
"_id": "1", //记录id
"_version": 4, //版本号
"_seq_no": 5, //并发控制字段,每次更新就会+1,用来做乐观锁
"_primary_term": 1, //同上,主分片重新分配,如重启,就会变化
"found": true, //表示找到了数据
"_source": { //数据内容
"name": "lohn Doe"
}
}
更新携带 ?if_seq_no=0&if_primary_term=1

在 postman 中使用 get 方法请求 http://192.168.56.10:9200/customer/external/1,会得到如下的结果:

{
    "_index": "customer",   //在哪个索引
    "_type": "external",    //在哪个类型
    "_id": "1",             //记录id
    "_version": 4,          //版本号
    "_seq_no": 5,           //并发控制字段,每次更新就会+1,用来做乐观锁
    "_primary_term": 1,     //同上,主分片重新分配,如重启,就会变化
    "found": true,          //表示找到了数据
    "_source": {            //数据内容
        "name": "lohn Doe"
    }
}

3.2、乐观锁修改

要使用乐观锁修改,我们就需要在 put 或 post 请求的路径中加上?if_seq_no=0&if_primary_term=1字段;
我们在 postman 中使用 put 方法发送 http://192.168.56.10:9200/customer/external/1?if_seq_no=0&if_primary_term=1 请求,参数传

{
"name":"update"
}

执行更新错操作后,出现如下返回结果:


在这里插入图片描述

如果我们使用最新的序列号去更新,就会返回状态为 200 的更新成功的结果:


在这里插入图片描述

4、更新文档

更新操作 参数或结论
POST
customer/external/1/_update
{
"doc": {
"name": "Jane Doe",
"age": 20
}
}
或者POST
customer/external/1
{
"name": "John Nash2"
}
或者PUT
customer/external/1
{
"name": "John Nash3"
}
不同 POST 操作会对比源文档数据,如果相同不会有什么操作,文档 version 、_seq_no 不增加;
PUT 操作总会将数据重新保存并增加 version 版本;
带 _update 对比元数据如果一样就不进行任何操作。
看场景 对于大并发更新,不带update;
对于大并发查询偶尔更新,带update;对比更新,重新计算分配规则。
更新同时增加属性
POST
customer/external/1/_update
{
"doc": {
"name": "Jane Doe",
"age": 20
}
}
更新同时增加属性
PUT&POST
customer/external/1
{
"name": "John Nash2",
"age": 40
}

使用带 _update 的 post 请求更新数据,在 postman 中使用 post 方法发送 http://192.168.56.10:9200/customer/external/1/_update 请求,参数传:

{
     "doc": {
          "name": "John Nash"
     }
}

发送请求可以得到下面的结果,可以看到更新成功:

在这里插入图片描述

再次发送请求,可以看到如果数据相同,对比原来数据,与原来一样就什么都不做,_version_seq_no也不会变:
在这里插入图片描述

使用_update 的 post 请求更新数据,在 postman 中使用 post 方法发送 http://192.168.56.10:9200/customer/external/1 请求,
参数同上,可以看到,每点击一次就会更新一次数据,不会做数据的校验:

在这里插入图片描述

注意,对于不带 _update 的更新,传参时可以使用

{
     "doc": {
          "name": "John Nash",
          "age":40
     }
}

也可以使用:

{
     "name": "John Nash2",
     "age": 40
}

5、删除文档&索引

删除类型 方法或路径参数
删除文档 DELETE customer/external/1
删除索引 DELETE customer

5.1、删除文档

在 postman 中使用 delete 方法发送 http://192.168.56.10:9200/customer/external/1 请求,可以看到以下结果,可以看到删除文档成功:

在这里插入图片描述

再发送一次请求,会返回一个 404 状态的 not_found 结果:

在这里插入图片描述

查询一下刚才删除的文档,会返回一个"found": false的 404 状态的结果:

在这里插入图片描述

5.2、删除索引

在 postman 中使用 delete 方法发送 http://192.168.56.10:9200/customer 请求,可以看到以下结果,可以看到删除索引成功:

在这里插入图片描述

再发送一次请求,会返回一个 404 状态的 index_not_found_exception 结果:

在这里插入图片描述

查询一下刚才删除的索引,会返回一个no such index [customer]的 404 状态的结果:

在这里插入图片描述

那么问题来了,既然可以删除文档索引,那么能不能删除类型呢?

在 ES 中,一个索引下有很多种类型,但是 ES 没有提供删除类型的方法,删除了索引,就会删除所有类型。

6、bulk 批量 API

操作 参数
POST
customer/external/_bulk
{"index" {"_id":"1"}
{"name": "John Nash"}

{"index":"_id"2"}
{"name": "Jane Nash"}
语法格式 {action: {metadata}}\n
{request body}\n

{action: {metadata}}\n
{request body}\n
复杂实例
POST /_bulk
{"delete":{"_index":"website","_type":"blog","_id":"123"}}
{"create":{"_index":"website","_type":"blog","_id":"123"}}
{"title":"My first blog post"}
{"index":{"_index":"website","_type":"blog"}}
{"title":"My second blog post"}
{"update":{"_index":"website","_type":"blog","_id":"123"}}
{"doc":{"title":"My updated blog post"}}

要使用 bulk 批量 API,就需要在 kibana 中来执行我们的操作,如果在 postman 中请求会报错:
首先我们的请求体中的数据已经不是 json 格式了,我们是用 text 格式,会报下面的错误:


在这里插入图片描述

我们再换成 json 试一下:

在这里插入图片描述

上面的 json 格式有误,修改再试一下:
在这里插入图片描述

可以看到在 postman 中无法完成 bulk 批量操作,我们需要在之前装好的 kibana 中进行操作。
打开 kibana 的控制台,选择 DevTools
在这里插入图片描述

点击后出现 DevTools 数据操作界面。我们就是要在这里来进行数据操作:

在这里插入图片描述

使用 DevTools 来执行批量操作,可以看到下面的结果:

在这里插入图片描述

进行一个复杂的批量操作:

POST /_bulk
{"delete":{"_index":"website","_type":"blog","_id":"123"}}
{"create":{"_index":"website","_type":"blog","_id":"123"}}
{"title":"My first blog post"}
{"index":{"_index":"website","_type":"blog"}}
{"title":"My second blog post"}
{"update":{"_index":"website","_type":"blog","_id":"123"}}
{"doc":{"title":"My updated blog post"}}

上面直接使用了/_bulk,没有指定具体的索引,表示在 ES 全局执行。执行结果如下:

在这里插入图片描述

bulk API 以此按顺序执行所有的 action (动作) 。
如果一个单个的动作因任何原因而失败,它将继续处理它后面剩余的动作。
当 bulk API 返回时,它将提供每个动作的状态(与发送的顺序相同) ,所以你可以检查是否一个指定的动作是不是失败了。

7、样本测试数据

我准备了一份顾客银行账户信息的虚构的 JSON 文档样本。每个文档都有下列的 schema(模式) :

schema
{
"account_number": 1,
"balance": 39225,
"firstname": "Amber",
"lastname": "Duke",
"age": 32,
"gender": "M",
"address": "880 Holmes Lane",
"employer": "Pyrami",
"email": "amberduke@pyrami.com",
"city": "Brogan",
"state": "IL"
}

上面的数据是从 github 的 ES 官方文档中截取的,可以访问下面的地址:

accounts.json 导入测试数据

在 ES 中执行测试数据
POST bank/account/_bulk:

在这里插入图片描述

如果在 github 上不好拷贝数据,可以使用我下载好的数据:accounts.json,或访问 gitee-accounts.json

创建完成后可以使用http://192.168.56.10:9200/_cat/indices,来查看一下现在 ES 中的索引,可以看到有 bank 的索引有 1000 条数据:

在这里插入图片描述


四、进阶检索

1、SearchAPl

ES 支持两种基本方式检索:

  • 一个是通过使用 REST request URI 发送搜索参数(uri+检索参数)
  • 另一个是通过使用 REST requestbody 来发送它们(uri+请求体)

1)、检索信息

一切检索从_search开始
uri+检索参数:

请求或返回 解释
GET bank/_search 检索 bank 下所有信息,包括 type 和 docs
GET bank/_search?q=*&sort=account_number:asc 请求参数方式检索
响应结果解释:
took Elasticsearch执行搜索的时间(臺秒)
time_out 告诉我们搜索是否超时
_shards 告诉我们多少个分片被搜索了,以及统计了成功/失败的搜索分片
hits 搜索结果
hits.total 搜索结果
hits.hits 实际的搜索结果数组(默认为前10的文档)
sort 结果的排序 key (键) (没有则按 score 排序)
score 和 max_score 相关性得分和最高得分(全文检索用)

查询结果:


在这里插入图片描述

uri+请求体 进行检索:

uri+请求体 进行检索
GET /bank/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"account_number": "asc"
},
{
"balance": "desc"
}
]
}
HTTP 客户端工具(POSTMAN),get 请求不能携带请求体,我们变为 post 也是一样的我们 POST 一个 JSON 风格的查询请求体到_search APl。
需要了解,一旦搜索的结果被返回, Elasticsearch 就完成了这次请求,并且不会维护任何服务端的资源或者结果的 cursor (游标)

查询结果:


在这里插入图片描述

参考文档getting-started-search

2、Query DSL

在上一节中使用的形如

GET /bank/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "account_number": "asc"
    },
    {
      "balance": "desc"
    }
  ]
}

的查询语言风格,我们称之为 Query DSL

1)、基本语法格式

Elastisearch 提供了一个可以执行查询的 Json 风格的 DSl (domain-specific language 领域特定语言) 。这个被称为Query DSL。
该查询语言非常全面,并且刚开始的时候感觉有点复杂,真正学好它的方法是从一些基础的示例开始的。

  • 一个查询语句的典型结构

2、Query DSL

在上一节中使用的形如

GET /bank/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "account_number": "asc"
    },
    {
      "balance": "desc"
    }
  ]
}

的查询语言风格,我们称之为 Query DSL

1)、基本语法格式

Elastisearch 提供了一个可以执行查询的 Json 风格的 DSl (domain-specific language 领域特定语言) 。这个被称为Query DSL。
该查询语言非常全面,并且刚开始的时候感觉有点复杂,真正学好它的方法是从一些基础的示例开始的。

  • 一个查询语句的典型结构
{
    QUERY_NAME:{
        ARGUMENT: VALUE,
        ARGUMENT: VALUE,
        ...
    }
}

例如:

GET /bank/_search
{
  "query": {
    "match_all": {}
  }
}
在这里插入图片描述
  • 如果是针对某个字段,那么它的结构如下:
{
    QUERY_NAME:{
        FIELD_NAME:{
            ARGUMENT: VALUE,
            ARGUMENT: VALUE,
            ...
        }
    }
}

例如:

GET /bank/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"balance": {
"order": "desc"
}
}
],
"from": 0,
"size": 5
}
- query 定义如何查询;
- match_all 查询类型【代表查询所有的所有】, es 中可以在 query 中组合非常多的查询类型完成复杂查询
- 除了 query 参数之外,我们也可以传递其它的参数以改变查询结果。如 sort,size;
- from+size 限定,完成分页功能;
- sort 排序,多字段排序,会在前序字段相等时后续字段内部排序,否则以前序为准
在这里插入图片描述

2)、返回部分字段

GET /bank/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "balance": {
        "order": "desc"
      }
    }
  ],
  "from": 0,
  "size": 5,
  "_source": ["balance","firstname"]
}

只返回 _source 中指定的字段,类似于 MySQL 中的 select field_1,field_2,... from table

在这里插入图片描述

参考文档-query-dsl

3)、match【匹配查询】

  • 基本类型(非字符串),精确匹配

match 返回 account_number=20 的数据:

GET /bank/_search
{
  "query": {
    "match": {
      "account_number": 20
    }
  }
}
在这里插入图片描述
  • 字符串,全文检索

最终查询出 address 中包含 Kings 单词的所有记录,当搜索字符串类型的时候,会进行全文检索,并且每条记录有相关性得分。

GET /bank/_search
{
  "query": {
    "match": {
      "address": "Kings"
    }
  }
}
在这里插入图片描述
  • 字符串,多个单词(分词+全文检索)

全文检索按照评分进行排序,会对检索条件进行分词匹配

GET /bank/_search
{
  "query": {
    "match": {
      "address": "Mill Lane"
    }
  }
}

最终查询出 address 中包含 Mill 或者 Lane 或者 Mill Lane 的所有记录,并给出相关性得分


在这里插入图片描述

参考文档-query-dsl

4)、match_phrase 【短语匹配】

将需要匹配的值当成一个整体单词(不分词)进行检索

举个栗子:查出 address 中包含 mill road 的所有记录,并给出相关性得分

GET /bank/_search
{
  "query": {
    "match_phrase": {
      "address": "mill road"
    }
  }
}
在这里插入图片描述

参考文档-query-dsl

5)、multi_match 【多字段匹配】

举例:state 或 address 包含 mill

GET /bank/_search
{
  "query": {
    "multi_match": {
      "query": "mill",
      "fields": ["address","state"]
    }
  }
}
在这里插入图片描述

多字段查询的时候也会进行分词查询,得分最高的在前面:

GET /bank/_search
{
  "query": {
    "multi_match": {
      "query": "mill movico",
      "fields": ["address","city"]
    }
  }
}
在这里插入图片描述

参考文档-query-dsl

6)、bool 【复合查询】

bool 用来做复合查询:

复合语句可以合并任何其它查询语句,包括复合语句,了解这一点是很重要的。
这就意味着,复合语句之间可以互相嵌套,可以表达非常复杂的逻辑。

  • must:必须达到 must 列举的所有条件
GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "gender": "F"
          }
        },
        {
          "match": {
            "address": "Mill"
          }
        }
      ]
    }
  }
}
在这里插入图片描述
  • must_not:子句(查询)不得出现在匹配的文档中
GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "gender": "F"
          }
        },
        {
          "match": {
            "address": "Mill"
          }
        }
      ],
      "must_not": [
        {"match": {
          "age": 30
        }}
      ]
    }
  }
}
在这里插入图片描述
  • should:子句(查询)应出现在匹配的文档中。(should表示有最好,没有也可以)
GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "gender": "M"
          }
        },
        {
          "match": {
            "address": "Mill"
          }
        }
      ],
      "must_not": [
        {"match": {
          "age": 30
        }}
      ],
      "should": [
        {"match": {
          "lastname": "Holland"
        }}
      ]
    }
  }
}
在这里插入图片描述

布尔查询

与文档匹配的查询,这些文档与其他查询的布尔组合匹配。布尔查询映射到Lucene BooleanQuery。它是使用一个或多个布尔子句构建的,每个子句都具有类型的出现。发生类型为:

发生 描述
must 子句(查询)必须出现在匹配的文档中,并将有助于得分。
filter 子句(查询)必须出现在匹配的文档中。但是不像 must查询的分数将被忽略。Filter子句在filter上下文中执行,这意味着计分被忽略,并且子句被考虑用于缓存。
should 子句(查询)应出现在匹配的文档中。
must_not 子句(查询)不得出现在匹配的文档中。子句在过滤器上下文中执行,这意味着计分被忽略,并且子句被视为用于缓存。由于忽略计分,0因此将返回所有文档的分数。

参考文档-query-dsl-bool-query

下一节我们来看一下 filter 结果过滤。

参考文档-query-dsl

7)、filter 【结果过滤】

并不是所有的查询都需要产生分数,特别是那些仅用于 "fitering" (过滤)的文档。为了不计算分数 Elasticsearch 会自动检查场景并且优化查询的执行。
在 filter 元素下指定的查询对得分没有影响-得分以 0 形式返回。分数仅受指定查询的影响。
must 查询为例:

GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        {"range": {
          "age": {
            "gte": 18,
            "lte": 30
          }
        }}
      ]
    }
  }
}
在这里插入图片描述

使用 filter 来替代 must 查询,需要注意的是,使用filter查询出的结果和must查询出的结果是一致的,差异仅是没有相关性得分:

GET /bank/_search
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "age": {
            "gte": 18,
            "lte": 30
          }
        }
      }
    }
  }
}
在这里插入图片描述

所以我们在 should 之后还可以加上 filter 条件进行过滤:

GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "gender": "M"
          }
        },
        {
          "match": {
            "address": "Mill"
          }
        }
      ],
      "must_not": [
        {"match": {
          "age": 30
        }}
      ],
      "should": [
        {"match": {
          "lastname": "Holland"
        }}
      ],
      "filter": {
        "range": {
          "age": {
            "gte": 18,
            "lte": 30
          }
        }
      }
    }
  }
}
在这里插入图片描述

参考文档-query-dsl

8)、term

和 match 一样。匹配某个属性的值。全文检索字段用 match,其他非 text 字段匹配用 term

Avoid using the term query for text fields.

By default, Elasticsearch changes the values of text fields as part of analysis. This can make finding exact matches for text field values difficult.

To search text field values, use the match query instead.

非文本值使用 term 检索:

GET /bank/_search
{
  "query": {
    "term": {
      "age":28
    }
  }
}
在这里插入图片描述

match 的 xxx.keyword,文本的精确匹配检索:

GET /bank/_search
{
  "query": {
    "match": {
      "address.keyword": "789 Madison"
    }
  }
}
在这里插入图片描述

match 全文分词匹配:

GET /bank/_search
{
  "query": {
    "match": {
      "address": "789 Madison"
    }
  }
}
在这里插入图片描述

match_phrase,将需要匹配的值当成一个整体单词(不分词)进行检索:

GET /bank/_search
{
  "query": {
    "match_phrase": {
      "address": "789 Madison"
    }
  }
}
在这里插入图片描述

注意:如果对于文本值使用 term 检索时,并不会进行分词,而是精确检索,所以可能会匹配不到数据:

GET /bank/_search
{
  "query": {
    "term": {
      "address": "789 Madison"
    }
  }
}
在这里插入图片描述

参考文档-query-dsl-term-query
参考文档-query-dsl

9) 、aggregations (执行聚合)

聚合提供了从数据中分组和提取数据的能力。
最简单的聚合方法大致等于 SQL GROUP BYSQL 聚合函数
在 Elasticsearch 中,您有执行搜索返回 hits (命中结果),并且同时返回聚合结果,
把一个响应中的所有hits(命中结果)分隔开的能力。这是非常强大且有效的,您可以执行查询和多个聚合,
并且在一次使用中得到各自的(任何一个的)返回结果,使用一次简洁和简化的 API 来避免网络往返。

aggregations 查询语法:

"aggregations" : {
    "<aggregation_name>" : {
        "<aggregation_type>" : {
            <aggregation_body>
        }
        [,"meta" : {  [<meta_data_body>] } ]?
        [,"aggregations" : { [<sub_aggregation>]+ } ]?
    }
    [,"<aggregation_name_2>" : { ... } ]*
}

举个栗子:

  • 搜索address中包含mill的所有人的年龄分布以及平均年龄,但不显示这些人的详情。
GET /bank/_search
{
  "query": { //查询
    "match": {
      "address": "mill"
    }
  },
  "aggs": { //聚合
    "ageAgg": { //年龄分布
      "terms": {
        "field": "age",
        "size": 10 //只取10中聚合的结果
      }
    },
    "ageAvg":{//平均年龄,基于上一次的结果
      "avg": {
        "field": "age"
      }
    },
    "balanceAvg":{//平均薪资
      "avg": {
        "field": "balance"
      }
    }
  },
  "size": 0 //不显示搜索数据,只显示聚合结果
}
在这里插入图片描述
aggs,执行聚合。聚合语法如下:
"aggs":{
    "ages_name 这次聚合的名字,方便展示在结果集中":{
        "AGG-TYPE 聚合的类型(avg,term,terms) ":{}
    }
}
  • 复杂聚合:按照年龄聚合,并且请求这些年龄段的这些人的平均薪资(使用一个子聚合)
GET /bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "ageAgg": {
      "terms": {//年龄范围分布聚合
        "field": "age",
        "size": 100//返回100中情况
      },
      "aggs": {//基于ageAgg的结果做聚合
        "ageAvg": {
          "avg": {//求balance的平均值
            "field": "balance"
          }
        }
      }
    }
  }
}
在这里插入图片描述
  • 复杂聚合进阶:查出所有年龄分布,并且这些年龄段中M的平均薪资和F的平均薪资以及这个年龄段的总体平均新资
GET /bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {//聚合
    "ageAgg":{
      "terms": {//年龄分布
        "field": "age",
        "size": 100
      },
      "aggs": {//基于ageAgg做聚合
        "genderAgg": {//性别分布
          "terms": {
            //文本字段聚合使用keyword进行精确匹配,否则会报错
            "field": "gender.keyword",
            "size": 10
          },
          "aggs": {//基于genderAgg做聚合
            "balanceAvg": {//求性别为M和F的各自的平均薪资
              "avg": {
                "field": "balance"
              }
            }
          }
        },
        "ageBalanceAvg":{//基于ageAgg,求各个年龄段的平均薪资
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}
在这里插入图片描述

更多聚合查询操作,请参考 ES 官方文档:参考文档-search-aggregations

参考文档-query-dsl

3、Mapping

1)、字段类型

  • 核心类型

    • 字符串(string)
      textkeyword
    • 数字类型(Numeric)
      long, integer, short, byte, double, float, half_float, scaled_float
    • 日期类型(Date)
      date
    • 布尔类型(Boolean)
      boolean
    • 二进制类型(Binary)
      binary
  • 复合类型

    • 数组类型(Array)
      Array 支持不针对特定的数据类型
    • 对象类型(Object)
      object 用于单个JSON对象的对象
    • 嵌套类型(Nested)
      nested 用于JSON对象的数组
  • 地理类型(Geo)

    • 地理坐标(Geo-point)
      geo_point 纬度/经度坐标
    • 地理圆形(Geo-shape)
      geo_shape 用于多边形等复杂形状
  • 特定类型

    • IP 类型(IP)
      ip 用于描述 IPv4 和 IPv6 地址
    • 补全类型(Completion)
      completion 提供自动完成提示
    • 令牌计数类型(Token count)
      token_count 用来统计字符串中词条的数量
    • 附件类型(attachment)
      参考 mapper-attachments 插件,支持将附件例如Microsoft Office格式,open document格式,ePub,HTML等索引为 attachment 数据类型。
    • 抽取类型(Percolator)
      接受来自领域特定语言(query-dsl)的查询

更多字段类型,请参考 ES 官方文档:参考文档-mapping-types

2)、映射

Mapping (映射)
Mapping 是用来定义一个文档(document),以及它所包含的属性(field)是如何存储和索引的。比如,使用mapping来定义:

  • 哪些字符串属性应该被看做全文本属性(full text fields)

  • 哪些属性包含数字,日期或者地理位置

  • 文档中的所有属性是否都能被索引(_all 配置)

  • 日期的格式

  • 自定义映射规则来执行动态添加属性

  • 查看 mapping 信息:
    GET bank/_mapping

    在这里插入图片描述

  • 修改 mapping 信息:

    • 创建索引
PUT /my-index
{
  "mappings": {//映射规则
    "properties": {
      "age":    { "type": "integer" },  
      "email":  { "type": "keyword"  },//keyword不会进行全文检索 
      "name":   { "type": "text"  }//text保存的时候进行分词,搜索的时候进行全文检索   
    }
  }
}
在这里插入图片描述

ES 自动猜测的映射类型:

JSON type 域 type
布尔型:true、false boolean
整数:123 long
浮点数:1.23 double
字符串,有效日期 2020-02-02 date
字符串,foo bar string
对象,也称为哈希,存储对象类型 object

参考文档-mapping

3)、新版本改变

ES7 及以上移除了 type 的概念。

  • 关系型数据库中两个数据表示是独立的,即使他们里面有相同名称的列也不影响使用,但ES中不是这样的。 elasticsearch 是基于 Lucene 开发的搜索引擎,而 ES 中不同 type 下名称相同的 filed 最终在 Lucene 中的处理方式是一样的。
    • 两个不同 type 下的两个 user_name,在 ES 同一个索引下其实被认为是同一个 filed,你必须在两个不同的 type 中定义相同的 filed 映射。否则,不同 type 中的相同字段名称就会在处理中出现冲突的情况,导致 Lucene 处理效率下降。
    • 去掉type就是为了提高 ES 处理数据的效率。

Elasticsearch 7.x:

  • URL中的 type 参数为可选。比如,索引一个文档不再要求提供文档类型。

Elasticsearch 8.x:

  • 不再支持URL中的type参数。

解决:
1)、将索引从多类型迁移到单类型,每种类型文档一个独立索引
2)、将已存在的索引下的类型数据,全部迁移到指定位置即可。详见数据迁移

1、创建映射

创建索引并指定映射

PUT /my-index
{
  "mappings": {//映射规则
    "properties": {
      "age":    { "type": "integer" },  
      "email":  { "type": "keyword"  },//keyword不会进行全文检索 
      "name":   { "type": "text"  }//text保存的时候进行分词,搜索的时候进行全文检索   
    }
  }
}
在这里插入图片描述

2、添加新的字段映射

PUT /my-index/_mapping
{
  "properties": {
    "employee-id": {
      "type": "keyword",
      "index": false//索引选项控制是否对字段值建立索引。 它接受true或false,默认为true。未索引的字段不可查询。
    }
  }
}
在这里插入图片描述

3、更新映射

对于已经存在的映射字段,我们不能更新。更新必须创建新的索引进行数据迁移

参考文档-mapping

4、数据迁移

先创建出 twitter 的正确映射。然后使用如下方式进行数据迁移

# 7.x 之后的写法
POST _reindex   //固定写法
{
  "source": {   //老索引
    "index": "twitter"
  },
  "dest": {     //目标索引
    "index": "new_twitter"
  }
}

# 7.x之前的带 type 的写法
将旧索引的 type 下的数据进行迁移
POST _reindex   //固定写法
{
  "source": {   
    "index": "twitter", //老索引
    "type": "twitter",  //老类型
  },
  "dest": {     //目标索引
    "index": "new_twitter"
  }
}

举例:
创建一个新的索引

PUT /newbank
{
  "mappings": {
    "properties": {
      "account_number": {
        "type": "long"
      },
      "address": {
        "type": "text"
      },
      "age": {
        "type": "integer"
      },
      "balance": {
        "type": "long"
      },
      "city": {
        "type": "keyword"
      },
      "email": {
        "type": "keyword"
      },
      "employer": {
        "type": "keyword"
      },
      "firstname": {
        "type": "text"
      },
      "gender": {
        "type": "keyword"
      },
      "lastname": {
        "type": "text"
      },
      "state": {
        "type": "keyword"
      }
    }
  }
}
在这里插入图片描述

数据迁移

POST _reindex
{
  "source": {
    "index": "bank",
    "type": "account"
  },
  "dest": {
    "index": "newbank"
  }
}

在这里插入图片描述

查看迁移后的数据
可以看到,不用 type,老的数据可以迁移过来

GET newbank/_search
在这里插入图片描述

参考文档-mapping

4、分词

一个 tokenizer (分词器)接收一个字符流,将之分割为独立的 tokens (词元,通常是独立的单词),然后输出 tokens流。

例如, whitespace tokenizer 遇到空白字符时分割文本。它会将文本"Quick brown fox!"分割为[Quickbrownfox!l。

tokenizer (分词器)还负责记录各个 term (词条)的顺序或 position位置(用于 phrase短语和 word proximity 词近邻查询) ,以及 term (词条)所代表的原始 word (单词)的 start(起始)和 end (结束)的 character offsets (字符偏移量) (用于高亮显示搜索的内容)。

Elasticsearch 提供了很多内置的分词器,可以用来构建 custom analyzers (自定义分词器) 。

测试 ES 默认的标准分词器:
英文

POST _analyze
{
  "analyzer": "standard",
  "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
在这里插入图片描述

中文

POST _analyze
{
  "analyzer": "standard",
  "text": "pafcmall电商项目"
}
在这里插入图片描述

1)、安装 ik 分词器

注意:不能用默认 elasticsearch-plugin install xxx.zip 进行自动安装

进入 https://github.com/medcl/elasticsearch-analysis-ik/releases
找到对应的 es 版本安装

1、进入 es 容器内部 plugins 目录

docker exec -it 容器id /bin/bash

wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
在这里插入图片描述

2、安装 wget

yum install wget
在这里插入图片描述

3、下载和 ES 匹配版本的 ik 分词器:

wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
在这里插入图片描述

4、unzip 下载文件并解压
1)、使用 unzip 解压 elasticsearch-analysis-ik-7.4.2.zip 发现 unzip 命令还未安装,先安装 unzip

在这里插入图片描述

2)、解压文件到 plugins 目录下的 ik 目录
在这里插入图片描述

3)删除压缩包,并给 ik 目录及其文件授权

rm -rf *.zip
chmod -R 777 ik/
在这里插入图片描述

5、可以确认是否安装好了分词器

cd../bin
elasticsearch plugin list:即可列出系统的分词器

1)、进入docker中的es容器内


在这里插入图片描述

2)、列出系统的分词器


在这里插入图片描述

6、重启 ES 使 ik 分词器生效

docker restart elasticsearch

2)、测试分词器

  • 使用默认分词
POST _analyze
{
  "analyzer": "standard",
  "text": "pafcmall电商项目"
}

结果:


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l0aY1xXN-1590760088470)(imgs/20-2.png)]
  • ik智能分词
POST _analyze
{
  "analyzer": "ik_smart",
  "text": "pafcmall电商项目"
}

结果:


在这里插入图片描述
POST _analyze
{
  "analyzer": "ik_smart",
  "text": "我是中国人"
}

结果:


在这里插入图片描述
  • ik_max_word分词
POST _analyze
{
  "analyzer": "ik_max_word",
  "text": "我是中国人"
}

结果:


在这里插入图片描述

够看出不同的分词器,分词有明显的区别,所以以后定义一个索引不能再使用默认的 mapping 了,要手工建立 mapping,因为要选择分词器。

参考文档-analysis

3)、自定义词库

ik 分词器默认的分词并不能满足我们的需求,对于一些新的网络用语,ik 分词器就会无法准确的进行分词识别,比如:

POST _analyze
{
  "analyzer": "ik_max_word",
  "text": "乔碧萝殿下"
}

分词之后显示为如下,可以看到 ik 分词器无法识别出“乔碧萝”是一个人名:


在这里插入图片描述

所以,需要进行自定义拓展词库。
要自定义拓展词库,可以修改 ik 分词器的配置文件,指定一个远程词库,让 ik 分词器向远程发送请求,要到一些最新的单词,这样最新的单词就会作为最新的词源进行分解。
自定义词库有两种方式实现:

  1. 自己实现一个服务,处理 ik 分词器的请求,让 ik 分词器的给自定义的项目发送请求
  2. 搭建一个 nginx 服务器,将最新词库放到 nginx 中,让 ik 分词器给 nginx 发送请求,由 nginx 给 ik 分词器返回最新的词库,这样 ik 分词器就可以将原来的词库和新词库合并起来。

nginx 安装参考 六、附录-安装nginx

在这里我使用第二种方式来自定义词库,创建前需要先安装 nginx, 请访问第六章有关内容。
/mydata/nginx/html/ 路径下新建一个 es 目录,并新建一个词库 fenci.txt

在这里插入图片描述

访问 http://192.168.56.10/es/fenci.txt,可以请求的词库的内容:
在这里插入图片描述

修改 /usr/share/elasticsearch/plugins/ik/config/ 中的 IKAnalyzer.cfg.xml

在这里插入图片描述

/usr/share/elasticsearch/plugins/ik/config

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典 -->
        <entry key="ext_dict"></entry>
         <!--用户可以在这里配置自己的扩展停止词字典-->
        <entry key="ext_stopwords"></entry>
        <!--用户可以在这里配置远程扩展字典 -->
        <entry key="remote_ext_dict">http://192.168.56.10/es/fenci.txt</entry>
        <!--用户可以在这里配置远程扩展停止词字典-->
        <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
在这里插入图片描述

注意:如果打开 IKAnalyzer.cfg.xml 为乱码的话,可以在先退出当前文件,在命令行输入 vi /etc/virc
然后在文件添加 set encoding=utf-8,保存退出,重新打开 IKAnalyzer.cfg.xml 即可。

在这里插入图片描述

原来的xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典 -->
        <entry key="ext_dict"></entry>
         <!--用户可以在这里配置自己的扩展停止词字典-->
        <entry key="ext_stopwords"></entry>
        <!--用户可以在这里配置远程扩展字典 -->
        <!-- <entry key="remote_ext_dict">words_location</entry> -->
        <!--用户可以在这里配置远程扩展停止词字典-->
        <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

重启 ES

docker restart elasticsearch 

重新在 kibana 中进行分词,可以看到之前无法识别的“乔碧萝”现在已经可以识别为一个单词了:


在这里插入图片描述

如果我们以后还有新的词组,直接在上面的自定义词库fenci.txt中进行添加,并重启 ES 即可。

由于之前在安装 nginx 时重装了 ES,所以需要设置一下 ES 的自动启动服务:
docker update elasticsearch --restart=always

参考文档-analysis


五、Elasticsearch-Rest-Client

Java 操作 ES 的两种方式:
1) 、9300:TCP (我们不在9300操作,官方也不建议)

  • spring-data-elasticsearch:transport-api.jar;
    • springboot 版本不同,transport-api.jar不同,不能适配es版本
    • 7.x 已经不建议使用,8 以后就要废弃

2)、9200:HTTP(推荐使用)

  • JestClient:非官方,更新慢
  • RestTemplate:模拟发 HTTP 请求,ES 很多操作需要自己封装,麻烦
  • HttpClient:同上 I
  • Elasticsearch-Rest-Client:官方 RestClient,封装了 ES 操作, API 层次分明,上手简单最终选择
    Elasticsearch-Rest-Client(elasticsearch-rest-high-level-client)
    在这里插入图片描述
在这里插入图片描述

1、SpringBoot整合

1)在pafcmall项目中新添加一个模块pafcmall-search,当然你也可以,单独创建一个项目

在这里插入图片描述

使用 spring 启动器创建:
在这里插入图片描述

添加 groupartifact 信息:
在这里插入图片描述

添加 web 依赖:
在这里插入图片描述

2)、修改pom文件

添加对应的当前 ES 版本的 rest-high-level-client 依赖,我使用的是7.4.2,所以添加7.4.2的依赖

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.4.2</version>
</dependency>

在这里插入图片描述

可以看到当前 SpingBoot(2.2.6)版本默认管理的 ES 的版本和 elasticsearch-rest-high-level-client 的版本不一致:
在这里插入图片描述

需要修改一下 pom 文件,让 ESelasticsearch-rest-high-level-client 的版本保持一致:

<elasticsearch.version>7.4.2</elasticsearch.version>
在这里插入图片描述

3)、添加 ES 配置类

/**
 * @description: Elasticsearch 配置文件
 * <p>
 * SpringBoot 集成 ES 的步骤:
 *  1、导入依赖
 *      https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-getting-started-maven.html
 *  2、编写 ES 配置,给容器中注入一个 RestHighLevelClient,用来操作 9200 端口
 *      https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-getting-started-initialization.html
 *  3、参照官方API
 */
@Configuration
public class PafcmallElasticsearchConfig {

    @Bean
    public RestHighLevelClient esRestHighLevelClient() {
        RestHighLevelClient client = new RestHighLevelClient(
                // 这里可以配置多个 es服务,我当前服务不是集群,所以目前只配置一个
                RestClient.builder(
                        new HttpHost("192.168.50.10", 9200, "http")));

        return client;
    }
}

在这里插入图片描述

修改启动类

@EnableDiscoveryClient // 开启服务注册与发现
// 这里需要排除一下数据库的依赖,因为引入了pafcmall-common依赖,其中包含了mybatis-plus的配置,目前我们的服务还没有依赖数据源,所以需要排除
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class PafcmallSearchApplication {

    public static void main(String[] args) {
        SpringApplication.run(PafcmallSearchApplication.class, args);
    }

}

修改 application.properties 文件:

# nacos配置中心地址
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
# 配置应用名
spring.application.name=pafcmall-search

使用测试类测试:

@SpringBootTest
class PafcmallSearchApplicationTests {
    @Autowired
    private RestHighLevelClient client;
    
    @Test
    void contextLoads() {
        System.out.println(client);
    }
}

在这里插入图片描述

更多整合信息请参考 java-rest-high-getting-started-mavenjava-rest-high-getting-started-initialization

2、配置

1)、配置
PafcmallElasticsearchConfig 配置类中添加如下配置:

    public static final RequestOptions COMMON_OPTIONS;
    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
        // 这里先注释掉,目前没有用到,后边用到了再解释
//        builder.addHeader("Authorization", "Bearer " + TOKEN);
//        builder.setHttpAsyncResponseConsumerFactory(
//                new HttpAsyncResponseConsumerFactory
//                        .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
        COMMON_OPTIONS = builder.build();
    }

2)、测试
PafcmallSearchApplicationTests 测试类中添加如下代码:

    /**
     * 测试存储数据到es
     * 更新也是可以的
     */
    @Test
    void indexData() throws IOException {
        // 准备数据
        IndexRequest indexRequest = new IndexRequest("users");//索引名
        indexRequest.id("1");//数据id

        // 第一种方式
//        indexRequest.source("userName", "zhangsan", "age", 18, "gender", "男");

        // 第二种方式(推荐使用)
        User user = new User();
        user.setUserName("zhangsan");
        user.setAge(18);
        user.setGender("男");

        String jsonString = JSON.toJSONString(user);
        indexRequest.source(jsonString,XContentType.JSON); //要保存的内容

        // 执行操作
        IndexResponse index = client.index(indexRequest, PafcmallElasticsearchConfig.COMMON_OPTIONS);

        // 提取有用的数据相应
        System.out.println(index);
    }

    @Data
    class User{
        private String userName;
        private Integer age;
        private String gender;
    }

执行测试后可以看到创建索引成功:


在这里插入图片描述

在 kibana 中查询一下:


在这里插入图片描述

更过配置信息请参考 java-rest-high-getting-started-request-optionsjava-rest-low-usage-request-options

3、使用

在上一小节中实现了创建索引,这一小节来试一下数据的检索功能。
在代码中实现 搜索address中包含mill的所有人的年龄分布以及平均薪资 这个功能,如果是在 kibana 中,使用的是下面的DSL语句:

GET /bank/_search
{
  "query": { //查询
    "match": {
      "address": "mill"
    }
  },
  "aggs": { //聚合
    "ageAgg": { //年龄分布
      "terms": {
        "field": "age",
        "size": 10 //只取10中聚合的结果
      }
    },
    "balanceAvg":{//平均薪资
      "avg": {
        "field": "balance"
      }
    }
  }
}

结果如下图所示:


在这里插入图片描述

要在 SpringBoot 集成环境中该如何实现呢?下面来使用代码实现上面的功能。

1)、测试类 PafcmallSearchApplicationTests.java 中添加测试方法searchData()

/**
 * 检索数据
 *
 * @throws IOException
 */
@Test
void searchData() throws IOException {
    // 1、创建检索请求
    SearchRequest searchRequest = new SearchRequest();
    // 指定索引
    searchRequest.indices("bank");
    // 指定DSL,检索条件
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    // 1.1)、构造检索条件
    //        sourceBuilder.query(QueryBuilders.termQuery("user", "kimchy"));
    //        sourceBuilder.from(0);
    //        sourceBuilder.size(5);
    //        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));

    searchSourceBuilder.query(QueryBuilders.matchQuery("address","mill"));

    // 聚合数据
    // 1.2)、根据年龄分布聚合
    TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
    searchSourceBuilder.aggregation(ageAgg);

    // 1.3)、计算平薪资
    AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
    searchSourceBuilder.aggregation(balanceAvg);

    System.out.println("检索条件:"+searchSourceBuilder.toString());
    searchRequest.source(searchSourceBuilder);

    // 2、执行检索
    SearchResponse searchResponse = client.search(searchRequest, PafcmallElasticsearchConfig.COMMON_OPTIONS);

    // 3、分析结果 searchResponse
    System.out.println(searchResponse.toString());

    //3.1)、获取所有查到的数据
    SearchHits hits = searchResponse.getHits(); // 获取到最外围的 hits
    SearchHit[] searchHits = hits.getHits(); // 内围的 hits 数组
    for (SearchHit hit : searchHits) {
        /**
             * "_index":"bank",
             *        "_type":"account",
             *        "_id":"970",
             *        "_score":5.4032025,
             *        "_source":{
             */
        //            hit.getIndex();hit.getType()''
        String str = hit.getSourceAsString();
        Account account = JSON.parseObject(str, Account.class);

        System.out.println(account.toString());

    }
    //3.1)、获取这次检索到的分析数据
    Aggregations aggregations = searchResponse.getAggregations();
    // 可以遍历获取聚合数据
    //        for (Aggregation aggregation : aggregations.asList()) {
    //            System.out.println("当前聚合:"+aggregation.getName());
    //            aggregation.getXxx
    //        }
    // 也可使使用下面的方式
    Terms ageAgg1 = aggregations.get("ageAgg");
    for (Terms.Bucket bucket : ageAgg1.getBuckets()) {
        String keyAsString = bucket.getKeyAsString();
        System.out.println("年龄:"+keyAsString+" ==> 有 "+bucket.getDocCount()+" 个");
    }

    Avg balanceAvg1 = aggregations.get("balanceAvg");
    System.out.println("平均薪资:"+balanceAvg);
}

2)、添加收集结果的测试类:

/**
 * 测试用账号类
 */
@ToString
@Data
static class Account {
    private int account_number;
    private int balance;
    private String firstname;
    private String lastname;
    private int age;
    private String gender;
    private String address;
    private String employer;
    private String email;
    private String city;
    private String state;
}

3)、执行测试方法,结果如下:


在这里插入图片描述

在这里插入图片描述

可以看到结果第一行中有一个 boost 参数,这个是系统自动为我们添加的,之前在 kibana 中使用 DSL 语言检索数据的时候是没有的。那么这个 boost 到底是什么呢?
参考官方文档,可以得出结论。
在这里插入图片描述

在这里插入图片描述

再来看查询结果,使用 json 工具格式化可以看到返回符合条件的数据有 4 条,和之前 kibana 中查出的一致:


在这里插入图片描述

以上,便是 SpringBoot 整合 ES 的全部内容,更多高级用法可以参考 ES 的官方文档进行尝试。

更多检索信息请参考 java-rest-high-search

参考文档-java-rest

总结

当然 ES 的在实际的生产中应用广泛:

比如使用 ELK 组件用来进行日志的收集或者进行全文的检索

在这里插入图片描述

或者用来收集异常信息,做成可视化的界面来提供分析等:

在这里插入图片描述

更多应用场景,还需要和实际的生产结合起来,也需要我们自己去尝试和探索。


六、附录-安装nginx

1、重装 ES

在安装 nginx 之前,需要重新安装一下 ES ,因为之前安装的 ES 的最大内存设置的是128M,在使用会出现各种问题,现在改成512M的,最快速的方式是删除原来的容器,然后重新创建一个。
那么之前的 ES 的数据会丢失吗?答案是不会。因为之前我在安装 ES 的时候进行了文件目录的映射,所有的数据文件都存在虚拟机之上,而不是 docker 容器之中。

docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2

# docker run --name elasticsearch 创建一个es容器并起一个名字;
# -p 9200:9200 将linux的9200端口映射到docker容器的9200端口,用来给es发送http请求
# -p 9300:9300 9300是es在分布式集群状态下节点之间的通信端口  \ 换行符
# -e 指定一个参数,当前es以单节点模式运行
# *注意,ES_JAVA_OPTS非常重要,指定开发时es运行时的最小和最大内存占用为64M和128M,否则就会占用全部可用内存
# -v 挂载命令,将虚拟机中的路径和docker中的路径进行关联
# -d 后台启动服务
在这里插入图片描述

2、安装 Nginx

  • /mydata 目录下新建一个 nginx 目录,之后所有 nginx 的安装数据都放在这里:

    在这里插入图片描述

  • 随便启动一个nginx 实例,只是为了复制出配置:

docker run -p 80:80 --name nginx -d nginx:1.10 #如果当前本地docker镜像中没有nginx,那么它会自动下载并创建一个并服务
在这里插入图片描述
  • 将容器内的配置文件拷贝到当前目录:
docker container cp nginx:/etc/nginx .

别忘了后面的点

在这里插入图片描述
  • 终止原容器: docker stop nginx

  • 执行命令删除原容器: docker rm $Containerld

    在这里插入图片描述

  • 修改文件名称: mv nginx conf 把这个 conf 移动到 /mydata/nginx

    在这里插入图片描述

  • 创建新的 nginx,执行以下命令

docker run -p 80:80 --name nginx \
-v /mydata/nginx/html:/usr/share/nginx/html \
-v /mydata/nginx/logs:/var/log/nginx \
-v /mydata/nginx/conf:/etc/nginx \
-d nginx:1.10
在这里插入图片描述
  • 访问 http://192.168.56.10/,可以看到 nginx 服务器已经安装成功了
    在这里插入图片描述

因为当前 nginx 没有指定默认的页面,所以访问的时候会出现403错误,这是正常的
如果想要设置默认的访问页,可以在/mydata/nginx/html目录下新建一个index.html文件,附上代码:

<!DOCTYPE html>
<html>
  <head>
      <meta charset="utf-8">
      <title></title>
  </head>
  <body>
      <h1>PafcMall</h1>
  </body>
</html>
在这里插入图片描述

重新访问 http://192.168.56.10/

在这里插入图片描述

补充 - Vagrant 创建虚拟机 - 修改 linux 网络设置 & 开启 root 密码访问

一、修改 linux 网络设置

1、进入网卡设置目录下

cd sysconfig/network-scripts/
在这里插入图片描述

2、查看当系统前网卡信息

ip addr
在这里插入图片描述

3、修改网卡地址,添加网关和 DNS 服务

vi ifcfg-eth1
在这里插入图片描述
NM_CONTROLLED=yes
BOOTPROTO=none
ONBOOT=yes
IPADDR=192.168.56.10
NETMASK=255.255.255.0
GATEWAY=192.168.56.1
DNS1=114.114.114.114
DNS2=8.8.8.8
DEVICE=eth1
PEERDNS=no
在这里插入图片描述
service network restart
在这里插入图片描述

4、配置新的 yum 源,提升软件安装下载速度

curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.163.com/.help/CentOS7-Base-163.repo
yum makecache #使新的yum源生效
在这里插入图片描述

二、开启 root 密码访问

1、修改 sshd_config 文件

vi /etc/ssh/sshd_config  # Vagrant ssh进去系统之后,修改sshd_config文件
在这里插入图片描述
PasswordAuthentication yes/no # 修改 no 为 yes
在这里插入图片描述

2、重启服务

service sshd restart
在这里插入图片描述

参考:

Elasticsearch Reference

elastic

全文搜索引擎 Elasticsearch 入门教程

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271