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