不知道大家在项目中是否使用过ElastricSearch?大家对它的了解又有多少呢?官网的定义:Elasticsearch是一个分布式、可扩展、近实时的搜索与数据分析引擎。今天我们就来揭开一下它的神秘面纱(以下简称ES)。
ES 是使用 Java 编写的一种开源搜索引擎,它在内部使用 Lucene 做索引与搜索,通过对 Lucene 的封装,隐藏了 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API。然而,ES不仅仅是 Lucene,并且也不仅仅只是一个全文搜索引擎,它还有如下特点:
- 一个分布式的实时文档存储,每个字段可以被索引与搜索;
- 一个分布式实时分析搜索引擎;
- 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据。
一、结构化数据、非结构化数据
首先我们将数据大体分为两类,一类是结构化数据:也称作行数据,指具有固定格式或有限长度的数据,是由二维表结构来逻辑表达和实现的数据,严格地遵循数据格式与长度规范,主要通过关系型数据库进行存储和管理,如数据库、元数据等;一类是非结构化数据:又可称为全文数据,不定长或无固定格式,不适于由数据库二维表来表现,包括所有格式的办公文档、XML、HTML、Word 文档,邮件,各类报表、图片和音频、视频信息等。
如果要更细致的区分的话,XML、HTML 可划分为半结构化数据。因为它们也具有自己特定的标签格式,所以既可以根据需要按结构化数据来处理,也可抽取出纯文本按非结构化数据来处理。
根据两种数据分类,搜索也相应的分为两种:
(1)结构化数据搜索:因为它们具有特定的结构,所以我们一般都是可以通过关系型数据库(MySQL,Oracle 等)的二维表(Table)的方式存储和搜索,也可以建立索引。
(2)非结构化数据搜索:即对全文数据的搜索,主要有两种方法:顺序扫描、全文搜索;
- 顺序扫描:即按照顺序扫描的方式查询特定的关键字。例如给你一张报纸,让你找到该报纸中“阿Q”的文字在哪些地方出现过。你肯定需要从头到尾把报纸阅读扫描一遍然后标记出关键字在哪些版块出现过以及它的出现位置。这种方式无疑是最耗时的最低效的。
- 全文搜索:将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构(这种结构,我们称之为索引),然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。这种方式的主要工作量在前期索引的创建,但是对于后期搜索却是快速高效的。
二、Lucene
Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。Lucene是一套用于全文检索和搜寻的开源程式库,由Apache软件基金会支持和提供。
我所熟知的以 Lucene 为基础建立的开源可用全文搜索引擎主要有 Solr 和 ES:ES 本身就具有分布式的特性和易安装使用的特点,而 Solr 的分布式需要借助第三方来实现,例如通过使用 ZooKeeper 来达到分布式协调管理。
接下来我们说一下Lucene能实现全文搜索的一个重要的查询结构:倒排索引
举例
- Java is the best programming language.
- PHP is the best programming language.
- Javascript is the best programming language.
通过分词器将每个文档的内容域拆分成单独的词(我们称它为词条或 Term),创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。
结果
这种结构由文档中所有不重复词的列表构成,对于其中每个词都有一个文档列表与之关联。这种由属性值来确定记录的位置的结构就是倒排索引。带有倒排索引的文件我们称为倒排文件。
将上面的内容转换为图的形式来说明倒排索引的结构信息
概念如下:
- 词条(Term):索引里面最小的存储和查询单元,对于英文来说是一个单词,对于中文来说一般指分词后的一个词。
- 词典(Term Dictionary):或字典,是词条 Term 的集合。搜索引擎的通常索引单位是单词,单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。
- 倒排表(Post list):一个文档通常由多个词组成,倒排表记录的是某个词在哪些文档里出现过以及出现的位置。
每条记录称为一个倒排项(Posting)。倒排表记录的不单是文档编号,还存储了词频等信息。 - 倒排文件(Inverted File):所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件被称之为倒排文件,倒排文件是存储倒排索引的物理文件。
词典和倒排表是 Lucene 中很重要的两种数据结构,是实现快速检索的重要基石。词典和倒排文件是分两部分存储的,词典在内存中而倒排文件存储在磁盘上。
三、ES中的概念解释
为了大家更好的理解ES,下面介绍一下ES中的概念:
(1)Near Realtime(NRT):近实时,一是从写入数据到数据可以被搜索到有一个小延迟(默认1秒);二是基于ES执行搜索和分析可以达到秒级;
(2)Cluster(集群):ES 集群由一个或多个 ES 节点组成,每个节点配置相同的 cluster.name (默认值为 “elasticsearch”)即可加入集群。确保不同的环境中使用不同的集群名称,否则最终会导致节点加入错误的集群。ES 的集群搭建很简单,不需要依赖第三方协调管理组件,自身内部就实现了集群的管理功能。
(3)Node:一个 ES 服务启动实例就是一个节点(Node)。节点通过 node.name 来设置节点名称,如果不设置则在启动时给节点分配一个随机通用唯一标识符作为名称(默认是随机分配的)。默认节点会去加入一个名称为“elasticsearch”的集群。如果直接启动一堆节点,那么它们会自动组成一个elasticsearch集群,当然一个节点也可以组成一个elasticsearch集群。
(4)Index(索引):简单理解就是一个数据库,索引有名称,包含一堆有相似结构的文档数据,一个index包含很多document,一个index就代表了一类类似的或者相同的document。比如说建立一个product index,商品索引,里面可能就存放了所有的商品数据,所有的商品document。索引是保存相关数据的地方。 索引实际上是指向一个或者多个物理 分片 的 逻辑命名空间 。
(5)Type(类型):简单理解就是一张表,每个索引里都可以有一个或多个type,type是index中的一个逻辑数据分类,一个type下的document,都有相同的field,比如博客系统,有一个索引,可以定义用户数据type,博客数据type,评论数据type。
(6)Document(文档)&field(字段):Document就是一行数据,es中的最小数据单元,一个document可以是一条客户数据,一条商品分类数据,通常用JSON数据结构表示,每个index下的type中,都可以去存储多个document。一个document里面有多个field,每个field就是一个数据字段。
四、ES 集群的选举原理
①节点的角色
节点分为主节点、候选主节点、数据节点和协调节点,其中候选主节点和数据节点是在配置文件中指定的,而主节点是由候选主节点选举出来的,协调节点则可以是任何节点,以下是它们的功能介绍:
数据节点:负责数据的存储和相关的操作,例如对数据进行增、删、改、查和聚合等操作,所以数据节点(Data 节点)对机器配置要求比较高,对 CPU、内存和 I/O 的消耗很大。通常随着集群的扩大,需要增加更多的数据节点来提高性能和可用性。
候选主节点:可以被选举为主节点(Master 节点),集群中只有候选主节点才有选举权和被选举权,其他节点不参与选举的工作。
主节点:负责创建索引、删除索引、跟踪哪些节点是群集的一部分,并决定哪些分片分配给相关的节点、追踪集群中节点的状态等。主节点和其他节点之间通过 Ping 的方式互相检查,主节点负责 Ping 所有其他节点,判断是否有节点已经挂掉;其他节点也通过 Ping 的方式判断主节点是否处于可用状态;稳定的主节点对集群的健康是非常重要的。
协调节点:虽然对节点做了角色区分,但是用户的请求可以发往任何一个节点,并由该节点负责分发请求、收集结果等操作,而不需要主节点转发,这种节点可称之为协调节点,协调节点是不需要指定和配置的,集群中的任何节点都可以充当协调节点的角色。
ES中每个节点既可以是候选主节点也可以是数据节点,通过在配置文件 …/config/elasticsearch.yml 中设置即可,默认都为 true。
node.master: true //是否候选主节点
node.data: true //是否数据节点
但是由于数据节点对 CPU、内存核 I/O 消耗都很大,所以如果某个节点既是数据节点又是主节点,那么可能会对主节点产生影响从而对整个集群的状态产生影响。因此为了提高集群的健康性,我们应该对 ES集群中的节点做好角色上的划分和隔离,可以使用几个配置较低
的机器群作为候选主节点群。
②发现与选举机制
发现机制(Zen Discovery)
那么有一个问题,ES 内部是如何通过一个相同的设置 cluster.name 就能将不同的节点连接到同一个集群的呢?答案是 Zen Discovery
。Zen Discovery
是 ES的内置默认发现模块(发现模块的职责是发现集群中的节点以及选举 Master 节点)。它提供单播和基于文件的发现,并且可以扩展为通过插件支持云环境和其他形式的发现。Zen Discovery
与其它模块集成:例如,节点之间的所有通信都使用 Transport 模块完成,节点使用发现机制通过 Ping 的方式查找其他节点。
ES默认被配置为使用单播
发现,以防止节点无意中加入集群,只有在同一台机器上运行的节点才会自动组成集群。如果集群的节点运行在不同的机器上,使用单播,你可以为 ES节点提供一些它应该去尝试连接的节点列表即单播列表。当该节点联系到单播列表中的成员时,它就会得到整个集群所有节点的状态,然后它会联系 Master 节点,并加入集群。这意味着单播列表不需要包含集群中的所有节点, 它只是需要足够的节点,当一个新节点联系上其中一个并且说上话就可以了。如果你使用 Master 候选节点作为单播列表,你只要列出三个就可以了。这个配置在 elasticsearch.yml 文件中:
discovery.zen.ping.unicast.hosts: ["host1", "host2:port"]
ES节点启动后先 Ping ,如果 discovery.zen.ping.unicast.hosts 有设置,则 Ping 设置中的 Host ,否则尝试 ping 本地的几个端口。ES支持同一个主机启动多个节点,Ping 的 Response 会包含该节点的基本信息以及该节点认为的 Master 节点。
Master选举
采用分布式系统思路,保证选举出的master被多数派(quorum)的master-eligible node(候选主节点)认可,以此来保证只有一个master。这个quorum通过以下配置进行配置:
conf/elasticsearch.yml:
discovery.zen.minimum_master_nodes: 2
(1)master选举谁发起,什么时候发起?
master(主节点)选举当然是由master-eligible(候选主节点)节点发起,当一个master-eligible节点发现满足以下条件时发起选举:
- 该master-eligible节点的当前状态不是master;
- 该master-eligible节点通过ZenDiscovery模块的ping操作询问其已知的集群其他节点,没有任何节点连接到master;
- 包括本节点在内,当前已有超过minimum_master_nodes个节点没有连接到master;
总结一句话,即当一个节点发现包括自己在内的多数派的master-eligible节点认为集群没有master时,就可以发起master选举。
(2)当需要选举master时,选举谁?
如果各节点都没有认为的 Master ,则从所有候选主节点中选择,规则很简单,按照 ID(ID为节点第一次启动时随机生成) 的字典序排序,取第一个。之所以这么设计,应该是为了让选举结果尽可能稳定,不要出现都想当master而选不出来的情况。
(3) 什么时候选举成功?
这里有个限制条件就是 discovery.zen.minimum_master_nodes :如果候选主节点数达不到最小值的限制,则循环上述过程,直到候选主节点数足够可以开始选举,如果只有一个 Local 节点那就选出的是自己。如果当前节点被选为Master ,则开始等待节点数达到 discovery.zen.minimum_master_nodes,然后提供服务;如果当前节点不是 Master ,则尝试加入 Master 。
举例说明:
当一个master-eligible node(我们假设为Node_A)发起一次选举时:
a、假设Node_A选Node_B当Master:Node_A会向Node_B发送join请求,那么此时:
- 如果Node_B已经成为Master,Node_B就会把Node_A加入到集群中,然后发布最新的cluster_state, 最新的cluster_state就会包含Node_A的信息,相当于一次正常情况的新节点加入。对于Node_A,等新的cluster_state发布到Node_A的时候,Node_A也就完成join了。
- 如果Node_B在竞选Master,那么Node_B会把这次join当作一张选票。对于这种情况,Node_A会等待一段时间,看Node_B是否能成为真正的Master,直到超时或者有别的Master选成功。
- 如果Node_B认为自己不是Master(现在不是,将来也选不上),那么Node_B会拒绝这次join。对于这种情况,Node_A会开启下一轮选举。
b、假设Node_A选自己当Master:
此时NodeA会等别的node来join,即等待别的node的选票,当收集到超过半数的选票时,认为自己成为master,然后变更cluster_state中的master node为自己,并向集群发布这一消息。
③脑裂现象
如果由于网络或其他原因导致集群中选举出多个 Master 节点,使得数据更新时出现不一致,这种现象称之为脑裂,即集群中不同的节点对于 Master 的选择出现了分歧,出现了多个 Master 竞争。
脑裂问题可能有以下几个原因造成:
- 网络问题:集群间的网络延迟导致一些节点访问不到 Master,认为 Master 挂掉了从而选举出新的 Master,并对 Master 上的分片和副本标红,分配新的主分片。
- 节点负载:主节点的角色既为 Master 又为 Data,访问量较大时可能会导致 ES 停止响应(假死状态)造成大面积延迟,此时其他节点得不到主节点的响应认为主节点挂掉了,会重新选取主节点。
- 内存回收:主节点的角色既为 Master 又为 Data,当 Data 节点上的 ES 进程占用的内存较大,引发 JVM 的大规模内存回收,造成 ES 进程失去响应。
为了避免脑裂现象的发生,我们可以从原因着手通过以下几个方面来做出优化措施:
- 适当调大响应时间,减少误判:通过参数 discovery.zen.ping_timeout 设置节点状态的响应时间,默认为 3s,可以适当调大。如果 Master 在该响应时间的范围内没有做出响应应答,判断该节点已经挂掉了。
- 选举触发:我们需要在候选集群中的节点的配置文件中设置参数 discovery.zen.munimum_master_nodes 的值。这个参数表示在选举主节点时需要参与选举的候选主节点的节点数,默认值是 1,官方建议取值(master_eligibel_nodes/2)+1,其中 master_eligibel_nodes 为候选主节点的个数。这样做既能防止脑裂现象的发生,也能最大限度地提升集群的高可用性,因为只要不少于 discovery.zen.munimum_master_nodes 个候选节点存活,选举工作就能正常进行;当小于这个值的时候,无法触发选举行为,集群无法使用,不会造成分片混乱的情况。
- 角色分离:即是上面我们提到的候选主节点和数据节点进行角色分离,这样可以减轻主节点的负担,防止主节点的假死状态发生,减少对主节点“已死”的误判。