1、为什么要使用Elasticsearch?
因为以往的商品模糊查询,模糊查询前置配置,会放弃索引,导致全表扫面,在百万级别的数据库中,效率非常低下,而我们使用ES做一个全文索引,我们将经常查询的商品的某些字段,比如说商品名,描述、价格还有id这些字段我们放入我们索引库里,可以提高查询速度。
2、Elasticsearch 集群构成
首先,一个Elasticsearch集群(下面简称ES集群)是由许多节点(Node)构成的,Node可以有不同的类型,通过以下配置,可以产生四种不同类型的Node:
conf/elasticsearch.yml:
node.master: true/false
node.data: true/false
四种不同类型的Node是一个node.master和node.data的true/false的两两组合。
当然还有其他类型的Node,比如IngestNode(用于数据预处理等),不在本文讨论范围内。
当node.master为true时,其表示这个node是一个master的候选节点,可以参与选举。
当node.data为true时,这个节点作为一个数据节点,会存储分配在该node上的shard的数据并负责这些shard的写入、查询等。
此外,任何一个集群内的node都可以执行任何请求,其会负责将请求转发给对应的node进行处理,所以当node.master和node.data都为false时,这个节点可以作为一个类似proxy的节点,接受请求并进行转发、结果聚合等。
上图是一个ES集群的示意图,其中NodeA是当前集群的Master,NodeB和NodeC是Master的候选节点,其中NodeA和NodeB同时也是数据节点(DataNode),此外,NodeD是一个单纯的数据节点,Node_E是一个proxy节点。每个Node会跟其他所有Node建立连接。
3、节点发现
ZenDiscovery是 ES 自己实现的一套用于节点发现和选主等功能的模块,没有依赖Zookeeper等工具,官方文档:
https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-discovery-zen.html
简单来说,节点发现依赖以下配置:
conf/elasticsearch.yml:
discovery.zen.ping.unicast.hosts: [1.1.1.1, 1.1.1.2, 1.1.1.3]
这个配置可以看作是,在本节点到每个hosts中的节点建立一条边,当整个集群所有的node形成一个联通图时,所有节点都可以知道集群中有哪些节点,从而不会让自己形成孤岛。
官方推荐这里设置为所有的 master-eligible node(master的候选节点)
4、master 选举
为了避免产生脑裂,ES采用了常见的分布式系统思路,保证选举出的master被多数派(quorum)的master-eligible node认可,以此来保证只有一个master。这个quorum通过以下配置进行配置:
conf/elasticsearch.yml:
discovery.zen.minimum_master_nodes: 2
这个配置对于整个集群非常重要。
(1)谁发起选举?
master选举当然是由master-eligible节点(master的候选节点)发起。
(2)什么时候发起选举?
当一个master-eligible节点发现满足以下条件时发起选举:
-
该master-eligible节点的当前状态不是master
-
该master-eligible节点通过ZenDiscovery模块的ping操作询问其已知的集群其他节点,没有任何节点连接到master
-
包括本节点在内,当前已有超过minimum_master_nodes个节点没有连接到master
总结就是当一个节点发现包括自己在内的多数派的master-eligible节点都认为集群没有master时,就可以发起master选举。
(3)选举谁?
选举的是排序后的第一个master-eligible node(master 候选节点)。
注:如何进行排序的?
clusterStateVersion越大,优先级越高。(当clusterStateVersion相同时,节点的Id越小,优先级越高。)
(4)什么时候选举成功?
当一个master-eligible node(我们假设为Node_A)发起一次选举时,它会按照上述排序策略选出一个它认为的master。
假设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会开启下一轮选举。
-
假设Node_A选自己当Master:
此时NodeA会等别的node来join,即等待别的node的选票,当收集到超过半数的选票时,认为自己成为master,然后变更cluster_state中的master node为自己,并向集群发布这一消息。
按照上述流程,我们描述一个简单的场景来帮助大家理解:
假如集群中有3个master-eligible node,分别为Node_A、 Node_B、 Node_C, 选举优先级也分别为Node_A、Node_B、Node_C。三个node都认为当前没有master,于是都各自发起选举,选举结果都为Node_A(因为选举时按照优先级排序,如上文所述)。于是Node_A开始等join(选票),Node_B、Node_C都向Node_A发送join,当Node_A接收到一次join时,加上它自己的一票,就获得了两票了(超过半数),于是Node_A成为Master。此时cluster_state(集群状态)中包含两个节点,当Node_A再收到另一个节点的join时,cluster_state包含全部三个节点。
5、选举怎么保证不脑裂?
基本原则还是多数派的策略,如果必须得到多数派的认可才能成为Master,那么显然不可能有两个Master都得到多数派的认可。
上述流程中,master候选人需要等待多数派节点进行join后才能真正成为master,就是为了保证这个master得到了多数派的认可。
6、错误检测
(1)MasterFaultDetection 与 NodesFaultDetection
这里的错误检测可以理解为类似心跳的机制,有两类错误检测,一类是Master定期检测集群内其他的Node,另一类是集群内其他的Node定期检测当前集群的Master。检查的方法就是定期执行ping请求。ES文档:
如果Master检测到某个Node连不上了,会执行removeNode的操作,将节点从cluste_state中移除,并发布新的cluster_state。当各个模块apply新的cluster_state时,就会执行一些恢复操作,比如选择新的primaryShard或者replica,执行数据复制等。
如果某个Node发现Master连不上了,会清空pending在内存中还未commit的new cluster_state,然后发起rejoin,重新加入集群(如果达到选举条件则触发新master选举)。
(2)rejoin
除了上述两种情况,还有一种情况是Master发现自己已经不满足多数派条件(>=minimumMasterNodes)了,需要主动退出master状态(退出master状态并执行rejoin)以避免脑裂的发生,那么master如何发现自己需要rejoin呢?
上面提到,当有节点连不上时,会执行removeNode。在执行removeNode时判断剩余的Node是否满足多数派条件,如果不满足,则执行rejoin。
在publish新的cluster_state时,分为send阶段和commit阶段,send阶段要求多数派必须成功,然后再进行commit。如果在send阶段没有实现多数派返回成功,那么可能是有了新的master或者是无法连接到多数派个节点等,则master需要执行rejoin。
在对其他节点进行定期的ping时,发现有其他节点也是master,此时会比较本节点与另一个master节点的cluster_state的version,谁的version大谁成为master,version小的执行rejoin。
7、集群扩缩容
(1)扩容
假设一个ES集群存储或者计算资源不够了,我们需要进行扩容,这里我们只针对DataNode,即配置为:
conf/elasticsearch.yml:
node.master: false
node.data: true
然后需要配置集群名、节点名等其他配置,为了让该节点能够加入集群,我们把discovery.zen.ping.unicast.hosts 配置为集群中的master-eligible node。
conf/elasticsearch.yml:
cluster.name: es-cluster
node.name: node_Z
discovery.zen.ping.unicast.hosts: ["x.x.x.x", "x.x.x.y", "x.x.x.z"]
然后启动节点,节点会自动加入到集群中,集群会自动进行rebalance,或者通过reroute api进行手动操作。
(2)缩容DataNode
假设一个ES集群使用的机器数太多了,需要缩容,我们怎么安全的操作来保证数据安全,并且不影响可用性呢?
首先,我们选择需要缩容的节点,注意本节只针对DataNode的缩容,MasterNode缩容涉及到更复杂的问题,下面再讲。
然后,我们需要把这个Node上的Shards迁移到其他节点上,方法是先设置allocation规则,禁止分配Shard到要缩容的机器上,然后让集群进行rebalance。
PUT _cluster/settings
{
"transient" : {
"cluster.routing.allocation.exclude._ip" : "10.0.0.1"
}
}
等这个节点上的数据全部迁移完成后,节点可以安全下线。
(3)扩容MasterNode
假如我们想扩容一个MasterNode(master-eligible node), 那么有个需要考虑的问题是,上面提到为了避免脑裂,ES是采用多数派的策略,需要配置一个quorum数:
conf/elasticsearch.yml:
discovery.zen.minimum_master_nodes: 2
假设之前3个master-eligible node,我们可以配置quorum为2,如果扩容到4个master-eligible node,那么quorum就要提高到3。
所以我们应该先把discovery.zen.minimum_master_nodes这个配置改成3,再扩容master,更改这个配置可以通过API的方式:
curl -XPUT localhost:9200/_cluster/settings -d '{
"persistent" : {
"discovery.zen.minimum_master_nodes" : 3
}
}'
这个API发送给当前集群的master,然后新的值立即生效,然后master会把这个配置持久化到cluster meta中,之后所有节点都会以这个配置为准。
但是这种方式有个问题在于,配置文件中配置的值和cluster meta中的值很可能出现不一致,不一致很容易导致一些奇怪的问题,比如说集群重启后,在恢复cluster meta前就需要进行master选举,此时只可能拿配置中的值,拿不到cluster meta中的值,但是cluster meta恢复后,又需要以cluster meta中的值为准,这中间肯定存在一些正确性相关的边界case。
总之,动master节点以及相关的配置一定要谨慎,master配置错误很有可能导致脑裂甚至数据写坏、数据丢失等场景。
(4)缩容MasterNode
缩容MasterNode与扩容是相反的流程,我们需要先把节点缩下来,再把quorum数调下来,不再详细描述。
8、如果我们使用Zookeeper来实现上面的这几个功能,会带来哪些变化?
(1)Zookeeper介绍
我们首先介绍一下Zookeeper,熟悉的同学可以略过。
Zookeeper分布式服务框架是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。
简单来说,Zookeeper就是用于管理分布式系统中的节点、配置、状态,并完成各个节点间配置和状态的同步等。大量的分布式系统依赖Zookeeper或者是类似的组件。
Zookeeper通过目录树的形式来管理数据,每个节点称为一个znode,每个znode由3部分组成:
stat. 此为状态信息, 描述该znode的版本, 权限等信息.
data. 与该znode关联的数据.
children. 该znode下的子节点.
stat中有一项是ephemeralOwner,如果有值,代表是一个临时节点,临时节点会在session结束后删除,可以用来辅助应用进行master选举和错误检测。
Zookeeper提供watch功能,可以用于监听相应的事件,比如某个znode下的子节点的增减,某个znode本身的增减,某个znode的更新等。
(2)怎么使用Zookeeper实现 ES 的上述功能
节点发现:每个节点的配置文件中配置一下Zookeeper服务器的地址,节点启动后到Zookeeper中某个目录中注册一个临时的znode。当前集群的master监听这个目录的子节点增减的事件,当发现有新节点时,将新节点加入集群。
master选举:当一个master-eligible node启动时,都尝试到固定位置注册一个名为master的临时znode,如果注册成功,即成为master,如果注册失败则监听这个znode的变化。当master出现故障时,由于是临时znode,会自动删除,这时集群中其他的master-eligible node就会尝试再次注册。使用Zookeeper后其实是把选master变成了抢master。
错误检测:由于节点的znode和master的znode都是临时znode,如果节点故障,会与Zookeeper断开session,znode自动删除。集群的master只需要监听znode变更事件即可,如果master故障,其他的候选master则会监听到master znode被删除的事件,尝试成为新的master。
集群扩缩容:扩缩容将不再需要考虑minimum_master_nodes配置的问题,会变得更容易。
9、使用Zookeeper的优劣点
使用Zookeeper的好处是,把一些复杂的分布式一致性问题交给Zookeeper来做,ES本身的逻辑就可以简化很多,正确性也有保证,这也是大部分分布式系统实践过的路子。而ES的这套ZenDiscovery机制经历过很多次bug fix,到目前仍有一些边角的场景存在bug,而且运维也不简单。
那为什么ES不使用Zookeeper呢,大概是官方开发觉得增加Zookeeper依赖后会多依赖一个组件,使集群部署变得更复杂,用户在运维时需要多运维一个Zookeeper。
那么在自主实现这条路上,还有什么别的算法选择吗?当然有的,比如raft。
10、raft
raft 算法是近几年很火的一个分布式一致性算法,其实现相比paxos简单,在各种分布式系统中也得到了应用。这里不再描述其算法的细节,我们单从master选举算法角度,比较一下 raft 与 ES 目前选举算法的异同点:
相同点:
多数派原则:必须得到超过半数的选票才能成为master。
选出的leader一定拥有最新已提交数据:在raft中,数据更新的节点不会给数据旧的节点投选票,而当选需要多数派的选票,则当选人一定有最新已提交数据。在es中,version大的节点排序优先级高,同样用于保证这一点。
不同点:
正确性论证:raft是一个被论证过正确性的算法,而ES的算法是一个没有经过论证的算法,只能在实践中发现问题,做bug fix,这是我认为最大的不同。
是否有选举周期term:raft 引入了选举周期的概念,每轮选举term加1,保证了在同一个term下每个参与人只能投1票。ES在选举时没有term的概念,不能保证每轮每个节点只投一票。
选举的倾向性:raft中只要一个节点拥有最新的已提交的数据,则有机会选举成为master。在ES中,version相同时会按照NodeId排序,总是NodeId小的节点优先级高。
11、针对 es 与 raft 的看法
raft从正确性上看肯定是更好的选择,而ES的选举算法经过几次bug fix也越来越像raft。当然,在ES最早开发时还没有raft,而未来ES如果继续沿着这个方向走很可能最终就变成一个raft实现。
raft不仅仅是选举,下一篇介绍meta数据一致性时也会继续比较ES目前的实现与raft的异同。
小结
本篇介绍了Elasticsearch集群的组成、节点发现、master选举、故障检测和扩缩容等方面的实现,与一般的文章不同,本文对其原理、存在的问题也进行了一些分析,并与其他实现方式进行了比较。
作为Elasticsearch分布式一致性原理剖析系列的第一篇,本文先从节点入手,下一篇会介绍meta数据变更的一致性问题,会在本文的基础上对ES的分布式原理做进一步分析。
原文链接:Elasticsearch分布式一致性原理剖析(一)-节点篇