文章目录
简介
全文搜索属于最常见的需求,开源的 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、属性
相当于mysql表中的列
5、ES集群
相当于整个mysql服务
6、倒排索引机制
词 | 记录 |
---|---|
红海 | 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
安装可视化界面
注意,一定要将192.168.56.10
修改为自己的虚拟机地址
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:查看所有节点
- GET /_cat/health:查看es健康状况
- GET /_cat/master:查看主节点
- GET /_cat/indices:查看所有索引 ;相当于 MySQL 的
show databases
;
2、索引一个文档(对应成Mysql就是保存一条记录)
保存一个数据,保存在哪个索引
的哪个类型
下,指定用哪个唯一标识PUT customer/external/1
;
在 customer 索引下的 external 类型下保存 1 号数据为
PUT customer/external/1 |
---|
{ “name”:“lohn Doe” } |
PUT 和 POST 都可以;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}}
{request body} {action: {metadata}} {request body} |
复杂实例 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"}}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
上面直接使用了/_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 (游标) |
查询结果:
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
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 的所有记录,并给出相关性得分
4)、match_phrase 【短语匹配】
将需要匹配的值当成一个整体单词(不分词)进行检索
举个栗子:查出 address 中包含 mill road
的所有记录,并给出相关性得分
GET /bank/_search { "query": { "match_phrase": { "address": "mill road" } } }
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"] } } }
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 因此将返回所有文档的分数。 |
下一节我们来看一下 filter
结果过滤。
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 } } } } } }
8)、term
和 match 一样。匹配某个属性的值。最好全文检索字段用 match,其他非 text 字段匹配用 term
。
Avoid using the
term
query fortext
fields.By default, Elasticsearch changes the values of
text
fields as part of analysis. This can make finding exact matches fortext
field values difficult.To search
text
field values, use thematch
query instead.
非文本值使用 term 检索(term是精确匹配,和match_phrase功能类似):
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" } } }
9) 、aggregations (执行聚合)
聚合提供了从数据中分组和提取数据
的能力。
最简单的聚合方法大致等于 SQL GROUP BY
和 SQL 聚合函数
。
在 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
3、Mapping
1)、字段类型
-
核心类型
- 字符串(string)
text
,keyword
- 数字类型(Numeric)
long
,integer
,short
,byte
,double
,float
,half_float
,scaled_float
- 日期类型(Date)
date
- 布尔类型(Boolean)
boolean
- 二进制类型(Binary)
binary
- 字符串(string)
-
复合类型
- 数组类型(Array)
Array
支持不针对特定的数据类型 - 对象类型(Object)
object
用于单个JSON对象的对象 - 嵌套类型(Nested)
nested
用于JSON对象的数组
- 数组类型(Array)
-
地理类型(Geo)
- 地理坐标(Geo-point)
geo_point
纬度/经度坐标 - 地理圆形(Geo-shape)
geo_shape
用于多边形等复杂形状
- 地理坐标(Geo-point)
-
特定类型
- IP 类型(IP)
ip
用于描述 IPv4 和 IPv6 地址 - 补全类型(Completion)
completion
提供自动完成提示 - 令牌计数类型(Token count)
token_count
用来统计字符串中词条的数量 - 附件类型(attachment)
参考 mapper-attachments 插件,支持将附件例如Microsoft Office格式,open document格式,ePub,HTML等索引为attachment
数据类型。 - 抽取类型(Percolator)
接受来自领域特定语言(query-dsl
)的查询
- IP 类型(IP)
更多字段类型,请参考 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 |
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、更新映射
对于已经存在的映射字段,我们不能更新。更新必须创建新的索引进行数据迁移
4、数据迁移
先创建出 twitter 的正确映射。然后使用如下方式进行数据迁移
# 7.x 之后的写法,没有了type字段,可以直接指定旧索引和新的索引 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", "fields": { "keyword": { "type": "keyword", "ignore_above": "256" } } }, "state": { "type": "keyword" } } } }
数据迁移
:
POST _reindex { "source": { "index": "bank", "type": "account" }, "dest": { "index": "newbank" } }
查看迁移后的数据
:
可以看到,不用 type,老的数据可以迁移过来
GET newbank/_search
4、分词
一个 tokenizer
(分词器)接收一个字符流
,将之分割为独立的 tokens
(词元,通常是独立的单词),然后输出 tokens
流。
例如, whitespace tokenizer
遇到空白字符时分割文本。它会将文本"Quick brown fox!"分割为[Quick
, brown
, fox!
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电商项目" }
结果:
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,因为要选择分词器。
3)、自定义词库
ik 分词器默认的分词并不能满足我们的需求,对于一些新的网络用语,ik 分词器就会无法准确的进行分词识别,比如:
POST _analyze { "analyzer": "ik_max_word", "text": "乔碧萝殿下" }
分词之后显示为如下,可以看到 ik 分词器无法识别出“乔碧萝”是一个人名:
所以,需要进行自定义拓展词库。
要自定义拓展词库,可以修改 ik 分词器的配置文件,指定一个远程词库,让 ik 分词器向远程发送请求,要到一些最新的单词,这样最新的单词就会作为最新的词源进行分解。
自定义词库有两种方式实现:
- 自己实现一个服务,处理 ik 分词器的请求,让 ik 分词器的给自定义的项目发送请求
- 搭建一个 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
五、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
:同上 IElasticsearch-Rest-Client
:官方 RestClient,封装了 ES 操作, API 层次分明,上手简单最终选择
Elasticsearch-Rest-Client(elasticsearch-rest-high-level-client
)
1、SpringBoot整合
1)在pafcmall
项目中新添加一个模块pafcmall-search
,当然你也可以,单独创建一个项目
使用 spring 启动器创建:
添加 group
和 artifact
信息:
添加 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
文件,让 ES
和 elasticsearch-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
使用测试类测试:
@RunWith(SpringRunner.class) @SpringBootTest class GulimallSearchApplicationTests { @Autowired private GulimallElasticSearchConfig gulimallElasticSearchConfig; @Test void contextLoads() { System.out.println(gulimallElasticSearchConfig); } }
更多整合信息请参考 java-rest-high-getting-started-maven 和 java-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-options 和 java-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("平均薪资:"+balanceAvg1.getValueAsString()); }
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
总结
当然 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
参考:
原文链接:
https://blog.csdn.net/runewbie/article/details/106507171
修改了一部分