etcd Learner
Gyuho Lee (github.com/gyuho, Amazon Web Services, Inc.), Joe Betz (github.com/jpbetz, Google Inc.)
背景
成员变更是系统其中最具挑战性的操作之一。我们重新回顾一下那些挑战。
1. 新集群成员使Leader过载
一个新的成员加入是没有数据的,因此需要从leader那里获取更新日志,直到追赶上leader的日志。所以啊,leader的网络可能更容易负载高,可能会阻塞或者丢失leader发给follower的心跳包。在这种情况下,follower可能会出现 选举超时,进而开始一轮新的leader选举。也就是新成员加入到集群可能更容易造成leader选举。leader选举和后续的更新传递给新成员都容易引起集群间断的不可用。
Finger 1
2. 网络分区的场景
网络分区的场景,如果网络分区了,取决于leader的分区。如果leader仍旧能够收到大多数的心跳响应,集群仍旧可以继续操作。
Figure 2
2.1 Leader被隔离
如果leader和其它剩余的节点隔离了?Leader会监控每一个follower的状态。当leader失去和大多数节点的链接,他转换回follower身份,这样会影响集群的可用性。
Figure 3
当一个节点加入到一个3节点的集群中,集群大小变成4,这时多数节点的个数就是3。此时如果发生网络分区会发生什么?这取决于在分区之后新节点加入的分区。
2.2 集群被分成了 3+1
如果这个新节点碰巧加入到了和leader同在的分区。leader仍旧可以和活跃的3个节点保持大多数。不会发生重新选举,也不会影响到集群的可用性。
Figure 4
2.3 集群被分成了 2+2
如果集群是2-2分区了,那么任何一个分区都达不到大多数3。在这种情况下,leader选举是会发生的。并且如果分区一直持续,那么leader会一直选,并且选不出来。
Figure 5
2.4 大多数活跃丢失
如果网络分区先发生,然后一个成员要加入。一个已经分区的3节点集群已经和一个follower失去链接了。当一个新的节点加入(我:为什么此时还能加入新成员呢?因为当前和还是有leader的,可以提交成功成员变更请求),因此大多数从2改变成3。现在这个集群4个节点只有2个活跃的(我:因为新成员还没加进来),因此失去大多数并且开始一个新的选举。
Figure 6
因为成员添加可能会改变quorum(大多数)的值的大小,所以首先推荐用“成员删除”去替换那些不健康的节点。
往一个只有一个成员的集群中添加节点会改变quorum大小成2,当之前的leader发现大多数(我:此时大多数的值已经是2了)不是active的时候立刻会引起leader重新选举。(我:这里我尝试分析一下,C(old,new) 会在老集群提交,那么老集群立刻使用C(old,new)的配置,此时quorum变成了2,那么就不能对外提供服务了)这是因为“member add”操作是一个两阶段处理的过程,即用户需要先执行 "member add" 命令,之后启动新节点的进程。
Figure 73. 集群配置错了
更坏的情况是当我们添加一个成员的时候配置错了。成员变更是一个两阶段处理的过程:
- "etcdctl member add"
- 在指定的peer URL上启动要添加的etcd进程
也就是说,"member add" 命令被应用时(我:这里的应用可以理解是写wal,应用状态机)会忽略这个URL,即使URL值是不合法的。当第一阶段将不合法的URL应用了之后,第二阶段甚至不能启动这个新的etcd节点,(我:因为这个节点会和老节点进行通信,验证集群的成员信息,如果不匹配的话,就不能启动)一旦集群失去了大多数活跃,就没有办法再切回到原来的成员配置了(我:因为没办法进行日志提交)。
Figure 8
在多节点的集群中也会有相同的场景。举个例子,集群有两个成员down了(一个挂了,另一个是配置配错了),另外有两个成员活着,但是它现在是需要最少3个投票去改变集群成员。
Figure 9
像上述看到的,一个简单的配置错误,可以导致整个集群进入一个不可操作的状态。在这种情况下,管理员需要手动重新创建集群通过 etcd --force-new-cluster 标志。如今etcd已经成为了一个在k8s中不可缺失的服务,甚至一个轻微的停服就可能导致很明显的对用户的影响。怎么样才能使得这些操作更简单些?除此之外,leader选举是影响集群可用性的最关键的因素:有没有可能我们可以使成员变更不改变quorum(大多数这个值的大小),这样就可以更少地产生破坏性。
Raft Learner
为了减轻上述描述的可用性间隔,Raft 4.2.1 介绍了一个新的节点状态称为"Learner",它加入集群内但不参与投票,直到它赶上了leader的日志
Features in v3.4
在添加一个learner的时候,管理员应该做尽可能少的工作。member add --learner 命令可以添加一个新的learner,这样新加入的节点就会是以一个不参与投票的节点,只是从leader收到所有的数据。也不计算在quorum中。
Figure 10
当一个learner追赶上leader的进度,learner可以被提升称为一个可投票的成员,使用member promote API,后续就成为多数派的计数之一了。
Figure 11
etcd 服务会在提升身份的请求中检查合法性去确保它的操作安全性。只有在它的日志已经追赶上leader的,learner才能被提升称为一个可投票的成员。
Figure 12
Learner在提升之前只作为一个备份节点的存在。领导权不能转给learner节点。Learner会拒绝客户端的读和写请求(client的均衡器不应该将请求路由到learner节点)。这也就意味着,learner不需要发出Read Index 请求给Leader。这些限制简化了在V3.4版本中learner的初步实现。
Figure 13
另外,etcd限制了一个集群可以有的最多的learner的数量,避免了leader复制日志过载。Learner从不会自己提升自己的身份。由于etcd提供了learner状态信息和安全性检查,集群操作者必须做最后决定是否提升learner或不提升。
Features in v3.5
让learner状态成为默认和仅有:默认一个新加入的成员的状态是learner将极大改善成员变更的安全性,因为learner不会改变quorum的大小。配置错了也可以回退,集群不会丢失大多数。
让提升为可投票身份完全自动起来:一旦learner追赶上了leader的log,集群可以自动地提升learner的身份。etcd需要用户定一个确切的阈值,一旦这个阈值需求满足了,learner提升自己成为一个可投票的成员。从用户的角度来说,"member add" 命令在今天会以同样的方式工作不过更加的安全,因为提供了learner特性。
让Learner成为一个备份的故障恢复节点:一个Learner加入是作为备份节点,并且自动地提升当集群的可用性受到影响的时候。
让Learner只读:一个Learner可以作为一个只读节点提供只读服务而从不被提升。在一个弱一致性模式下,learner只从leader接收数据,并不提供写。在不需要一致性的前提下提供读将极大减少leader的负载,但有可能会返回过期数据。在强一致性模式下,learner向leader请求read index来提供最新数据,但是仍旧拒绝写。
Learner vs. Mirror Maker
etcd 实现了"mirror maker"(镜像同步) 使用了watch 机制去持续地转发key创建和更新到其它独立的集群。一旦完成了初始的同步情况下,镜像同步就是低延迟的。Learner和镜像同步都可以被用于同步已存在的数据。然而镜像同步不保证线性一致性。在网络失去连接期间,先前的kv可能已经被废弃了,客户端需要去验证watch的响应顺序的正确性。因此镜像复制没有顺序性保证。为了低延迟(跨数据中心)以一致性为代价。使用Learner去保持所有历史的数据和它的顺序。
Appendix: Learner Implementation in v3.4
Etcd客户端给MemberAdd的API添加了一个标志,标志是为了添加一个learner。Etcd服务处理成员变更的条目的类型是pb.ConfChangeAddLearnerNode
。一旦这个命令被应用了,服务加入集群使用 etcd --initial-cluster-state=existing 标志。这个learner节点既不投票也不被算成大多数。
etcd服务一定不能将leader转给learner,因为它可能仍旧落后并且不被计算为大多数。
etcd服务限制了learner的数量:learner越多,leader就需要传送的数据越多。client可能和learner节点有交流,但learner拒绝所有的请求,除了序列化读和成员状态API。这是为了简化的一个初步实现。将来,learner可以被扩展成为一个只读的服务,持续同步映射集群的数据。客户端的负载均衡器必须提供帮助函数排除learner节点。否则,请求发送到learner可能失败。客户端同步成员信息的调用应该考虑learner节点的类型。所以应该客户端更新调用。
MemberList和MemberStatus的响应应该指明哪个节点是learner。
添加 MemberPromote API
在Raft内部,第二次MemberAdd添加learner的调用提升它成为一个可投票成员。leader维护每一个follower和learner的进展状况。如果learner还没有完成它的快照消息,拒绝提升请求。接受提升请求仅在:
- learner节点是健康状态
- learner节点已经同步到了leader或者差距在阈值之内(比如:少于1/10快照数量,意味着不太可能提升之后leader还需要发送快照给这个learner)。所有这些逻辑被hard-code在etcdserver包中,不可配配置。
Reference
- Original github issue: etcd#9161
- Use case: etcd#3715
- Use case: etcd#8888
- Use case: etcd#10114
windleaves
2020-04-04 23:08
网友评论