一、Elasticsearch简介
Elasticsearch是使用Java编写的一种开源搜索引擎,它在内部使用Luence做索引与搜索,通过对Lucene的封装,提供了一套简单一致的RESTful API。Elasticsearch也是一种分布式的搜索引擎架构,可以很简单地扩展到上百个服务节点,并支持PB级别的数据查询,使系统具备高可用和高并发性。
二、Elasticsearch的核心概念如下
Cluster:集群,由一个或多个Elasticsearch节点组成。
Node:节点,组成Elasticsearch集群的服务单元,同一个集群内节点的名字不能重复。通常在一个节点上分配一个或者多个分片。
Shards:分片,当索引上的数据量太大的时候,我们通常会将一个索引上的数据进行水平拆分,拆分出来的每个数据库叫作一个分片。在一个多分片的索引中写入数据时,通过路由来确定具体写入那一个分片中,所以在创建索引时需要指定分片的数量,并且分片的数量一旦确定就不能更改。分片后的索引带来了规模上(数据水平切分)和性能上(并行执行)的提升。每个分片都是Luence中的一个索引文件,每个分片必须有一个主分片和零到多个副本分片。
Replicas:备份也叫作副本,是指对主分片的备份。主分片和备份分片都可以对外提供查询服务,写操作时先在主分片上完成,然后分发到备份上。当主分片不可用时,会在备份的分片中选举出一个作为主分片,所以备份不仅可以提升系统的高可用性能,还可以提升搜索时的并发性能。但是若副本太多的话,在写操作时会增加数据同步的负担。
Index:索引,由一个和多个分片组成,通过索引的名字在集群内进行唯一标识。
Type:类别,指索引内部的逻辑分区,通过Type的名字在索引内进行唯一标识。在查询时如果没有该值,则表示在整个索引中查询。
Document:文档,索引中的每一条数据叫作一个文档,类似于关系型数据库中的一条数据通过_id在Type内进行唯一标识。
Settings:对集群中索引的定义,比如一个索引默认的分片数、副本数等信息。
Mapping:类似于关系型数据库中的表结构信息,用于定义索引中字段(Field)的存储类型、分词方式、是否存储等信息。Elasticsearch中的mapping是可以动态识别的。如果没有特殊需求,则不需要手动创建mapping,因为Elasticsearch会自动根据数据格式识别它的类型,但是当需要对某些字段添加特殊属性(比如:定义使用其他分词器、是否分词、是否存储等)时,就需要手动设置mapping了。一个索引的mapping一旦创建,若已经存储了数据,就不可修改了。
Analyzer:字段的分词方式的定义。一个analyzer通常由一个tokenizer、零到多个filter组成。比如默认的标准Analyzer包含一个标准的tokenizer和三个filter:Standard Token Filter、Lower Case Token Filter、Stop Token Filter。
Elasticsearch的节点的分类如下:
主节点(Master Node):一个节点启动后,就会使用Zen Discovery机制去寻找集群中的其他节点,并与之建立连接。集群中会从候选主节点中选举出一个主节点,主节点负责创建索引、删除索引、分配分片、追踪集群中的节点状态等工作。Elasticsearch中的主节点的工作量相对较轻,用户的请求可以发往任何一个节点,由该节点负责分发和返回结果,而不需要经过主节点转发。
正常情况下,集群中的所有节点,应该对主节点的选择是一致的,即一个集群中只有一个选举出来的主节点。然而,在某些情况下,比如网络通信出现问题、主节点因为负载过大停止响应等等,就会导致重新选举主节点,此时可能会出现集群中有多个主节点的现象,即节点对集群状态的认知不一致,称之为脑裂现象。为了尽量避免此种情况的出现,可以通过discovery.zen.minimum_master_nodes来设置最少可工作的候选主节点个数,建议设置为(候选主节点数 / 2) + 1, 比如,当有三个候选主节点时,该配置项的值为(3/2)+1=2,也就是保证集群中有半数以上的候选主节点。
候选主节点的设置方法是设置node.mater为true,默认情况下,node.mater和node.data的值都为true,即该节点既可以做候选主节点也可以做数据节点。由于数据节点承载了数据的操作,负载通常都很高,所以随着集群的扩大,建议将二者分离,设置专用的候选主节点。当我们设置node.data为false,就将节点设置为专用的候选主节点了。
node.master = true
node.data = false
数据节点(Data Node):数据节点负责数据的存储和相关具体操作,比如CRUD、搜索、聚合。所以,数据节点对机器配置要求比较高,首先需要有足够的磁盘空间来存储数据,其次数据操作对系统CPU、Memory和IO的性能消耗都很大。通常随着集群的扩大,需要增加更多的数据节点来提高可用性。
前面提到默认情况下节点既可以做候选主节点也可以做数据节点,但是数据节点的负载较重,所以需要考虑将二者分离开,设置专用的数据节点,避免因数据节点负载重导致主节点不响应。
node.master = false
node.data = true
客户端节点(Client Node):就按照官方的介绍,客户端节点就是既不做候选主节点也不做数据节点的节点,只负责请求的分发、汇总等等,也就是下面要说到的协调节点的角色。这样的工作,其实任何一个节点都可以完成,单独增加这样的节点更多是为了负载均衡。
node.master=false
node.data=false
部落节点(Tribe Node):部落节点可以跨越多个集群,它可以接收每个集群的状态,然后合并成一个全局集群的状态,它可以读写所有集群节点上的数据,在配置文件中通过如下设置使节点成为部落节点:
tribe:
one:
cluster.name: cluster_one
two:
cluster.name: cluster_two
因为Tribe Node要在Elasticsearch 7.0以后移除,所以不建议使用。
协调节点(Coordinating Node):协调节点,是一种角色,而不是真实的Elasticsearch的节点,我们没有办法通过配置项来配置哪个节点为协调节点。集群中的任何节点都可以充当协调节点的角色。当一个节点A收到用户的查询请求后,会把查询语句分发到其他的节点,然后合并各个节点返回的查询结果,最好返回一个完整的数据集给用户。在这个过程中,节点A扮演的就是协调节点的角色。由此可见,协调节点会对CPU、Memory和I/O要求比较高。
集群的状态有Green、Yellow和Red三种,如下所述:
Green:绿色,健康。所有的主分片和副本分片都可正常工作,集群100%健康。
Yellow:黄色,预警。所有的主分片都可以正常工作,但至少有一个副本分片是不能正常工作的。此时集群可以正常工作,但是集群的高可用性在某种程度上被弱化。
Red:红色,集群不可正常使用。集群中至少有一个分片的主分片及它的全部副本分片都不可正常工作。这时虽然集群的查询操作还可以进行,但是也只能返回部分数据(其他正常分片的数据可以返回),而分配到这个分片上的写入请求将会报错,最终会导致数据的丢失。
建议:
在一个生产集群中我们可以对这些节点的职责进行划分,建议集群中设置3台以上的节点作为master节点,这些节点只负责成为主节点,维护整个集群的状态。再根据数据量设置一批data节点,这些节点只负责存储数据,后期提供建立索引和查询索引的服务,这样的话如果用户请求比较频繁,这些节点的压力也会比较大,所以在集群中建议再设置一批client节点(node.master: false node.data: false),这些节点只负责处理用户请求,实现请求转发,负载均衡等功能。
三、一致性(Consistency)
Elasticsearch集群保证写一致性的方式是在写入前先检查有多少个分片可供写入,如果达到写入条件,则进行写操作,否则,Elasticsearch会等待更多的分片出现,默认为一分钟。
有如下三种设置来判断是否允许写操作:
One:只要主分片可用,就可以进行写操作。
All:只有当主分片和所有副本都可用时,才允许写操作。
Quorum(k-wu-wo/reng,法定人数):是Elasticsearch的默认选项。当有大部分的分片可用时才允许写操作。
其中,对“大部分”的计算公式为int((primary+number_of_replicas)/2)+1。
Elasticsearch集群保证读写一致性的方式是,为了保证搜索请求的返回结果是最新版本的文档,备份可以被设置为sync(默认值),写操作在主分片和备份分片同时完成后才会返回写请求的结果。这样,无论搜索请求至哪个分片都会返回最新的文档。但是如果我们的应用对写要求很高,就可以通过设置replication=async来提升写的效率,如果设置replication=async,则只要主分片的写完成,就会返回写成功。
四、事务日志
Elasticsearch 增加了一个 translog ,或者叫事务日志,在每一次对 Elasticsearch 进行操作时均进行了日志记录。通过 translog ,整个流程看起来是下面这样:
一个文档被索引之后,就会被添加到内存缓冲区,并且 追加到了 translog ,正如 图 21 “新的文档被添加到内存缓冲区并且被追加到了事务日志” 描述的一样。
图 21. 新的文档被添加到内存缓冲区并且被追加到了事务日志
刷新(refresh)使分片处于 图 22 “刷新(refresh)完成后, 缓存被清空但是事务日志不会” 描述的状态,分片每秒被刷新(refresh)一次:
这些在内存缓冲区的文档被写入到一个新的段中,且没有进行 fsync 操作。
这个段被打开,使其可被搜索。
内存缓冲区被清空。
图 22. 刷新(refresh)完成后, 缓存被清空但是事务日志不会
这个进程继续工作,更多的文档被添加到内存缓冲区和追加到事务日志(见 图 23 “事务日志不断积累文档” )。
图 23. 事务日志不断积累文档
每隔一段时间–例如 translog 变得越来越大–索引被刷新(flush);一个新的 translog 被创建,并且一个全量提交被执行(见 图 24 “在刷新(flush)之后,段被全量提交,并且事务日志被清空”):
1、所有在内存缓冲区的文档都被写入一个新的段。
2、缓冲区被清空。
3、一个提交点被写入硬盘。
4、文件系统缓存通过 fsync 被刷新(flush)。
5、老的 translog 被删除。
translog 提供所有还没有被刷到磁盘的操作的一个持久化纪录。当 Elasticsearch 启动的时候, 它会从磁盘中使用最后一个提交点去恢复已知的段,并且会重放 translog 中所有在最后一次提交后发生的变更操作。
translog 也被用来提供实时 CRUD 。当你试着通过ID查询、更新、删除一个文档,它会在尝试从相应的段中检索之前, 首先检查 translog 任何最近的变更。这意味着它总是能够实时地获取到文档的最新版本。
图 24. 在刷新(flush)之后,段被全量提交,并且事务日志被清空
分词器
什么是分词器
因为Elasticsearch中默认的标准分词器分词器对中文分词不是很友好,会将中文词语拆分成一个一个中文的汉子。因此引入中文分词器-es-ik插件 。
standard分词器:
http://192.168.0.110:9200/_analyze
参数:
{
“analyzer”:“standard”,
“text”:“小明”
}
结果为:
{
“tokens”:[
{
“token”:“小”,
“start_offset”:0,
“end_offset”:1,
“type”:"",
“position”:0
},
{
“token”:“明”,
“start_offset”:1,
“end_offset”:2,
“type”:"",
“position”:1
}
]
}
由上面的结果可以看出来,这个分词器 “analyzer”:“standard” 会把结果都拆分成一个个的中文汉字,这肯定是不对的,所以我们需要使用 ik 分词器。
ik 官方网站下载 https://github.com/medcl/elasticsearch-analysis-ik/releases
注意: es-ik分词插件版本一定要和es安装的版本对应
第一步:下载es的IK插件 ,可重命名文件名称。
第二步: 上传到/usr/local/elasticsearch-6.4.3/plugins
第三步: 重启elasticsearch即可
ik_smart 分词器:
http://192.168.0.110:9200/_analyze
参数:
{
“analyzer”:“ik_smart”,
“text”:“小明”
}
结果为:
{
“tokens”:[
{
“token”:“小明”,
“start_offset”:0,
“end_offset”:2,
“type”:“CN_WORD”,
“position”:0
}
]
}
由上面结果可以看出 使用 ik 分词器 “analyzer”:“ik_smart” 会把结果正确显示出来。
映射的分类
动态映射
我们知道,在关系数据库中,需要事先创建数据库,然后在该数据库实例下创建数据表,然后才能在该数据表中插入数据。而ElasticSearch中不需要事先定义映射(Mapping),文档写入ElasticSearch时,会根据文档字段自动识别类型,这种机制称之为动态映射。默认映射为 long 类型。
静态映射
在ElasticSearch中也可以事先定义好映射,包含文档的各个字段及其类型等,这种方式称之为静态映射。
String 类型分为text、keyword。
text:会进行分词查询。
keywork:不会进行分词查询。