本文主要探讨es集群的高可用容错方案和容错能力的探讨。在出现单机故障时相关的容错方案。
更多关于分布式系统的架构思考请参考文档关于常见分布式组件高可用设计原理的理解和思考
可以参考文章ES高可用架构涉及常用功能整理,本文不再赘述。
更多细节可以参考玩转Elasticsearch源码-一图看懂ES启动流程
es集群的启动大致流程如下
这里的集群启动过程指集群完全重启时的启动过程,期间要经历选举主节点、主分片、数据恢复等重要阶段,理解其中原理和细节,对于解决或避免集群维护过程中可能遇到的脑裂、无主、恢复慢、丢数据等问题有重要作用。
es的master选主逻辑根据版本不同,有不同的调整
选举的第一步,就是需要过滤出选参选的活跃master节点列表,并判断活跃的master列表是否满足选举条件。
discovery.zen.ping.unicast.hosts
获取初始的master列表,之后需要做2个事情discovery.zen.minimum_master_nodes
要求,如果不满足,说明集群中参选的数量不足,有可能会有脑裂的风险,不能进一步选举。否则无法满足quorum机制注: 在7.0后版本中,废除了
discovery.zen.minimum_master_nodes
参数,而是通过类raft算法自行计算
Bully算法的基本原理就是,根据节点的ID大小来判定谁是leader
Bully算法在选举的时候会发送三种消息类型
这三种消息类型组成了Bully的基础消息类型,这也是Bully算法选举必须要了解的东西。
分步解释
从如上算法的介绍中,可以得知,
因此es给bully算增加了限制,以规避bully算法的原生问题。
discovery.zen.minimum_master_nodes
(n+1)/2
选票,才能成为leader这也是为什么在7.0版本,选举算法切换为raft的重要原因。
raft协议经常接触,可以参考 ETCD高可用架构涉及常用功能整理,不在介绍。
相比于Raft算法,Es的选主算法有如下不同
如果node1当选leader,但是node2发来了投票要求,那么node1无条件退出leader状态,node2选为主节点,但是node3也发来了投票要求,那么node2退出leader状态,node3当选主节点。
说明白了,就是保证最后当选的leader为主leader
无论是bully算法还是类raft协议,并不考虑当前节点的数据是否最新,而是在完成选举出leader后进行数据合并中完成数据的一致性问题。
原因是客户端在es的副本写入数据过程中,并不会通知master节点,因此master节点并不知道哪个节点的元数据最新,而是通过后续node节点的数据汇报进行完善,在这一点上跟hdfs的nn类似。
这跟etcd、zk有本质区别,因为etcd、zk的leader节点也是数据节点,所有的数据写入是从leader完成,follower进行同步,因此能够感知谁的数据最新。而es的master节点和node节点是拆分的,因此无法实现这一点,因此只能是类raft协议。
因此在完成leader选举后,需要进行元数据合并
当探测到节点离开事件时,必须判断当前节点数是否过半。如果达不到半数以上,则放弃Master身份,重新加入集群。如果不这么做,则设想以下情况:假设5台机器组成的集群产生网络分区,2台一组,3台一组,产生分区前,Master位于2台中的一个,此时3台一组的节点会重新并成功选取Master,产生双主,俗称脑裂。(节点失效检测)
节点失效检测会监控节点是否离线,然后处理其中的异常。失效检测是选主流程之后不可或缺的步骤,不执行失效检测可能会产生脑裂(双主或多主)。
完成master选主后,需要重建集群的shard路由表,该工作全部都是master完成
是不是效率有些低?这种询问量=shard 数×节点数。所以说我们最好控制shard的总规模别太大。
构建完所有的分片信息,现在考虑把哪个分片作为主分片。
但是有问题: 在多副本的情况下,考虑到如果只有一个 shard 信息汇报上来,则它一定会被选为主分片,但也许数据不是最新的,版本号比它大的那个shard所在节点还没启动。因此可能会数据丢失。
在解决这个问题的时候,ES 5.x开始实施一种新的策略:给每个 shard 都设置一个 UUID,然后在元信息中记录哪个shard是最新的(ES是先写主分片,再由主分片节点转发请求去写副分片,所以主分片所在节点肯定是最新的,如果它转发失败了,则要求Master删除那个节点,所以可以识别哪个分片最新)
如果集群设置了:禁止分配分片,集群仍会强制分配主分片。
"cluster.routing.allocation.enable": "none"
因此,在设置了上述选项的情况下,集群重启后的状态为Yellow,而非Red。
由于每次写操作都会记录事务日志(translog),事务日志中记录了哪种操作,以及相关的数据。因此将最后一次提交(一次提交就是一次 fsync 刷盘的过程)之后的 translog中进行重放,建立索引,如此完成主分片的recovery。
副分片的恢复是比较复杂的,在ES的版本迭代中,副分片恢复策略有过不少调整。副分片需要恢复成与主分片一致,同时,恢复期间允许新的索引操作。在目前的6.0版本中,恢复分成两阶段执行:
针对当前的分片数据做checkpoint,并送给副分片恢复,耗时长,但是并不影响新的数据写入(写的数据写入到新的translog中,并且在快照期间不会translog不会被清理)
涉及的数据量少,所以耗时短。
由于需要支持恢复期间的新增写操作(让ES的可用性更强),这两个阶段中需要重点关注以下几个问题:
es的分片恢复根据版本不同,有不同的调整
恢复时,因为主副分片恢复时间不一致,主分片先进行Recovery,然后副分片才能基于主分片进行Recovery,所以主分片可以工作之后,副分片可能还在恢复中,此时主分片会向副分片发送写请求,因此恢复reply与主分片可能会同时(或者不按发生顺序)对同一个doc进行操作。ES中通过doc的版本号解决这个问题,当收到一个版本号低于doc当前版本号的操作时,会放弃本次操作。对于特定的doc,只有最新一次操作生效。
当主分片不可用时,es就会重新进行选举,把最新的副本分片提高到主分片的地位,由master进行检测和分片选主,并在分片完成选主后,触发分片的数据恢复逻辑。
如果正在写入过程时,副本分片宕机或者出现异常,master会从shard分片中剔除该分片,继续执行写入。
取决于是否配置自动分配参数cluster.routing.allocation.enable
,默认是all,表示能够自动触发分配。
curl -XPUT http://127.0.0.1:9200/_cluster/settings -d '{
"transient" : {
"cluster.routing.allocation.enable" : "none"
}
}'
是否配置主动触发分配,有利有弊,主要原因是自动分配不能识别难以识别业务高峰期,会占用磁盘io和网络带宽。并且如果只是短时间维护节点,触发分配后,机器维护完成,又要重新触发恢复分配,恢复时间较长,因此根据实际情况调整。
个人建议