ElasticSearch的学习笔记并整合SpringBoot做测试
ElasticSearch的学习
简介
ElasticSearch是一个分布式的开源搜索和分析引擎,MySQL专攻于数据的持久化存储与管理(即CRUD),在真正要处理海量数据的检索与分析时,ElasticSearch是更胜一筹的,可以秒级地从海量数据中检索出需要需要的数据,而MySQL如果单表达到百万以上的数据再进行检索是非常慢的。
ElasticSearch功能有很多,包括各种检索功能(应用程序搜索、网站搜索、企业搜索)、对检索来的数据做处理分析(特别是日志处理和分析)、应用指标的监控、数据的分析和可视化等。
ElasticSearchm是目前全文搜索引擎的首选,可以快速地存储、搜索和分析海量数据。数据默认放在内存中
其底层是用以前Apache的开源库Lucene,对Lucene做了再一次的简化封装,直接提供REST API的操作接口,比如直接给ElasticSearch发请求就可以使用其复杂的检索功能。
一、基本概念
1、Index(索引)
动词,相当于MySQL中的insert,在MySQL中插入一条数据 = 在ElasticSearch中索引一条数据;
名词,相当于MySQL中的Database,MySQL中的库 = ElasticSearch的索引
2、Type(类型)【根据当前ES版本最新文档,“Before 7.0.0, the mapping definition included a type name. Elasticsearch 7.0.0 and later no longer accept a default mapping. See Removal of mapping types.”,类型已被移除。具体原因及解决见 Mapping 知识块】
在Index(索引)中,可以定义一个或多个类型;
类似于MySQL中的Table;每一种类型的数据放在一起
3、Document
保存在某个索引(Index)下,某种类型(Type)的一个数据(Document),文档是JSON格式的,Document就像是MySQL中的某个Table里面的内容(一条条的记录)
4、ElasticSearch概念 — 倒排索引机制
分词:将整句分拆为单词;
将拆分出来的单词和该单词出现的记录存放在ElasticSearch额外维护的倒排索引表中
检索:拆分检索的数据为单词,在倒排索引表中查询这些单词分别在哪些记录中
相关性得分,将相关性得分高的记录数据查出来
二、docker安装ElasticSearch
修改 Linux 网络设置
[root@localhost ~]# cd /etc/sysconfig/network-scripts/
[root@localhost network-scripts]# ls
ifcfg-eth0 ifdown-bnep ifdown-isdn ifdown-sit ifup ifup-ippp ifup-plusb ifup-sit ifup-wireless
ifcfg-eth1 ifdown-eth ifdown-post ifdown-Team ifup-aliases ifup-ipv6 ifup-post ifup-Team init.ipv6-global
ifcfg-lo ifdown-ippp ifdown-ppp ifdown-TeamPort ifup-bnep ifup-isdn ifup-ppp ifup-TeamPort network-functions
ifdown ifdown-ipv6 ifdown-routes ifdown-tunnel ifup-eth ifup-plip ifup-routes ifup-tunnel network-functions-ipv6
[root@localhost network-scripts]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 52:54:00:4d:77:d3 brd ff:ff:ff:ff:ff:ff
inet 10.0.2.15/24 brd 10.0.2.255 scope global noprefixroute dynamic eth0
valid_lft 83062sec preferred_lft 83062sec
inet6 fe80::5054:ff:fe4d:77d3/64 scope link
valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:87:53:b1 brd ff:ff:ff:ff:ff:ff
inet 192.168.56.10/24 brd 192.168.56.255 scope global noprefixroute eth1
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fe87:53b1/64 scope link
valid_lft forever preferred_lft forever
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:da:49:0e:5c brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:daff:fe49:e5c/64 scope link
valid_lft forever preferred_lft forever
6: veth9f2577f@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 4a:e1:fa:70:da:a3 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::48e1:faff:fe70:daa3/64 scope link
valid_lft forever preferred_lft forever
8: veth938fc84@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 2a:e1:8c:3b:bc:66 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::28e1:8cff:fe3b:bc66/64 scope link
valid_lft forever preferred_lft forever
12: vethb2c64c0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 0e:fa:e2:77:1a:62 brd ff:ff:ff:ff:ff:ff link-netnsid 3
inet6 fe80::cfa:e2ff:fe77:1a62/64 scope link
valid_lft forever preferred_lft forever
18: veth9f373db@if17: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 1e:42:ad:3e:88:f2 brd ff:ff:ff:ff:ff:ff link-netnsid 2
inet6 fe80::1c42:adff:fe3e:88f2/64 scope link
valid_lft forever preferred_lft forever
[root@localhost network-scripts]# vi ifcfg-eth1
#VAGRANT-BEGIN
# The contents below are automatically generated by Vagrant. Do not modify.
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# 公共DNS,用于解析域名
DNS2=8.8.8.8
DEVICE=eth1
PEERDNS=no
#VAGRANT-END
[root@localhost network-scripts]# service network restart
修改 Linux 的 yum 源
- 备份原 yum 源
- 使用新 yum 源
- 生成缓存
[root@localhost network-scripts]# mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
[root@localhost network-scripts]# curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.163.com/.help/CentOS7-Base-163.repo
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1572 100 1572 0 0 3849 0 --:--:-- --:--:-- --:--:-- 3852
[root@localhost network-scripts]# yum makecache
Loaded plugins: fastestmirror
Determining fastest mirrors
base | 3.6 kB 00:00:00
docker-ce-stable | 3.5 kB 00:00:00
extras | 2.9 kB 00:00:00
updates | 2.9 kB 00:00:00
(1/11): base/7/x86_64/other_db | 2.6 MB 00:00:01
(2/11): base/7/x86_64/filelists_db | 7.2 MB 00:00:01
(3/11): docker-ce-stable/7/x86_64/filelists_db | 24 kB 00:00:05
(4/11): extras/7/x86_64/filelists_db | 226 kB 00:00:00
(5/11): extras/7/x86_64/other_db | 134 kB 00:00:00
(6/11): extras/7/x86_64/primary_db | 225 kB 00:00:00
(7/11): updates/7/x86_64/primary_db | 5.6 MB 00:00:00
(8/11): updates/7/x86_64/other_db | 454 kB 00:00:00
(9/11): updates/7/x86_64/filelists_db | 3.4 MB 00:00:00
(10/11): docker-ce-stable/7/x86_64/other_db | 117 kB 00:00:01
(11/11): docker-ce-stable/7/x86_64/primary_db | 56 kB 00:00:09
Metadata Cache Created
1、下载镜像文件
docker pull elasticsearch:7.10.1#存储和检索数据,相当于MySQL服务
docker pull kibana:7.10.1#可视化检索数据,相当于SQLyog
两个镜像版本需统一
2、创建实例
1)、Elasticsearch
mkdir -p /mydata/elasticsearch/config
#将在docker容器中的ElasticSearch所有的配置文件信息都挂载在外面虚拟机中的/mydata/elasticsearch/config文件夹下,通过修改虚拟机/mydata/elasticsearch/config中配置文件的配置信息,就能修改docker容器中ElasticSearch的配置
mkdir -p /mydata/elasticsearch/data
#同样将ElasticSearch中的一些数据文件挂载在外面虚拟机中的/mydata/elasticsearch/data文件夹下。
echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml
#将http.host: 0.0.0.0写入elasticsearch.yml配置文件中,以便ElasticSearch可以让远程的任何机器进行访问
#如果在之后一部步启动elasticsearch容器报错,并无法通过浏览器访问elasticsearch的9200端口访问到信息,可以查看日志排查,如是文件权限问题,进行以下修改。也可以事先进行文件权限修改
docker logs elasticsearch#查看elasticsearch启动的日志
ll /mydata/elasticsearch/#查看/mydata/elasticsearch/目录下的文件的权限,包括config、data、plugins
#发现三个文件都为drwxr-xr-x权限,文件所有人(这里是root用户)可读可写可执行rwx,文件所有组和其他人只有可读和可执行的权限r-x。
#解读drwxr-xr-x:
#第1位 d代表文件类型,-:普通文件,d:目录文件,l:链接文件,b:设备文件,c:字符设备文件,p:管道文件
#第2-4位rwx代表文件所有者(属主)有可读可写可执行的权限
#第5-7位r-x代表文件所有组(属组,与属主同一组的用户)有可读可执行的权限
#第8-10位r-x代表其他人有可读可执行的权限
#r:可读,数字表示为4;w:可写,数字表示为2;x:可执行,数字表示为1。
chmod -R 777 /mydata/elasticsearch/ #chmod是修改权限的命令;-R是可选项,表示递归;777表示将任何用户任何组的权限改为可读可写可执行,对应了三个角色;改的是/mydata/elasticsearch/目录下的所有文件
#-R是命令可选项,参考如下说明:
#-c : 若该文件权限确实已经更改,才显示其更改动作
#-f : 若该文件权限无法被更改也不要显示错误讯息
#-v : 显示权限变更的详细资料
#-R : 对目前目录下的所有文件与子目录进行相同的权限变更(即以递回的方式逐个变更)
#--help : 显示辅助说明
#--version : 显示版本
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.10.1
#--name elasticsearch表示为ElasticSearch镜像起个名字为elasticsearch;
#-p 9200:9200 -p 9300:9300暴露了两个端口,一个为9200,一个为9300。9200是后来由REST API向ElasticSearch的9200端口发送http请求,9300是ElasticSearch在分布式集群状态下节点之间的通信端口
#-e "discovery.type=single-node"ElasticSearch以单节点模式运行
#-e ES_JAVA_OPTS="-Xms64m -Xmx512m"很重要!如果不指定该条,ElasticSearch一启动会将内存全部占用,会导致整个虚拟机卡死,因此在此指定Xms64m初始内存为64兆,最大占用内存为512兆(测试期间够用,但真正服务上线后,公司的检索服务器内存一般都是32G左右,因此可以给ElasticSearch多分配)
#-v /mydata/elasticsearch/config/elasticsearch.yml:/user/share/elasticsearch/config/elasticsearch.yml -v是进行挂载,相当于将容器中ElasticSearch中的所有配置,跟外部虚拟机创建的配置文件进行一一关联,以后修改外部的就相当于修改容器内的。包括挂载data数据目录、plugins插件目录
#-d elasticsearch:7.10.1最后-d用elasticsearch:7.10.1镜像启动ElasticSearch
2)、Kibana
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.56.10:9200 -p 5601:5601 -d kibana:7.10.1
#-e修改参数ELASTICSEARCH_HOSTS,修改ES主机地址修改为自己虚拟机的地址
#-p映射到端口5601,通过访问5601端口访问到kibana的可视化界面
#然后从可视化界面,kibana把请求发送给ES的http://192.168.56.10:9200
三、初步检索
*:http://192.168.56.10:9200
1、_cat
GET */cat/nodes# 查看所有节点
GET */_cat/health# 查看ES健康状况
GET */_cat/master# 查看主节点
GET */_cat/indices# 查看所有索引show databases;
2、索引一个文档(保存记录)
保存一个数据,保存在哪个索引的哪个记录下,指定用哪个唯一标识
PUT customer/external/1;在customer索引下的external类型下保存1号数据为:
PUT */customer/external/1# postman发送请求:
{"name":"Cyril_P"} # 并带上数据:
PUT请求和POST请求都可以保存数据,但是PUT请求必须带上id,POST请求可以不带id。
如果POST请求不指定id,会自动生成id;如果指定id,并且该id之前没数据就会新增,继续指定该id,就会修改这个数据,并新增版本号
PUT可以新增可以修改,但PUT必须指定id;由于PUT需要指定id,我们一般都用来做修改操作,PUT不指定id会报错
3、查询文档
GET */customer/external/1
{// 带下划线'_'表示元数据
"_index": "customer",// 在哪个索引
"_type": "external",// 在哪个类型
"_id": "1",// 记录id
"_version": 1,// 版本号
"_seq_no": 0,// 并发控制字段,每次更新就会 1,用来做乐观锁
"_primary_term": 1,// 同上,主分片重新分配,如重启,就会变化,用来做乐观锁
"found": true,// 为true,代表找到数据
"_source": {// 查询到的真正内容
"name": "Cyril_P"
}
}
乐观锁处理多个修改请求,更新文档uri需携带 ?if_seq_no=0&if_primary_trem=1
4、更新文档
POST */customer/external/1/_update
{"doc": {"name": "PrPrPr"}}
会对比原来数据,与原来一样就什么都不做,version、seq_no都不变;
或
POST */customer/external/1/
{"doc": {"name": "PrPrPr"}}
直接更新数据,version和seq_no会改变;
或
PUT */customer/external/1/
{"doc": {"name": "PrPrPr"}}
直接更新数据,version和seq_no会改变。
更新同时增加属性,以上三个都可以:
{“doc”: {“name”: “PrPrPr”,“age”:21}}
5、删除文档&索引
DELETE */customer/external/1/
DELETE */customer
6、bulk批量API 【在这之后的请求体无法在postman中测试,均移步至Kibana上测试】
POST /customer/external/_bulk
{"index":{"_id":"1"}} # index表示插入动作
{"name":"Cyril_P"}
{"index":{"_id":"2"}}
{"name":"PrPrPrPr"}
语法格式:
{action:{metadata}}
{requestbody}
{action:{metadata}}
{requestbody}
复杂实例:(metadata前带有"_",此处markdown没有显示,index、type和id前都有)
POST /_bulk
{"delete":{"_index":"website","_type":"blog","_id":"123"}}# delete表示删除动作
{"create":{"_index":"website","_type":"blog","_id":"123"}}# create表示创建动作
{"title":"My First Blog POST"}# 真正的内容
{"index":{"_index":"website","_type":"blog"}}# index表示插入动作
{"title":"My Second Blog POST"}# 真正的内容
{"update":{"_index":"website","_type":"blog","_id":"123","retry_on_conflict":3}}# update表示更新动作
{"doc":{"title":"My Updated Blog POST"}}# 真正的内容
四、进阶检索
1、SearchAPI
ES支持两种基本方式检索:
一个是通过使用 REST request URI 发送搜索参数(uri 检索参数);
另一个是通过使用 REST request body 来发送它们(uri 请求体)。
1)、检索信息
一切检索从 _search 开始
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 请求体进行检索
GET/bank/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"account_number": "asc"
}
]
}
HTTP 客户端工具(postman),GET 请求不能携带请求体,我们变为 POST 也是一样的,我们 POST 一个 JSON 格式的查询请求体到_search API。
需要了解,一旦搜索的结果被返回,ElasticSearch 就完成了这次请求,并且不会维护任何服务端的资源或者结果的 cursor(游标)。
2、Query DSL
1)、基本语法格式
ElasticSearch 提供了一个可以执行查询的 JSON 格式的 DSL(domain-specific language 领域特定语言),这个被称为 Query DSL。
- Query DSL 的典型结构如下:
{
QUERY_NAME:{
ARGUMENT:VALUE,
ARGUMENT:VALUE,...
}
}
- 如果是针对某个字段,那么结构如下:
{
QUERY_NAME:{
FILED_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": ["account_number","balance"]
}
- 真正内容"_source"只查询"account_number"、"balance"两个字段
3)、match【匹配查询】
- 基本类型(非字符串),精准匹配
GET /bank/_search
{
"query": {
"match": {
"account_number": "20"
}
}
}
match返回 account_number = 20 的文档。
- 字符串,全文检索(如是单词便进行全文检索;如多个单词的字符串,会先进行分词,并进行全文检索;都会根据相关性得分排序)
GET /bank/_search
{
"query": {
"match": {
"address": "mill lane"
}
}
}
会对检索条件"mill lane"进行分词匹配,最终查询出 address 中包含 "mill "或者 "lane "或者 “mill lane” 的所有文档,并给出相关性得分,全文检索按照相关性得分进行排序。
4)、match_phrase【短语匹配】
将需要匹配的值当成一个整体单词(不分词)进行检索。
GET /bank/_search
{
"query": {
"match_phrase": {
"address": "mill lane"
}
}
}
查出 address 中包含 “mill lane” 的所有文档,并给出相关性得分,全文检索按照相关性得分进行排序。
5)、multi_match【多字段匹配】
GET /bank/_search
{
"query": {
"multi_match": {
"query": "mill movico",
"fields": ["address","city"]
}
}
}
会对 “mill movico” 进行分词,查出 address 或者 city 包含 "mill "或者 "movico "或者 “mill movico” 的所有文档。
6)、bool【复合查询】
bool 用来做复合查询:
复合语句可以合并任何其他查询语句,包括复合语句,意味着 复合语句之间可以互相嵌套,表达非常复杂的逻辑。
must、must_not、should:
must:必须达到 must 列举的所有条件
should:应该达到 should 列举的所有条件,如果达到会增加相关文档的评分(相关性得分),并不会改变查询的结果。如果 query 中只有 should 且只有一种匹配规则,那么 should 的条件就会被作为默认匹配条件而去改变查询结果
must_not:必须不是指定的情况
GET /bank/_search
{
"query": {
"bool": {
"must": [
{"match": {
"gender": "M"
}},
{"match": {
"address": "mill"
}}
],
"must_not": [
{"match": {
"age": "18"
}}
],
"should": [
{"match": {
"lastname": "Hines"
}}
]
}
}
}
查询出 gender 必须是 "M"并且 address 必须包含 “mill”,如果 lastname 里有 “Hines” 就最好并且会加额外相关性得分,但是 age 必须不能是 18 的所有文档。
7)、filter【结果过滤】
并不是所有的查询都需要产生相关性得分,特别是那些仅用于 “filtering”(过滤)的文档。为了不计算相关性得分 ElasticSearch 会自动检查场景并且优化查询的执行。
GET /bank/_search
{
"query": {
"bool": {
"filter": [
{
"range": {
"age": {
"gte": 18,
"lte": 30
}
}
}
]
}
}
}
GET /bank/_search
{
"query": {
"bool": {
"must": [
{"match": {
"gender": "M"
}},
{"match": {
"address": "mill"
}}
],
"must_not": [
{"match": {
"age": "18"
}}
],
"should": [
{"match": {
"lastname": "Hines"
}}
],
"filter": [
{
"range": {
"age": {
"gte": 18,
"lte": 30
}
}
}
]
}
}
}
filter 不会计算相关性得分,会直接将不满足 filter 的文档给过滤掉。
8)、term
和 match 一样,匹配某个属性的值。全文检索字段用 match,其他非 text 字段匹配用 term。
GET /bank/_search
{
"query": {
"term": {
"age": "25"
}
}
}
GET /bank/_search
{
"query": {
"match_phrase": {
"address": "927 Bay"
}
}
}
GET /bank/_search
{
"query": {
"match": {
"address.keyword": "927 Bay"
}
}
}
非text(非文本)字段建议使用 term 进行匹配,文本字段的全文检索建议使用 match
对于文本字段的匹配,“match_phrase” 与 “address.keyword” 不同的是整个 "address.keyword "的内容就是 “927 Bay” 的全部值,进行的是精确匹配;而 “match_phrase” 做的是短语匹配,意思是 address 文本里面包含一个完整短语 “927 Bay”
9)、aggregations(执行聚合)
聚合提供了从函数中分组和提取数据的能力,最简单的聚合方法大致等于SQL GROUP BY 和 SQL 聚合函数。在 ElasticSearch 中,可以执行搜索返回 hits(命中结果),并且同时返回聚合结果,把一个响应中的所有 hits(命中结果)分隔开。我们就能够执行查询和多个聚合,并且在一次使用中得到各自的(任何一个的)返回结果,使用一次简洁的 API 来避免网络往返。
- 搜索 address 中包含 mill 的所有人的年龄分布以及平均年龄,但不显示这些人的详情。
GET /bank/_search
{
"query": {# 先查询出来 address 中包含 mill 的所有文档
"match": {
"address": "mill"
}
},
"aggs": {# 执行聚合
"ageAgg": {# 本次聚合的名字,方便展示在结果集中
"terms": {# 聚合的类型【只看某个字段的信息】
"field": "age",# 对哪个字段进行聚合
"size": 10# 对聚合的结果作取几条数据处理
}
},
"ageAvg":{# 本次聚合的名字,方便展示在结果集中
"avg": {# 聚合的类型 【取平均值】
"field": "age"# 对哪个字段进行聚合
}
},
"balanceAvg":{# 本次聚合的名字,方便展示在结果集中
"avg": {# 聚合的类型 【取平均值】
"field": "balance"# 对哪个字段进行聚合
}
}
},
"size": 0# 不显示搜索数据
}
- 按照年龄聚合,并且请求这些年龄段的这些人的平均薪资
先根据年龄聚合,再在这些年龄的聚合中套用聚合算出薪资平均值
GET /bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"balanceAvg": {
"avg": {
"field": "balance"
}
}
}
}
}
}
- 查出所有年龄分布,并且这些年龄段中 M 的平均薪资和 F 的平均薪资以及这个年龄段的总体平均薪资
GET /bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"genderAgg": {
"terms": {
"field": "gender.keyword",
"size": 10
},
"aggs": {
"balanceAvg": {
"avg": {
"field": "balance"
}
}
}
},
"ageBalanceAvg":{
"avg": {
"field": "balance"
}
}
}
}
}
}
3、Mapping【详细参考】
1)、字段类型【详细参考】
核心类型 | |
---|---|
字符串(String) | text, keyword |
数字类型(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-Points) | geo_point 用于描述 经纬度坐标 |
地理图形(Geo-Shape) | geo_shape 用于描述 复杂形状,如多边形 |
特定类型 | |
---|---|
IP类型 | ip 用于描述 ipv4 和 ipv6 地址 |
补全类型(Completion) | completion 提供自动完成提示 |
令牌计数类型(Token count) | token_count 用于 统计字符串中的词条数量 |
附件类型(Attachment) | 参考 mapper-attachments 插件,支持将附件如 Microsoft Office格式、 Open Document格式、ePub、HTML等等索引为 attachment 数据类型 |
抽取类型(Percolator) | 接受特定领域查询语言(query-dsl)的查询 |
多字段 |
---|
通常用于为不同目的用不同的方法索引同一个字段。例如,string 字段可以映射为一个 text 字段用于全文检索,同样可以映射为一个 keyword 字段用于排序和聚合。另外,你可以使用 standard analyzer,english analyzer,french analyzer 来索引一个text字段。 这就是 muti-fields 的目的。大多数的数据类型通过 fields 参数来支持 muti-fields。 |
2)、映射
Mapping(映射)
Mapping 是用来定义一个文档(document),以及它所包含的属性(field)是如何存储和索引的。比如,使用 mapping 来定义:
- 哪些字符串属性应该被看成全文本属性(full text firlds);
- 哪些属性包含数字、日期或者地理位置;
- 文档中的所有属性是否都能被索引( _all 配置);
- 日期的格式;
- 自定义映射规则来执行动态添加属性
查看 mapping 信息:
GET /bank/_mapping
3)、新版本改变
ElasticSearch7 及以后版本移除了 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、创建索引,并指定索引下每一个数据的类型,指定数据的映射规则
PUT /my_index# 发送PUT请求,指定索引,在索引后不需要加type了
{
"mappings": {# "mappings" 创建映射规则
"properties": {# "properties" 指明每一个属性的映射类型
"age":{"type": "integer"},# "属性名":{【详细规则:】"type": "指定什么类型"}
"email":{"type": "keyword"},# "属性名":{"type": "指定什么类型"}
"name":{"type": "text"}# "属性名":{"type": "指定什么类型"}
}
}
}
2、添加新的字段映射
PUT /my_index/_mapping# 发送PUT请求,指定修改某个索引"index"下的映射"_mapping",仅限于添加新的字段
{
"properties": {# "properties" 指明每一个属性的映射类型
"employee-id": {# 定义新增加的属性,为"employee-id"
"type": "keyword", # 指定类型
"index": false# 映射参数"index"用于控制该属性能否被索引,能否被检索到,默认为true可以被索引
}
}
}
3、更新映射
对于已经存在的映射字段,不能通过上面两个操作实现更新操作。更新必须通过创建新的索引并进行数据迁移。
4、数据迁移
先创建出一个正确映射,同时最好正确指定好每一个数据的类型,然后使用 “_reindex” 进行数据迁移
创建步骤参考第一个创建索引并指定每一个数据类型操作,字段名要统一,可以先查看旧索引的 mapping 信息,然后复制 properties 属性再进行字段属性类型修改
POST _reindex# 发送POST请求,进行"_reindex"索引下数据迁移操作
{
"source": {# 来源于哪个旧索引
"index": "bank",# 旧索引的索引名
"type": "account"# 如果是带有type的,就写上type名。没有的话就可以不写
},
"dest": {# 目标索引,新索引
"index": "newbank"# 新索引的名字
}
}
4、分词
ES有个分词器 tokenizer 能接收一个字符流,将之分割为独立的tokens(词元,通常是独立的单词),然后输出 tokens 流。
同时ES提供了很多内置的分词器,可以用来构建 custom analyzer(自定义分词器)。
1)、安装ik分词器
注意:不要用默认 elasticsearch-plugin install xxx.zip 进行自动安装。
要进入 https://github.com/medcl/elasticsearch-analysis-ik/releases 对应ES版本安装
- 进入 ES 容器内部 plugins 目录
- docker exec -it 容器id /bin/bash
- wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.10.1/elasticsearch-analysis-ik-7.10.1.zip
- upzip 下载的文件
- rm -rf *.zip
- mv elasticsearch/ ik
- 可以确认是否安装好了分词器
- cd …/bin
- elasticsearch plugin list:即可列出系统的分词器
2)、测试分词器
POST _analyze
{
"analyzer": "ik_smart",
"text": ["我是中国人"]
}
POST _analyze
{
"analyzer": "ik_max_word",# 找到最大的单词组合
"text": ["我是中国人"]
}
3)、自定义词库
修改 /usr/share/elasticsearch/plugins/ik/config 中的 IKAnalyzer.cfg.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">http://192.168.56.10/es/fenci.txt</entry>
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
五、ElasticSearch-Rest-Client
1、对比
1)、9300:TCP
- spring-data-elasticsearch:transport-api.jar
springboot版本不同,transport-api.jar 不同,不能适配 es 版本
7.x 已经不建议使用,8 以后就要废弃
2)、9200:HTTP
JestClient:非官方,更新慢
RestTemplate:模拟发 HTTP 请求,ES 很多操作需要自己封装,麻烦
HttpClient:同上
Elasticsearch-Rest-Client:官方 RestClient,封装了 ES 操作,API 层次分明,上手简单
最终选择 Elasticsearch-Rest-Client(elasticsearch-high-level-client)
2、实例
1)、导入依赖后编写配置类
/**
* 1、导入依赖
* 2、编写配置,给容器中注入一个 RestHighLevelClient
* 3、参照 API https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.x/java-rest-high.html
*/
@Configuration
public class MyElasticSearchConfig {
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();
}
@Bean
public RestHighLevelClient esRestClient() {
RestClientBuilder builder = null;
builder = RestClient.builder(new HttpHost("IP", 9200, "http"));
RestHighLevelClient client = new RestHighLevelClient(builder);
// RestHighLevelClient client = new RestHighLevelClient(
// RestClient.builder(
// new HttpHost("IP", 9200, "http")));
return client;
}
}
2)、测试类
@RunWith(SpringRunner.class)
@SpringBootTest
class MymallSearchApplicationTests {
@Autowired
private RestHighLevelClient client;
/**
*
*/
@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;
}
/**
*
*/
@Test
public void searchIndex() throws IOException {
//1、创建检索请求
SearchRequest searchRequest = new SearchRequest();
//指定索引
searchRequest.indices("bank");
//指定 DSL ,检索条件
// SearchSourceBuilder searchSourceBuilder 封装的条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//1.1)、构造检索条件
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, MymallElasticSearchConfig.COMMON_OPTIONS);
//3、分析结果 searchResponse
System.out.println(searchResponse.toString());
// Map map = JSON.parseObject(searchResponse.toString(), Map.class);
//3.1)、获取所有查到的数据
SearchHits hits = searchResponse.getHits();
SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
/**
* "_index": "bank",
* "_type": "account",
* "_id": "970",
* "_score": 5.4032025,
* "_source": {}
*/
// hit.getIndex();hit.getType();hit.getId();
String sourceAsString = hit.getSourceAsString();
Account account = JSON.parseObject(sourceAsString, Account.class);
System.out.println("account:" account);
}
//3.2)、获取这次检索到的分析信息
Aggregations aggregations = searchResponse.getAggregations();
// for (Aggregation aggregation : aggregations.asList()) {
// System.out.println("当前聚合:" aggregation.getName());
// }
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.getValue());
}
/**
* 测试存储数据到 ES
* 也可以进行更新操作
*/
@Test
public void indexData() throws IOException {
IndexRequest indexRequest = new IndexRequest("users");
indexRequest.id("1");//数据的id
// indexRequest.source("username","PrPrPr","age",22,"gender","女");
User user = new User();
user.setUserName("PrPrPr");
user.setAge(23);
user.setGender("女");
String jsonString = JSON.toJSONString(user);
indexRequest.source(jsonString, XContentType.JSON);//要保存的内容
//执行操作
IndexResponse index = client.index(indexRequest, MymallElasticSearchConfig.COMMON_OPTIONS);
//提取有用的响应数据
System.out.println(index);
}
}
六、安装 nginx
- 随便启动一个 nginx 实例,只是为了复制出配置(如果没有该镜像,也会先下载镜像后启动容器实例)
[root@localhost mydata]# mkdir nginx
[root@localhost mydata]# ls
elasticsearch mysql mysql8 nginx redis
[root@localhost mydata]# docker run -p 80:80 --name nginx -d nginx:1.19
Unable to find image 'nginx:1.19' locally
1.19: Pulling from library/nginx
a076a628af6f: Already exists
0732ab25fa22: Pull complete
d7f36f6fe38f: Pull complete
f72584a26f32: Pull complete
7125e4df9063: Pull complete
Digest: sha256:10b8cc432d56da8b61b070f4c7d2543a9ed17c2b23010b43af434fd40e2ca4aa
Status: Downloaded newer image for nginx:1.19
227cc822c4679c2f6de23bbc6cdd4b27c7555214a959e31bc2aff0ef4c8df0c4
- 将容器内的配置文件拷贝到当前目录【别忘了命令后的点,并且 nginx 和点之间有个空格】
[root@localhost mydata]# ll nginx/
total 0
[root@localhost mydata]# docker container cp nginx:/etc/nginx .
[root@localhost mydata]# ll nginx/
total 36
drwxr-xr-x. 2 root root 26 Feb 27 16:39 conf.d
-rw-r--r--. 1 root root 1007 Dec 15 13:59 fastcgi_params
-rw-r--r--. 1 root root 2837 Dec 15 13:59 koi-utf
-rw-r--r--. 1 root root 2223 Dec 15 13:59 koi-win
-rw-r--r--. 1 root root 5231 Dec 15 13:59 mime.types
lrwxrwxrwx. 1 root root 22 Dec 15 13:59 modules -> /usr/lib/nginx/modules
-rw-r--r--. 1 root root 643 Dec 15 13:59 nginx.conf
-rw-r--r--. 1 root root 636 Dec 15 13:59 scgi_params
-rw-r--r--. 1 root root 664 Dec 15 13:59 uwsgi_params
-rw-r--r--. 1 root root 3610 Dec 15 13:59 win-utf
[root@localhost mydata]# docker stop nginx
nginx
[root@localhost mydata]# docker rm nginx
nginx
- 修改文件 nginx 名称为 conf,并把这个 conf 文件夹移动到 /mydata/nginx 下
[root@localhost mydata]# mv nginx conf
[root@localhost mydata]# ls
conf elasticsearch mysql mysql8 redis
[root@localhost mydata]# mkdir nginx
[root@localhost mydata]# mv conf nginx/
[root@localhost mydata]# ls
elasticsearch mysql mysql8 nginx redis
- 创建新的 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.19