文章目录
ElasticSearch
ElasticSearch,简称ES ,是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据。本身的扩展性很好,可以扩展到上百台服务器,可以处理PB(1024TB)级别的数据,它的目的是通过简单的Restful API 来隐藏Lucene的复杂性
elasticSearch是一个搜索引擎框架,elastic是 有弹性的
的意思,所以使用 elasticSearch进行搜索时关键字不准确也一样可以搜到想要的数据
在我们使用Mysql的时候,如果我们想要搜索mysql里的数据,那么需要使用模糊查询,但是搜索效率并不高,所以我们使用elasticSearch来进行我们的搜索操作
ES是一个使用java语言并且基于Lucene编写的搜索引擎框架,提供了分布式的全文搜索功能,提供了统一的基于Restful风格的Web接口,官方客户端也对多种语言都提供了相应的API
Lucene : 本身就是一个搜索引擎的底层,或者是第三方的jar包,但是直接使用比较麻烦,ES作为Lucene的封装,将使用简便化了
分布式: ES突出了高扩展能力,可用于分布式
全文检索:将一段词语进行分词,并且将分出的单个单词统一的放到一个分词库中,在搜索时,根据关键词去分词库中检索,找到匹配的内容(倒排索引)
Restful风格的web接口:操作ES很简单,只需要发送一个HTTP请求,并且根据请求方式的不同,携带方式的不同,执行相应的功能
ES和Slor的区别:
- 这两个都是基于Lucene编写的搜索引擎框架
- Solr在查询死数据时,比ES 更快,但是如果数据是实时改变的,Solr的查询速度会降低很多,但是ES的数据则基本没有变化
- Solr搭建集群需要依赖zookeeper帮助管理,ES本身就支持集群的搭建,不需要第三方的介入
- ES对云计算和大数据支持的特别好
ES的结构:
- book索引: 相当于数据库
- type 一张数据表
- document 一条记录
- field 字段
索引: 可以创建多个索引,那么就是多个数据库。每个索引默认被分成5片分片存储,每一个分片都会存在至少一个备份分片,备份分片默认不会帮助检索数据,只有当ES检索压力很大的时候,部分分片才会帮助检索数据,备份的分片必须放在不同的ES服务器中
类型Type的概念已经被删除了,也就是一个index只能包含一个type,所以在之后的命令中不要带type了
安装:
需要有JDK1.8的环境
下载地址 : https://repo.huaweicloud.com/elasticsearch/7.8.0/
我这里下载的是 elasticsearch-7.8.0-windows-x86_64.zip
解压后如下:
bin是二进制命令文件夹
config是配置文件夹 ,里面的 log4j2.properties
是日志配置文件, jvm.options
是Java虚拟机相关的配置, elasticsearch.yml
是elasticsearch的配置文件。
启动:
使用 /bin/elasticsearch.bat
即可启动,启动端口默认是9200
启动之后,可以访问 http://localhost:9200/
来查看当前ES服务器的信息:
elasticsearch可能会出现跨域问题,我们需要修改 /config/elasticsearch.yml
文件,添加如下内容:
http.cors.enabled: true
http.cors.allow-origin: "*"
ElasticSearch-head插件安装:
我这里选择将插件安装到浏览器里。
先去GitHub上下载 crx文件:
然后将这个 es-head.crx文件重命名为 es-head.zip
然后解压 es-head.zip 文件,得到如下:
然后进入浏览器选择我们的扩展文件夹(这里是 crx),插件就会加载成功了:
我们直接使用插件就行:
KIBANA安装:
使用kibana实现 ES 的可视化(注意kibana 的版本要和ES的版本相同)
KIBANA是一个免费且开放的用户界面,能够让我们对ES数据进行可视化,并让我们可以做各种操作,从跟踪查询负载到理解请求如何流经整个应用
Kibana可以通过各种图表进行高级数据分析及展示。
去GitHub上下载 kibana的压缩包,并解压。
解压之后使用 /bin/kibana.bat
来启动kibana,启动会比较慢
启动完成之后,可以访问 http://localhost:5601/app/kibana#/home
查看
全都是英文的,如果需要汉化:
可以看到这里提供了中文的json
然后去/config/kibana.yml
修改这个文件:
这样就汉化成功
我们可以进入kibana的 Dev Tools
来对 ES操作(发送各种HTTP请求即可,这部分也可以使用POSTMAN)
分词器:
分词: 就是把一段中文或者别的语句划分成一个个的关键词,我们在搜索的时候会把自己信息进行分词,会把数据库中或索引库中的数据进行分词,然后进行匹配操作,默认的中文分词是将每个字看成一个词,所以我们需要安装中文分词器 IK 来解决
IK提供了两种分词算法, ik_smart
和 ik_max_word
ik_smart
会做最粗粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为中华人民共和国、人民大会堂。ik_max_word
会将文本做最细粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国、大会堂、大会、会堂等词语。
两种分词器使用的最佳实践是:索引时用ik_max_word,在搜索时用ik_smart。
即:索引时最大化的将文章内容分词,搜索时更精确的搜索到想要的结果。
安装IK分词器
还是去github上下载压缩包,并解压到 ES 的 **/plugins/**目录
然后我们重启ES服务器:
说明ES启动时加载了IK 分词器
测试两种分词:
发送符合Restful规范的URL来获取分词:
GET _analyze
{
"text": "中华人民共和国人民大会堂",
"analyzer": "ik_smart"
}
这样得到的分词是:
请求 ik_max_word
的分词:
GET _analyze
{
"text": "中华人民共和国人民大会堂",
"analyzer": "ik_max_word"
}
结果:
可以发现 ik_max_word
这种分词方式将所有能形成的词都进行分词了
IK分词器的分词如果不能满足我们的话,我们也可以自己扩展IK的分词:
看到 ik分词器的目录下的 config/IKAnalyzer.cfg.xml
文件
我们需要在 ext_dict
里写入我们配置的 dic
文件,
<entry key="ext_dict">custom.dic</entry>
custom.dic
:
然后重启ES服务器,我们会发现,可以分词出我们配置的分词了
ES的请求URL:
创建索引:
要对数据做操作,必须要有索引(也就是先有数据库)
kibana的环境下: PUT /shopping
: 创建一个名为 shopping
的索引:
做同样的操作是不允许的,也就是说连续第二次PUT时会报错已存在索引
查询索引信息 :
GET /shopping
取得索引的各种信息
删除索引:
DELETE /shopping
即可删除索引
document的创建(只能使用POST):
在MySQL中添加数据还需要数据表,但是现在的ES去掉了type这个数据表定义,所以是直接在索引里创建文档 document
当我们数据创建时会返回id ,这个id是随机生成的,所以对于同样的请求每次返回的结果是不同的,所以POST不是幂等性的,但是PUT必须是幂等性,也就是两次同样的请求结果也必须一样
使用 :
POST /shopping/_doc
{
"name": "onlineshops",
"cate": "小米手机"
}
我们还可以自己指定 id,那么就不会产生随机id了, POST /shopping/_doc/1001
里面的 result
就表示是否已创建
查询文档document:
使用 GET /shopping/_doc/ID
,这就是用主键查询文档
如果要查询所有数据,那么使用 GET /shopping/_doc/_search
来进行查询
修改文档数据:
在修改之前的数据是 :
"_source" : {
"name" : "onlineshops",
"cate" : "小米手机"
}
完全覆盖数据:
PUT /shopping/_doc/ID
{
"name" : "onlineshops",
"cate" : "华为手机"
}
看到 "result": "updated"
可以知道我们的修改提交成功了,再次使用 GET /shopping/_doc/LXs0s3kBya_CuchCNFyl
查询的时候,就会发现数据变成了 华为手机
注意:这里有一个 version
,你每提交一次 version
就会加一
局部更新:
POST /shopping/_update/LXs0s3kBya_CuchCNFyl
{
"doc": {
"name": "onlinesshops"
}
}
可以看到,局部更新里面我们需要指定 doc
,配置需要修改的值,那么就只会修改我们配置的值,其他的不会变
删除document数据:
使用DELETE /shopping/_doc/LXs0s3kBya_CuchCNFyl
即可,
查询
搜索查询才是ES最主要的内容
在请求体中增加查询
命令就是前面的查询全部数据GET /shopping/_search
然后加上匹配信息的配置
GET /shopping/_search
{
"query": {
"match": {
"FIELD": "TEXT"
}
}
}
这就是配置某个Field
的值能匹配TEXT
的值(匹配是包含,而不只是等于)
配置分页(与query
同级):
from
从命中的第几个开始显示,从 0开始size
分页的大小 ,from
和size
一起使用的时候,from就表示分页的第几页了,如 from=1,size=2,那么显示的就是所有查询里的第三四条
只需要特定的字段值,可以配置 _source
属性
GET /shopping/_search
{
"query": {
"match_all": {
}
},
"_source": ["name","cate"]
}
这里的 _source
配置的是两个字段,那么查询的时候就查询出这两个字段,抛弃其他的字段
按照某个字段进行排序:
配置 sort
属性就行了
"sort": [
{
"FIELD": {
"order": "desc"
}
}
]
查看所有的索引及其信息:
使用GET发送 http://127.0.0.1:9200/_cat/indices
设置某些条件查询
配置 query
里的 bool
即可
GET /shopping/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "onlineshops"
}
}
]
}
}
}
如图就是配置name要匹配 "onlineshops"这个值,由于 must是一个数组,所以可以配置多个条件
match
是全文检索匹配,会将关键词进行分词,索引内容也分词进行匹配,
我们可以使用 match_phrase
进行完全匹配,不会进行分词,就是对当前的词进行匹配
使用match和match_phrase分别进行匹配:
如图,同样关键词是 小华,match
能够查出所有内容, match_phrase
就查不出,因为 match_phrase
是直接使用 小华
两个字去匹配的,自然匹配不到
must是 与的意思
should 是 或
filter是 范围筛选
"filter": [
{
"range": {
"FIELD": {
"gte": 10,
"lte": 20
}
}
}
]
- gte 是 greater than ,也就是大于
- lte 是 lower than ,也就是小于
配置高亮:
GET /shopping/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"cate": "小华"
}
}
]
}
},
"highlight": {
"fields": {
"cate": {}
}
}
}
这就是将查询出来的 cate 字段值进行高亮显示:
进行聚合操作:
配置 aggs
属性
GET /shopping/_search
{
"aggs": {
"price_term": {
"terms": {
"field": "price"
}
}
},
"size": 0
}
这里的 price_term
可以是任意名字 , term
表示的是这是分组操作,根据 price
字段进行分组(price
字段必须是整数型的,如果是字符串会 报错), size为0表示我只需要聚合的信息,原内容不需要
如图,将我们的统计信息查询出来了
对数据进行约束
我们可以对索引里的数据进行约束,如类型约束等等
GET /shopping/_mapping
{
"properties": {
"name": {
"type": "text",
"index": true
},
"cate": {
"type": "keyword",
"index": false
}
}
}
配置约束要发送到 _mapping
,其中name
字段限制的是 类型为文本,可以作为索引,cate
字段限制的是类型是关键词(也就是不能被分词,默认作为完全匹配),不能被索引,不能被索引也就是不能查询,在match
中指定了 cate
会报错
搜索原理:
我们在搜索的时候可以发现,即使只输入一部分字,也可以查出内容,因为ES会将关键词进行分词,然后对索引中的内容也进行分词,使用倒排索引进行匹配
集群:
前面所使用的ES服务器是单机的。
但是单台ES服务器提供服务往往都有最大负载能力,超过这个阈值,服务器性能就会大大降低甚至不可用,所以在生产环境中,一般都是运行在指定服务器集群中。
集群Cluster:
一个集群就是由一个或多个服务器节点组织在一起,共同持有整个数据,并一起提供索引和搜索功能。一个ES集群有一个唯一的名字标识,这个名字默认是 elasticsearch
节点NODE:
一个节点就是其中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能。
windows集群配置:
先将data
和logs
文件夹删除,打开config/elasticsearch.yml
文件
配置这两个内容:
必须将集群内的配置文件的 cluster.name改成一致的,node.name必须不一致
将network.host
改成 localhost
-
启动端口号
http.port: 9201
-
配置监听端口号:
transport.tcp.port:9301
-
配置跨域配置:
http.cors.enabled: true
http.cors.allow-origin: "*"
-
配置当前节点的配置 :
node.master: true
node.data: true
然后将ES文件目录复制三份:
然后修改其中的 启动端口号、监听端口号和节点名字,其他的不需要修改
启动之后可以使用 http://127.0.0.1:9201/_cluster/health
来查询集群信息:
现在 number_of_nodes
的值为1,说明集群中只有一个节点
现在启动的第一个节点是 9201的,监听端口号是9301
那么启动第二个节点 端口号是9202,监听端口号是9302(记得先删除data和log文件夹)时候要去寻找第一个节点,要在配置文件 /conf/elasticsearch.yml
里额外配置寻找信息:
discovery.zen.ping.unicast.hosts: ["127.0.0.1:9301"]
discovery.zen.fd.ping_timeout: 1m
discovery.zen.fd.ping_retries: 5
再启动第三个(端口号是9203,监听端口号是9303)
启动第三个时可以寻找第二个节点的监听端口号来加入集群:
discovery.zen.ping.unicast.hosts: ["127.0.0.1:9301","127.0.0.1:9302"]
现在再使用http://127.0.0.1:9201/_cluster/health
查看集群信息时,可以发现集群节点变成了3个
到此,三个节点形成的ES集群就创建成功了
Linux配置ES:
先下载安装包:
https://repo.huaweicloud.com/elasticsearch/7.8.0/elasticsearch-7.8.0-linux-x86_64.tar.gz
上传到阿里云服务器上之后解压
tar -zxvf elasticsearch-7.8.0-linux-x86_64.tar.gz
mv elasticsearch-7.8.0/ es
因为安全问题,ElasticSearch不允许root用户直接运行,所以需要创建新用户:
useradd es
passwd es
- 密码输入 es
那么现在是使用 es-es
这个用户来操作ES
配置集群:
在 config/elasticsearch.yml
这个配置文件里面加入
cluster.name: elasticsearch
node.name: node-1
network.host: 0.0.0.0
http.port: 9200
cluster.initial_master_nodes: ["node-1"]
然后修改 /etc/security/limits.conf
,配置es这个用户的进程可以打开的文件数的限制
es soft nofile 65536
es hard nofile 65536
修改 /etc/security/limits.d/20-nproc.conf
,添加如下内容:
es soft nofile 65536
es hard nofile 65536
然后修改 /etc/sysctl.conf
vm.max_map_count = 655360
然后重新加载: sysctl -p
启动:
首先进入 elasticsearch
的目录,然后登录es 用户
-
chown -R es:es /envir/elasticsearch/es
把elasticsearch文件目录授权给es用户 -
su es
我这里的问题使用的是轻量级服务器,内存不够,完全启动不了ES,等啥时候有 2C2G的服务器再说吧
ES进阶:
索引 index
一个索引就是一个拥有几分相似特征的文档的集合。你可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。一个索引用一个名字标识(必须是小写字母)。当我们要对这个索引中的文档进行增删改查的时候都需要使用这个名字。在一个集群中,可以定义任意多个索引
能搜索的数据必须要索引,这样的好处是可以提高查询速度
一切设计都是为了提高搜索的性能
类型 Type
已被抛弃,可以不理解
索引可以直接关联数据,所以多一个类型是没有必要的
文档 document
文档就是 json格式的数据。在一个索引内可以存储任意多个文档
字段 field
相当于数据库内的字段、列
映射 mapping
mapping
是对处理数据的方式和规则方面做一些限制,如: 某个字段的数据类型、默认值、分析器、是否被索引等等。
分片:
一个索引可以存储超出单个节点硬件限制的大量数据。如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点可能都没有这样大的磁盘空间,或者单个节点处理搜索请求,响应太慢。所以ES提高了分片功能,将索引划分成多份的能力,每一份都称之为分片,当你创建一个索引的时候,你可以指定你想要的分片的数量,每个分片本身也是一个功能完善的并且独立的 索引
,这个 索引
可以放置到集群中的任何节点上
- 分片可以允许我们水平分割
- 可以在分片上进行分布式的、并行的操作,进而提高性能和吞吐量
分片就是ES里的最小单位
副本 replicas:
副本就是故障转移机制,ES允许创建分片的一份或多份拷贝,这些拷贝叫做复制分片(副本)
- 在节点、分片失败的时候,提高了高可用性,要注意 复制分片 从不与 原分片置于同一节点
- 扩展搜索量和吞吐量,因为搜索可以在所有的副本上并行允许
分配 allocation:
将分片分配给某个节点的过程,包括分配主分片或者副本。如果是副本,还包含从主分片复制数据的过程,这个过程是由master节点完成
架构:
ES的分布式集群:
单节点集群:
我们启动第一个9201节点(先清空data和log文件夹,要不然可能会自动寻找集群),在的集群内创建 users
索引,我们为其分配三个主分片和一个副本
PUT http://127.0.0.1:9201/users
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}
创建这个 users
索引之后,查看一下发现:
分片个数和副本个数都是我们设置的
现在我们有三个分片,但是当前只有一个节点,所以三个节点都被分配到了 node-1这个节点上
要查看ES的集群信息,我们可以使用 edge自带的扩展程序(其实不如 elasticsearch-head好用,前可以使用前面已经说过的浏览器安装 elasticsearch-head插件):
启动扩展程序之后,如下:
可以看到Cluster Health
里面的 status
为 yellow ,说明集群运行正常,但是分片没有全分配好。
但是 unassigned_shards
的值是3,说明有三个副本分片都是 unassigned
,它们都没有被分配到任何节点,在同一个节点上既保存原始数据又保存副本是没有意义的,因为一旦失去了那个节点,我们也将丢失该节点上的所有副本数据
故障转移
当集群中只有一个节点在运行,就意味着会有单点故障问题—没有冗余。
我们可以启动第二个节点来防止数据丢失。现在启动9202节点(记得先删除data和log)。
第二个节点加入的时候会进行副本的重新分配
如图,就是我们添加了第二个节点进入集群,在这个集群中我们可以发现 status
变成了 green
,说明集群运行是完全正常的。未分配的分片也变成了 0。
绿色 green表示所有的分片都在正常运行
水平扩容:
当我们启动了第三个节点,我们的集群会拥有三个节点 的集群,此时分片会继续重新分配
分配原则: 1.副本分片和原分片不可以在同一个节点, 2. 要尽量提高均衡,也就是如果有三个节点,不可能一个有三个副本分片,一个只有一个副本分片这种情况
启动第三个节点那么副本分片就会变成:
可以发现node-1和node-2和node-3都存在了两个分片。
原先的分配是 node-1和node-2各有三个分片,现在每个节点都拥有两个分片,而不是之前的三个,这表示每个节点的硬件资源将被更少的分片共享,每个分片的性能将会提高
分片是一个功能完整的搜索引擎,它拥有使用一个节点上的所有资源的能力。我们这个拥有六个分片(三个主分片和三个副本分片)的users索引可以最大扩容到6个节点,每个节点上存在一个分片,并且每个分片拥有节点的全部资源
主分片的数量在索引创建时就已经确定了,这个数目定义了这个索引能够存储的最大数据量。但是读操作(搜索和返回数据)可以同时被主分片或副本分片所处理,所以当你有越多的副本分片时,也将拥有越高的吞吐量
如果需要扩容超过6个分片怎么做?
首先,现在是两个节点,三个不同的分片,如果我们要变成九个分片,那么每个节点至少放4个(4大于3),那么说明一个节点上是必定会有一个重复的分片,这样就和分配原则相违背,会报错。那么必须增加节点的数量
主分片的数量是不能修改的,但是我们可以通过修改副本分片的数量来进行扩容:
PUT http://127.0.0.1:9201/users/_settings
{
"settings": {
"number_of_replicas": 2
}
}
应对故障:
当我们将 主节点 node_1下线,那么现在副本分片就会变成主分片,然后非主节点会选举成主节点,但是因为node_1的分片当前无法使用了,所以 集群状态会从 green–>yellow。当node_1节点重新连接时(需要配置 discovery.seed_hosts
为其他在集群中的监听IP地址 来加入集群),node_1就会变成普通节点
现在的集群如图所示:
集群是正常的,但是当我们将master节点node-3关闭。
此时,master节点就变成了node-1,健康值为变成了yellow(因为有三个分片无法分配)
路由计算:
我们放入的数据可能在三个分片的某一个,对于唯一的数据这个分片必须要固定,要不然我们查找的分片和存储的分片可能不一致。
路由计算是通过hash计算的,也就是通过hash算法来计算出具体放在索引的哪个分片
分片控制:用户可以访问任何一个节点获取数据,那么会通过轮询来进行负载均衡
数据写的流程:
- 用户连接时可能连接到node-1002这个节点,但是hash计算出来应该将
zhangsan
这个数据放到p0这个分片上,所以会转移到node-1001节点进行写 - 写主分片写完之后,node -1节点会发送请求给副本分片所在的node-1,将R0这个副本分片更新
- 副本分片R0更新完之后,主分片发送更新完成的响应
- 客户端获取响应
写数据时,当前节点要判断副本分片要达到足够的数量才会进行写数据(数量可以修改),如果没达到指定数量,那么就会等待,默认一分钟,然后作为超时处理
持久化如下:
-
ES MEMORY 是指内存
-
ES 持久化是将index先变为 段(segment)再进行持久化的
-
ES是先进行
index->segment
的操作再进行index->translog
的,因为index->segment
很有可能会出错,如果先写log文件那么可能会产生很多无效记录 -
OS CACHE
就是操作系统的文件缓冲区
数据读流程
客户端第一次连接的节点是随机的。连接到之后会找到你要找的所有主分片和副本分片,然后根据轮询算法访问不同的节点
- 客户端发送查询请求到协调节点(第一次连接的是哪个节点,那个节点就作为协调节点)
- 协调节点计算数据所在分片及其所有副本的位置
- 为了能够负载均衡,可以轮询所有节点
- 将节点转发给具体的节点
- 节点返回查询结果,将结果反馈给客户端
数据更新的流程:
- 客户端发送请求
- 协调节点将请求发送到主分片所在的节点
- 主分片修改
- 主分片修改完之后通知副本分片修改
- 主分片及所有副本分片修改完之后返回响应
倒排索引
倒排索引是全文检索的主要方式
通过分词将 内容分成一个个的单词,将每个单词拿出来做key,value则是这个单词所在的文章ID
然后根据分词的不同又有 max_word
和 smart
,这在前面的ik分词器中有说
- 词条:就是分出的各个单词
- 词典: 字典,词条的集合
- 倒排表: 也就是 单词-文章ID 的表
文档分析:
- 将一块文本分成适合于倒排索引的独立的词条
- 将这些词条统一化为标准格式以提高他们的
可搜索性
分析器用于执行上面的工作,分析器实际上是将三个功能封装到了一个包里:
-
字符过滤器:
首先,字符串按顺序通过每个字符过滤器,用处是在分词前整理字符串,一个字符过滤器可以用来去掉HTML,或者将 & 转换成 and
-
分词器:
字符串被 分词器分为 单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条
-
Token 过滤器:
词条按顺序通过每个 token过滤器。这个过程可能会改变词条(例如,小写化单词),删除词条(如 a ,and ,the 这种无用词),或者增加 词条、、
ES自带一些分析器
测试ES的分析器:
发送如下URL:
GET http://127.0.0.1:9200/_analyze
{
"analyzer": "standard",
"text": "text to analyze"
}
使用 standard的分析器进行文本分析
结果如下:
{
"tokens": [
{
"token": "text",
"start_offset": 0,
"end_offset": 4,
"type": "<ALPHANUM>",
"position": 0
},
{
"token": "to",
"start_offset": 5,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "analyze",
"start_offset": 8,
"end_offset": 15,
"type": "<ALPHANUM>",
"position": 2
}
]
}
token
是指词条start_offset
是指在文本中的开始偏移量
ES的默认的分析器是做不了 中文的分词的,所以我们需要使用 IK分词器,前面已经介绍了
ES的并发控制
es中使用乐观并发控制,它假定冲突是不可能发生的,并且不会阻塞正在尝试的操作。然而,如果源数据在读写中被修改,更新将失败。
ES是分布式的,当文档创建、更新和删除时,新版本的文档必须复制到集群中的其他节点,ES也是异步和并发的,这意味着这些复制请求被并行发送,并且到达目的地时也许顺序是乱的。那么ES需要确保文档的旧版本不会覆盖新的版本。
当我们之前讨论index时 ,发送GET和DELETE请求时,我们指出每个文档都有一个 _version
号,当文档被修改时版本号递增。ES使用这个 version
来确保变更以正确顺序得到执行。如果旧版本的文档在新版本之后到达,它可以被简单的忽略
我们先创建一个索引:
POST http://127.0.0.1:9200/users/_doc/1001
{
"name": "hello111",
"content": "concurrency111"
}
结果如下:
{
"_index": "users",
"_type": "_doc",
"_id": "1001",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}
这里面的 _seq_no
和_primary_term
就是用于并发控制的属性
我们进行修改:
POST http://127.0.0.1:9200/users/_doc/1001?if_seq_no=1&if_primary_term=1
{
"name": "hello222",
"content": "concurrency222"
}
可以发现属性值被修改成功, _seq_no
这个属性值也自增了 1
但是当我们再一次发送同样的请求时,就会报错,因为当前需要的 seq_no
值变成了 2,并不等于1,所以把 if_seq_no=1
改成 if_seq_no=2
即可修改
ES集成Spring Data
Spring Data 是一个由于简化关系数据库、非关系型数据库、索引库的访问的框架。主要目标是使得对数据的访问变得方便快捷
spring Data可以极大地简化JPA(Java Persistence API)的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作,除了CRUD,还包括如分页、排序等一些常用的功能
Spring Data集成ES则是使用springdata-elasticsearch
创建spring boot项目:
版本对应:版本查看
因为我们使用的是7.8.0的ES,那么使用2.3.*的spring boot即可
我这使用2.3.6的spring boot:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.6.RELEASE</version>
</parent>
<dependency> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
创建实体类(创建这个实体类之后,在启动spring boot时会自动去ES创建indexName为 shopping
的索引):
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Document(indexName = "shopping",shards = 3,replicas = 1)
public class Product {
@Id
private Long id;
private String title;
@Field(type = FieldType.Keyword)//keyword表示不能被分词
private String category;
@Field(type = FieldType.Double)//表示为浮点类型,非字符串类型都必须要使用这个
private Double price;
@Field(type = FieldType.Keyword,index = false) //不能被索引
private String images;
}
创建Dao对象:
@Repository
public interface ProductDao extends ElasticsearchRepository<Product,Long> {
}
创建配置类:
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
RestHighLevelClient client=new RestHighLevelClient(RestClient.builder(new HttpHost("localhost",9200)));
return client;
}
}
创建索引:
索引是在我们初始化模板时自动进行创建的,如下:
//初始化模板时会自动创建索引
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
所以我们在启动测试类时要在测试类里写上,如果启动spring boot,那么可以在spring boot启动类里写上
操作文档:
创建文档:
//初始化模板时会自动创建索引
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@Autowired
ProductDao productDao;
@Test
void create_document(){
System.out.println("插入document");
Product product=new Product();
product.setId(1001L);
product.setTitle("华为");
product.setCategory("手机");
product.setImages("/111.jpg");
product.setPrice(500.0);
productDao.save(product);
}
要记得在每个类中都必须写模板类,使用 ElasticsearchRepository的save()
方法插入文档document
批量插入:
List<Product> list=new ArrayList<>();
productDao.saveAll(list);
修改文档:
也是使用save() 方法,ID相同的时候就作为修改
查询文档:
@Test
void find_document(){
Product product = productDao.findById(1001L).get();
System.out.println(product);
}
@Test
void find_all_document(){
Iterator<Product> iterator = productDao.findAll().iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
删除文档:
productDao.deleteById(1001L);
分页查询:
PageRequest pageRequest=PageRequest.of(0,5, Sort.by(Sort.Direction.DESC,"id"));
productDao.findAll(pageRequest);
Sort.by(Sort.Direction.DESC,"id")
说明是按照id进行降序排序
0,5
说明当前是第一页的前五个文档
文档搜索:
条件搜索:
使用 TermQueryBuilder
来创建搜索条件
@Test
void term_Query(){
Product p=new Product();
p.setTitle("华为");
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("price", "500.0");
Iterable<Product> products = productDao.search(termQueryBuilder, PageRequest.of(0,2));
for (Product product : products) {
System.out.println(product);
}
}
分片策略:
分片和副本的设计为ES提供了分布式和故障转移的特性,但并不意味着分片和副本是可以无限分配的,而且索引的分配完成分配后我们是不能重新修改分分片数的
一个分片也是有代价的:
- 一个分片的底层即为一个Lucene索引,会消耗一i党的文件句柄,内存以及CPU运转。
- 每一个搜索请求都需要命中索引的每一个分片,如果每一个分片都处于不同的节点还好,如果存在一个节点,那么会造成很大压力。
- 计算相关度是基于分片的,如果有许多分片,每一个都只有很少的数据会导致很低的相关度
所以应该按着下面的原则进行分配分片:
- 分片数不超过节点数的3倍
- 节点数 <=主分片数*(副本数+1)
断线重连机制:
当一个节点断线了,那么集群会等待一分钟,超过一分钟就会重新分配分片(会造成极大开销),
我们可以推迟等待时间:
PUT /_all/_settings
{
"settings": {
"index.unassigned.node_left.delayed_timeout": "5m"
}
路由选择:
当我们查询文档的时候,ES可以带路由查询,也可以不带路由查询:
不带 routing查询:
- 分发:请求到达协调节点后,协调节点将查询请求分发到每个分片上。
- 聚合: 协调节点搜集到每个分片上查询结果,在将查询的结果进行排序,之后给用户返回结果。
使用routing查询:
直接根据 routing 信息定位到某个分配查询,如果 routing 设置为 id 的话,可以直接查询出数据来, 效率提升很多
减少副本数量:
ES为了保证集群的可用性,提供了 Replicas支持,但是每个副本也会执行分析、索引、及可能的合并过程。所以Replicas的数量会严重影响写索引的效率
当写索引时,需要把数据同步到副本节点,所以副本节点越多,写索引的效率就越慢。
当我们需要大批量写入操作时,可以先禁止Replica复制,
设置index.number_of_replicas: 0
关闭副本,写入完成之后,Replica修改回正常的状态
productDao.findAll(pageRequest);
`Sort.by(Sort.Direction.DESC,"id")`说明是按照id进行降序排序
`0,5`说明当前是第一页的前五个文档
### 文档搜索:
##### 条件搜索:
使用 `TermQueryBuilder`来创建搜索条件
```java
@Test
void term_Query(){
Product p=new Product();
p.setTitle("华为");
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("price", "500.0");
Iterable<Product> products = productDao.search(termQueryBuilder, PageRequest.of(0,2));
for (Product product : products) {
System.out.println(product);
}
}
分片策略:
分片和副本的设计为ES提供了分布式和故障转移的特性,但并不意味着分片和副本是可以无限分配的,而且索引的分配完成分配后我们是不能重新修改分分片数的
一个分片也是有代价的:
- 一个分片的底层即为一个Lucene索引,会消耗一i党的文件句柄,内存以及CPU运转。
- 每一个搜索请求都需要命中索引的每一个分片,如果每一个分片都处于不同的节点还好,如果存在一个节点,那么会造成很大压力。
- 计算相关度是基于分片的,如果有许多分片,每一个都只有很少的数据会导致很低的相关度
所以应该按着下面的原则进行分配分片:
- 分片数不超过节点数的3倍
- 节点数 <=主分片数*(副本数+1)
断线重连机制:
当一个节点断线了,那么集群会等待一分钟,超过一分钟就会重新分配分片(会造成极大开销),
我们可以推迟等待时间:
PUT /_all/_settings
{
"settings": {
"index.unassigned.node_left.delayed_timeout": "5m"
}
路由选择:
当我们查询文档的时候,ES可以带路由查询,也可以不带路由查询:
不带 routing查询:
- 分发:请求到达协调节点后,协调节点将查询请求分发到每个分片上。
- 聚合: 协调节点搜集到每个分片上查询结果,在将查询的结果进行排序,之后给用户返回结果。
使用routing查询:
直接根据 routing 信息定位到某个分配查询,如果 routing 设置为 id 的话,可以直接查询出数据来, 效率提升很多
减少副本数量:
ES为了保证集群的可用性,提供了 Replicas支持,但是每个副本也会执行分析、索引、及可能的合并过程。所以Replicas的数量会严重影响写索引的效率
当写索引时,需要把数据同步到副本节点,所以副本节点越多,写索引的效率就越慢。
当我们需要大批量写入操作时,可以先禁止Replica复制,
设置index.number_of_replicas: 0
关闭副本,写入完成之后,Replica修改回正常的状态