分享

ElasticSearch的学习笔记并整合SpringBoot做测试

 印度阿三17 2021-03-01

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 源
  1. 备份原 yum 源
  2. 使用新 yum 源
  3. 生成缓存
[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版本安装

  1. 进入 ES 容器内部 plugins 目录
  2. docker exec -it 容器id /bin/bash
  3. wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.10.1/elasticsearch-analysis-ik-7.10.1.zip
  4. upzip 下载的文件
  5. rm -rf *.zip
  6. mv elasticsearch/ ik
  7. 可以确认是否安装好了分词器
  8. cd …/bin
  9. 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./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./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
来源:https://www./content-4-874951.html

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多