前言
曾用Elasticsearch完成全文搜索的需求,当时只为业务使用未做深入研究,这两天有时间便翻阅了官方文档和相关的博文,对es也有了更多的理解,将自己的所学和理解做下总结
一、es的专有名词
cluster 集群
cluster就是一个及以上个node的集合,它们一起存储你的所有数据,提供跨节点的搜索和索引能力,集群通过一个唯一的名字来标识. 默认情况下,当你在同一个网络环境启动一个及以上node时,它们会自动加入并形成一个名为elasticsearch的集群.
对于外部调用,es暴露了两个端口
- 9200 供rest api使用,官方推荐
- 9300 es节点内部使用, 官方不推荐外部使用,目前java client也用了这个端口,以后会转移到9200
node 节点
一个node就是一个es实例
,每个节点都可以
- 存储数据
- 参与索引(添加)数据
- 搜索
index 索引
等同于关系型数据库中的表,用来存储Document
document 文档
等同于关系型数据库表中的行,文档由字段组成,创建index时可以指定对字段的分析方式(analyzer,search_analyzer等,类似于关系型数据库中给字段添加索引),如果一个字段被指定不分析("index" : false),那么不能使用
它来搜索相关操作
shard 分片
es中的shard用来解决节点的容量上限问题,通过将index分为多个分片(默认为一个也就是不分片),一个或多个node共同存储该index的所有数据实现水平拓展
(类似于关系型数据库中的分表)它们共同持有该索引的所有数据,默认通过hash(文档id)决定数据的归属
replicas 副本
replicas主要为了以下两个目的
- 由于数据只有一份,如果一个node挂了,那存在上面的数据就都丢了,有了replicas,只要不是存储这条数据的node全挂了,数据就不会丢
- 通过在所有replicas上并行搜索提高搜索性能.由于replicas上的数据是近实时的(near realtime),因此所有replicas都能提供搜索功能,通过设置合理的replicas数量可以极高的提高搜索吞吐量
eg,如果指定了replicas=2,那么对于一条数据它共有三份,一份称为primary shard,另外两份称为 replicas shard. 这三个统称为replicas group(副本组)
二、倒叙索引的魅力
es的搜索功能是基于lucene,而lucene搜索的基本原理就是倒叙索引
什么是倒叙索引
借用在网上找到的图片,个人认为是最好理解的
文档中的句子被划分为一个个term(term 用来表示一个单词或词语,
取决于使用的分词方式
),倒叙索引
中存储着term,term的出现频率(tf,term frequency)和出现位置(倒叙索引中的单词是按顺序排列的,这张图没有体现出来
),请注意这里的文档内容是document中的一个字段,也就是说每个被索引了的字段都有自己的倒叙索引
一次简单的搜索流程
假设我们搜索谷歌地图之父
,搜索流程会是这样
- 分词,分词插件将句子分为3个term
谷歌
,地图
,之父
- 将这3个term拿到倒叙索引中去查找(会很高效,比如二分查找),如果匹配到了就拿对应的文档id,获得文档内容
但是,如何确定结果顺序?
这里要引入_score的概念,对于term的匹配,lucene会对其打分,得分越高,排名越靠前.这里要介绍几个相关的概念
- TF(term frequency),词频,term在当前document中出现的频率,一个term在当前document中出现5次要比出现1次更相关,打分也会更高
- IDF(inverse doucment frequency),逆向文档频率,term在所有document中出现的频率,这个频率越高,该term对应的分值越低
- 字段长度归一值,简单来说就是字段越短,字段的权重越高, 比如 term
我
在匹配我123
和我123456
时,我123
的得分会更高.
搜索就这么简单吗?
当然不是,es的搜索时基于lucene的,但是lucene是单机
的,es是分布式的,这就意味着es必定会更复杂,举例来说
- es是有shard这个概念的,那么搜索时要到一个index上的所有shard上去查,这样相当于将搜索膨胀了
shard的个数
倍,es是如何做优化的? - es还有replicas这个概念,那么如何充分利用replicas来实现并行搜索,提高搜索吞吐量的?
这里说下自己对单机和分布式的理解,打个比方,做饭
- 给2个人做饭,一个人(单机)就够了,压力很小,随便购买点食材就可以了,偶尔一次没做好也没问题
- 给5个人做饭,一个人(单机)也能行,有点压力,购买食材要仔细考虑了,偶尔一次没做好可能会被抱怨
- 给50个人做饭,一个人(单机)已经不够了,要多人(分布式)合作了,有人负责采购,有人负责做,可以身兼多职,多人合作完成,一次没做好也会影响50个人一天的生活,已经不可接受了
- 给5000个人做饭,只能是多人(分布式)了,到了这个程度,不可能再身兼多职了,每个人都分工明确,所有事情都会预先安排好,买什么食材,什么时候买,饭点是什么时候都需要很精确.这时候如果发生了很小的问题比如饭点晚了10分钟,很可能整个集体就垮掉了.
分布式就是这样,会将很小的问题无限放大,原本微不足道的问题放到分布式上会引起整个系统的崩溃
三、实际操作一下
使用docker-compose创建一个双节点es集群
docker-compose.yml
version: '3'
services:
elasticsearch:
image: elasticsearch:6.5.0
container_name: elasticsearch
environment:
- cluster.name=docker-cluster
ports:
- 9200:9200
networks:
- esnet
elasticsearch2:
image: elasticsearch:6.5.0
container_name: elasticsearch2
environment:
- cluster.name=docker-cluster
- "discovery.zen.ping.unicast.hosts=elasticsearch"
networks:
- esnet
networks:
esnet:
在docker-compose.yml下执行docker-compose up
就完成集群的启动了.docker-compose的使用可以参考这篇文章
下面看下集群的信息
λ curl http://127.0.0.1:9200/_cluster/health?pretty=true
{
"cluster_name" : "docker-cluster",
"status" : "green",
"timed_out" : false,
"number_of_nodes" : 2,
"number_of_data_nodes" : 2,
"active_primary_shards" : 0,
"active_shards" : 0,
"relocating_shards" : 0,
"initializing_shards" : 0,
"unassigned_shards" : 0,
"delayed_unassigned_shards" : 0,
"number_of_pending_tasks" : 0,
"number_of_in_flight_fetch" : 0,
"task_max_waiting_in_queue_millis" : 0,
"active_shards_percent_as_number" : 100.0
}
- 可以看到集群名称就是我们再docker-compose.yml中设置的
docker-cluster
- status为
green
,es中集群有三个状态,在shard级别状态来说
- green 正常状态 primary shard和replicas shard分配正常,集群运行正常
- yellow 异常状态 primary shard分配正常,部分replicas shard未分配,集群仍然可以运行
- red 错误状态 某个分片未被分配,与该shard相关操作无法执行,系统运行异常
index级别状态由最坏的shard级别状态控制,cluster级别状态又由最坏的index级别状态控制.可以说是坏事传千里
由于我们还没有创建索引,其他参数先不介绍
创建索引
以图书为例,我们创建一个索引,(对于需要创建多个的类似的索引,可以使用index template),包含书名,作者两个字段,如果要使用中文需要添加分词插件,这里为了简单只使用英文.使用postman来请求
这里我们设置book这个index分为
3
个shard,每个shard有1
份replicas,为什么要设置1
个replicas呢?我们只有2个节点,1个replicas shard+1个primary shard正好保证对于一条数据,每个节点上都有一份
,如果设置了"number_of_replicas": 2
那么就会有1个replicas shard没办法分配,出现unassigned shard问题,参见es未分配分片导致集群状态报黄
再来查看集群信息
{
"cluster_name" : "docker-cluster",
"status" : "green",
"timed_out" : false,
"number_of_nodes" : 2,
"number_of_data_nodes" : 2,
"active_primary_shards" : 3,
"active_shards" : 6,
"relocating_shards" : 0,
"initializing_shards" : 0,
"unassigned_shards" : 0,
"delayed_unassigned_shards" : 0,
"number_of_pending_tasks" : 0,
"number_of_in_flight_fetch" : 0,
"task_max_waiting_in_queue_millis" : 0,
"active_shards_percent_as_number" : 100.0
}
可以看到
- 我们现在有
3
个primary shard,总shard数为6=3 priamry shard + 3 replicas shard
, -
2
个data_node(es中节点是带有角色的,后面会讲到)
shard分布
上图格式为
index shard下标 shard状态 shard中的doc数量 数据量 节点ip地址 节点名称
(查询返回字段含义时使用GET /_cat/shards?help,其他命令类似)如下图,
请注意这里primary shard都分配在master node,而replicas shard都分布在普通节点只是巧合,shard的分布是随机的
插入数据
如果插入数据时不指定id,es会生成一个uuid作为其id
更新数据
-
通过id更新字段,最简单的更新方式了
- 通过id使用动态脚本更新字段,es支持多种脚本语言比如painless,groovy,但是要注意脚本的消耗很高,要慎用,对于会多次使用的脚本使用
带参数的脚本
而非动态脚本
,后者有最大数量限制,超过后es会拒绝执行
通过将变量作为参数 params 传递,我们可以查询时动态改变脚本无须重新编译
3 带参数的脚本
上面三个示范是按顺序执行的,可以看到每执行一个
_version是会自增的
(用户也可以指定),和其他框架相同,es的_version也是用来处理并发情况的,这点后面会介绍._shard
中展示了各shard的执行结果,对于这条数据 我们共有2=1 primary shard + 1replicas shard
份,都执行成功了_seq_no
和_version类似,严格递增的序列号,保证后写入doc的_seq_no大于先写入doc的_seq_no_primary_term
每当priamry shard重新分配时,这个字段就会自增1
这些字段的详细信息请参考Elasticsearch内核解析 - 数据模型篇
删除数据
查询
查询是es使用的重点,也是难点.es提供了多种查询方式,,上面的删/改也都可以利用查询,例如 update by qery
这些属于对api的应用,参见es官方文档
四、总结
本文介绍了es的基本概念,介绍了倒叙索引的概念,构建了一个es集群并执行了基本的crud操作,希望读者看完能够对es有基本的了解.
以下为参考的文章
- https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
- https://zhuanlan.zhihu.com/p/34852646
- https://mp.weixin.qq.com/s?__biz=MzI1NDY0MTkzNQ==&mid=2247484687&idx=1&sn=6e341e41a2b9fafd5a016394153caaf9&chksm=e9c3576fdeb4de7991f8b870d16c3648f67f6c93cdd0358b84d3aa00ab4c33ce0d711ff86edb&scene=0#rd
- https://yq.aliyun.com/articles/573637?spm=a2c4e.11155435.0.0.4b46f4beYNMXkP
- https://blog.csdn.net/u010454030/article/details/79794788
- https://zhuanlan.zhihu.com/p/33671444