ETCD节点故障的容错方案
- [1. 选主逻辑](#1. 选主逻辑)
-
- [1.1 什么样的节点应该成为Leader?](#1.1 什么样的节点应该成为Leader?)
- [1.2 选主的4个阶段](#1.2 选主的4个阶段)
- [1.2 初始运行的选主过程](#1.2 初始运行的选主过程)
- [1.3 运行过程中异常的选主过程](#1.3 运行过程中异常的选主过程)
- [2. Raft日志复制逻辑](#2. Raft日志复制逻辑)
- [3. Leader故障的几种典型场景](#3. Leader故障的几种典型场景)
-
- [3.1 故障恢复 - Leader选举](#3.1 故障恢复 - Leader选举)
- [3.2 故障恢复 - 数据恢复](#3.2 故障恢复 - 数据恢复)
- [4. 疑问和思考](#4. 疑问和思考)
-
- [4.1. 如果etcd集群全挂了,怎么保持启动顺序?](#4.1. 如果etcd集群全挂了,怎么保持启动顺序?)
- [4.2. 在COMMIT未完成时,Leader宕机,相关的事务数据是否可能会丢失?](#4.2. 在COMMIT未完成时,Leader宕机,相关的事务数据是否可能会丢失?)
- [4.3. 为什么要设计Leader?](#4.3. 为什么要设计Leader?)
- [5. 参考文档](#5. 参考文档)
本文主要探讨etcd集群的高可用容错方案和容错能力的探讨。在出现单机故障时相关的容错方案,如果故障的节点是Leader,重新选择Leader的选主逻辑,以及集群的恢复方案。由于单节点已经保存保存了所有数据,因此集群出现节点异常时,只需要重新选举出Leader即可,不需要进行数据迁移、副本备份等操作。
更多关于分布式系统的架构思考请参考文档关于常见分布式组件高可用设计原理的理解和思考
1. 选主逻辑
etcd通过Raft协议通过Quorum机制进行集群的高可用和保证数据一致性,在节点故障场景下,能够通过选主逻辑,选择新的Leader,重新提供服务。集群最大能够容忍的故障节点数量为 (n -1) / 2
,n表示集群的节点数量(通常要求部署n为单数,如3,5,7)。
选主的目标,是希望选择一个Leader以继续提供服务,分为初始运行选择Leader 和运行中Leader出现异常重新触发选主。
1.1 什么样的节点应该成为Leader?
在讨论选主逻辑之前,应该思考一下,什么样的节点应该成为Leader? 因为投票过程需要比较,比较的依据就是按照成为Leader的标准进行参照!
按照etcd的设计,成为Leader的节点应该满足如下条件,在同一个选主周期过程中
- lastIndex(本地数据索引)越大,越容易成为Leader(通常写入的数据越多)
- lastTerm(选票纪元)大的选票有效,代表当前投票的有效性越高
- 获取超过集群半数节点的选票的节点,成为Leader
因此整理出,在投票选主过程中,有效相关因素
属性 | 作用 | 说明 |
---|---|---|
lastIndex | 日志记录单调递增 | 节点lastIndex越大(数据最新),该节点越容易成为Leader |
lastTerm | 选举纪元,单调递增 | 原则上选举纪元大的更有效,如果多个Leader相遇,纪元大有效(规避脑裂情况) |
可以总结出,成为Leader的原则是,在相同选票周期内,选择数据最新的节点成为Leader。
1.2 选主的4个阶段
在同一投票纪元内,只有Follower才能参与投票成为Leader,默认启动时节点的角色都是Follower
1,阶段1: 各节点增加当前的投票周期term,转变为candidate给自己投票,投票的信息内容是(lastTerm,lastIndex),将选票信息广播
2,阶段2: 接受其他节点的广播选票信息,跟当前节点的选对比,根据lastTerm大、lastIndex大的原则,更改本节点的选票,比如当前节点的选票信息(2,0) < 广播选票(2,1),则当前节点的选票更新为(2,1)
3,阶段3: 完成阶段2的交换选票后,将当前节点交换后的选票广播给其他节点并进行统计,当前节点会根据获取的选票判断自己的角色,并得知对应的Leader节点。如果本节点收到超过集群半数节点选票,当前节点则成为Leader,反之则是Follower
4,阶段4: Leader主动发布心跳,如果Follower能够跟Leader进行通信并进行数据同步,选主结束;否则进行下一轮投票。
整体的逻辑跟zk的 选主逻辑 相近,但是差别是第4步,zk是Follower主动联系Leader,etcd是Leader主动发送心跳给Follower,目标都是为了让Leader和Follower完成握手,确认和承认对方的江湖地位。
1.2 初始运行的选主过程
以5个服务节点组成的etcd集群来看看Leader选举的过程。
第一次启动,都处于初始状态,lastIndex都是0,lastTerm分别为0。票据(lastIndex,lastTerm)分别为(0,0)、(0,0)、(0,0)、(0,0)、(0,0)(此概念不涉及源码层面,但是原理上差不多),逐个启动服务器,如下是具体的投票选举过程:
- 服务器1启动,发起一次投票,服务器1给自己投1票(0,1),票数不过半,选举无法完成,并且随着时间进行会继续发起下一轮投票(0,n),并广播选票信息,请求其他节点选票
- 服务器2的服务器启动,发起一次投票,服务器2给自己投票(0,1),并广播选票信息
- 服务器1和服务器2交换选票信息,对比(lastIndex,lastTerm),服务器1的lastTerm比服务器2的大,所以服务器2的选票更换为给服务器1投票,服务器1不需要更改
- 统计选票,服务器1和服务器2变更选票信息后再次交换选票,服务器1和服务器2都有两张一样的票(0,n),服务器1有2票,服务器2有0票,但是票数不过半,无法完成投票。
- 服务器3的服务器启动,发起一次投票,服务器3给自己投票(0,1),此时服务器1持有的票据是(0,n),服务器3的票据为(0,1),互相交换选票(每个服务器的票据都要广播给其他具有投票权的服务器),所以服务器3的选票更换为给服务器1投票,服务器1不需要更改
- 再次统计选票,服务器1有3票,服务器2和3都是0票。服务器1持有的票数过半,Leader选举完成,服务器1成为 Leader,服务器1和2成为Follower
- Leader广播心跳,Follower收到心跳信息后,同步相关信息,集群的Leader地位确立,选举过程结束。
- 服务器4启动,发现集群中已经有Leader了,就变更自己的角色状态为Follower。
- 服务器5启动,同理也变更自己的角色状态为 Follower。
1.3 运行过程中异常的选主过程
etcd集群正常运行期间,一旦选出了Leader,那么所有服务器的角色都不会再发生变化。
- 启动的服务器发现有Leader,就直接变更自己的角色为Follower;
- Follower挂了,只要正常运行的服务器数量还在半数以上,整个集群就还可以正常对外提供服务。
但是Leader所在的服务器挂了,如果集群中正常运行的机器半数以上,就得重选Leader。整体的选主逻辑跟《1.2 初始运行的选主过程》一致。
2. Raft日志复制逻辑
日志复制分为2个阶段
- 阶段1: Wal日志和内存数据同步(过程较重,需要超过半数节点完成即即可认为该阶段已经结束),这个阶段如果写入过程Leader宕机,由于没有节点COMMIT所以认为数据无效
- 阶段2: 广播数据COMMIT(过程较轻),这个阶段如果在COMMIT期间Leader宕机,只要有1个Follower完成了COMIT,数据就认为是有效的,因为重新进行选举时完成COMMIT的节点的lastIndex最大,会被选举成为Leader(除非这个节点也宕机了)
整体过程如下
- 客户端发送一个改变数据的请求X,
- Leader将X写入自己的日志中, 并向所有Follower发送日志追加(Append Entries)请求A(X)、
- Follower接受A(X), 将X的数据变更写入日志并返回给Leader
- 当不少于
(N - 1) / 2
个Follower(加上自己已经过半)响应成功时, Leader发起commit该数据并向所有节点发送commit请求C(X), 同时响应客户端
3. Leader故障的几种典型场景
Leader在挂掉的时候,可能还有一些写操作没有完成,如下是几种Leader故障的场景:
- Leader完成了所有事务COMMIT,然后挂了,此时过半数以上的服务器的数据是一致的,重选Leader基本不影响(事务已经完成,数据不丢失)
- Leader在接收到事务请求至最后广播COMMIT之前的某个时刻挂了,这都属于未完成的事务请求,即使有服务器已经把事务请求持久化到了日志文件,但是还没有完成最后的COMMIT将数据同步到内存数据库(事务未完成,数据会被丢弃)
- Leader在广播COMMIT的过程中挂了,只要有1个节点完成了COMMIT,则认为这个数据有效,否则就会认为无效。原因是集群选举Leader时,该节点的lastIndex最大(数据最新)会被选举成为Leader。选举成Leader后会重新加载Wal并同步给其他节点(事务已经完成,数据不丢失)
这就会导致数据恢复过程中,需要拆分为Leader选举 和数据恢复 2个阶段
3.1 故障恢复 - Leader选举
Leader 重新选举的过程和第一次启动选举一样,不再赘述。
成为新Leader必须满足以下两个条件:
- 新Leader的lastIndex是所有服务器中最大的,这就可以最大限度的恢复数据。
- 新Leader不能包含未COMMIT的事务提议,如果事务日志文件中有未同步到内存数据库的事务将会回滚丢弃。
需要注意的是在Leader选举的过程中,集群是处于无法正常对外提供服务的状态。如果由于各种原因,导致选主失败,则集群一直无法恢复。
3.2 故障恢复 - 数据恢复
为了集群全局数据一致性,所有的 Follower上的数据都必须和Leader的一样,数据同步过程如下
- Leader有的Follower没有,Leader将这些事务同步给Follower。
- Leader没有的,Follower有,Follower需要回滚丢弃这些所谓超前的事务。
特别是已经宕机很久的Follower节点,有可能会保存一些无效的COMMIT事务。
4. 疑问和思考
4.1. 如果etcd集群全挂了,怎么保持启动顺序?
如果原先集群运行了一段时间,产生了数据不一致,然后全部停机后,再一个个重启,最好是先去看看每个服务的事务日志文件内记录的最后提交的lastIndex,最大的那个服务先重启,否则可能会导致Leader已经选举好了,后面一个带着最全数据的服务节点重启了,反而要回滚事务。
4.2. 在COMMIT未完成时,Leader宕机,相关的事务数据是否可能会丢失?
Leader在广播COMMIT的过程中挂了,只要有1个节点完成了COMMIT,则认为这个事务已经完成数据有效,否则就会认为无效会被丢弃。如果完成COMMIT的节点也宕机了,数据就会丢失,就算后续拉起该节点,相关的事务也会被回滚。
4.3. 为什么要设计Leader?
Leaner的职能和功能跟Follower整体上并没有区别,但是Leaner没有选票权,不参与投票,也不会参与竞选Leader。这样设计的目的是,能够提升集群的读能力,但是又不必引入过多的投票节点,从而导致造成过多轮投票。过多轮投票选择出Leader,势必会延迟集群的恢复时间。