Raft成员变化(Membership Change)
我司高产作家唐刘老师的小猪佩奇版“深入浅出 Raft”第四弹来啦~~前几篇内容戳这里 ↓
在猪爸爸的努力下,泥坑银行终于能高效正常的运作了,但猪爸爸一直比较担心海盗岛那边的网点,因为他总是担心跨海的通讯会因为极端情况出现问题。果不其然,一个雷雨交加的晚上,海盗岛的发电站被击中,整个岛处于停电状态,海盗岛的网点没法正常工作了。虽然狗爷爷尽了很多努力,让海盗岛重新供电也花了一天时间。
第二天,猪爸爸去见了兔小姐:『兔小姐,我觉得我们不能再将网点放在海盗岛上面,因为这个岛上面的情况太复杂,很容易就因为极端天气导致网点不可用。』
『看起来是的,猪爸爸,那么我们怎么办呢?』
『我们需要再找几个安全的地方设立新的网点,顺带将海盗岛的网点移除。』
『是的,猪爸爸,可是我们要怎么做呢?直接找一个地方就对外提供服务吗?或者为了更加安全,我们直接多设置几个网点?』
『当然不是的,兔小姐,为了保证数据的安全,我们需要做很多事情,防止我们在添加网点的时候出现多个 Leader 的情况。』
『哦,好复杂,猪爸爸,你能详细解释一下吗?』
『好的,兔小姐。』
Problem
『首先我们来看现在面临的问题,为了更好的举例,我就仍然以之前选举的例子,成员 A,B,C 来举例吧,兔小姐。』
『好的,猪爸爸。』
『假设现在 A 已经是 Leader 了,我们有三个成员,这时候要加入两个新的成员,D 和 E,因为 D 和 E 在加入的时候,其实是会知道之前的 A,B,C,所以我们需要处理的问题就是如何让 A,B,C 能知道新加入的 D 和 E。』
『恩,这是一个问题。』兔小姐沉思道。
『最简单的做法,就是我们有另外一个全局协调者,先依次告诉 A,B,C 休息一下,别开会了,第二天 D 和 E 要加入,这样大家都知道了最新的成员信息,然后第二天,就是五个成员开始竞选 Leader 了。这个方式最好,但有一个问题就是一段时间会议没法进行,对我们银行来说,也就意味着不能正常对外提供服务。』
这里, 其实就是一个集群成员变更常见的问题,当我们添加或者删除节点的时候,如何让其他的节点知道成员变更了。最通常的做法,可能就是通过一个全局的协调器,譬如 zookeeper 或者 etcd 这种的,做一个 Two Phase(2 PC) 的变更,但这样其实是有问题的,先不说 2 PC 一些 corner case 需要处理,整个过程还可能会导致暂时的服务不可用,虽然这个时间在多数情况下面可能比较短,所以 Raft 这边采用了另外一种做法,我们继续说明。
Add/Remove one Node
『那怎么办呢?』兔小姐继续问道。
『为了保证在进行成员变更的时候,仍然能正常工作,我们可以这样做。不知道你还记不记得,当一个 Leader 被选出来之后,所有的操作都是由 Leader 进行的。兔小姐?』
『当然记得,猪爸爸。』
『好的,所以这里,如果我们要添加新的成员,我们就可以直接告诉 Leader,然后让 Leader 再去告诉其他的 Follower,当这条消息被大多数成员确认了,我们就知道新的成员已经加入了。』
『这方法不错,我们也不需要额外在用一个外部的协调人员了。』
『是的,兔小姐,但这个方法也有一个问题,就是一次只能进行一个成员的变更,不能进行多个。』
『哦,这是怎么回事?』兔小姐困惑的问道。
『继续上面的例子,A,B,C,A 现在是 Leader,然后我们告诉 A 要加入了 D 和 E, A 给 B 和 C 发了消息,当我们确认这条消息被 committed 之后,A,B,C 都可能会 apply 这条消息,并且知道了 D 和 E 已经加入了。』
猪爸爸稍微休息了一会,继续说道:『那么,这时候,就有可能出现一种情况,C 先 apply 了这条消息,然后 C 就知道了 D 和 E,但 A 和 B 因为还没 apply,不知道 D,E。然后 C,D,E 三个就可能选出新的 Leader,譬如 E,因为根据我们之前的讨论,五个成员只需要三个成员同意,但这个时候,A 还认为自己是 Leader,于是我们这里出现了两个 Leader。』
『那我们如何去防止这样的事情发生呢?』
『很简单,兔小姐,我们一次只能进行一个成员的变更。譬如上面的例子,我们只能先加入 D,然后等 D 加入成功之后,才能加入 E。』
『那这样为什么就没有问题呢?』
『我们继续上面的例子,C 仍然先 apply 知道了 D,但 C 和 D 这边知道现在是四个成员,也就是至少需要三个成员投票才能选出 Leader,而 A 和 B 这时候因为根本还不知道 D,而且 A 仍然还是 Leader, 所以 A 和 B 都不可能给 D 投票。也就是说,只要我们能保证每次成员添加的时候,新的成员集合仍然跟老的成员集合大部分的成员是重合的,那么就不会有任何问题。』
『恩,我想我大概明白了,那么对我们来说,现在要做的就是先移除掉海盗岛的网点,当海盗岛的网点移除成功了,我们在加入一个新的网点,是吧,猪爸爸?』
『非常正确,兔小姐!』
在 Raft 的博士论文里面,当 Leader 收到 Configuration Change 的消息之后,它就将新的配置(后面叫 C-new,旧的叫 C-old) 作为一个特殊的 Raft Entry 发送到其他的 Follower 上面,任何节点只要收到了这个 Entry,就开始直接使用 C-new。当 C-new 这个 Log 被 committed,那么这次 Configuration Change 就结束了。当在 TiKV 以及 etcd 里面,我们并没有使用这种方式,只有当 C-new 这个 Log 被 committed 以及被 applied 之后,节点才知道最新的 Configuration 的情况。这样做的方式是比较简单,但需要注意几点:
当 Log 里面有一个 Configuration Change 还没有被 committed,不允许接受新的 Configuration Change 请求,主要是为了防止出现多 Leader 情况。
如果只有两个节点,需要移除一个节点,如果 Leader 在发起命令之后,另一个节点挂了,这时候系统没法恢复了。
Snapshot
好了,我们继续回到小镇银行这边,兔小姐跟猪爸爸选好了新的场地 - 森林小径,然后就准备开始海盗岛的网点替换工作了。但这时候,兔小姐突然想到一个严重的问题:
『猪爸爸,我们是可以建立新的网点,但这边新的网点现在可是完全没有数据的呀。』
『这个问题非常的好,兔小姐,我完全考虑到了。一个最简单的做法,因为我们每笔交易都有记录,我们将记录从最开始以此重新发给新的网点不就可以了。』
『这办法很不错,但我们银行现在已经开业这么久了,记录都有很多了,而且我们还在不停的交易,如果重头这么传输,不知道要多久呀!』兔小姐担心的问道。
『是的,兔小姐,很佩服你能想到这个问题。所以这里我们需要的是一套 snapshot 机制。』猪爸爸回答道。
『Snapshot,这个是什么?』
『就是镜像,兔小姐。你可以这样理解,虽然一个客户进行了很多笔交易,但在某一个时间点上面,客户的金钱总数就是一个固定的值。』
『这个我能理解,猪爸爸。』
『所以,我们只需要在一个时间点上面,记录下客户当前的金钱总数,然后将这条数据发送到新的网点,然后新的网点收到之后,直接在金库里面给对应的客户设置好相应的金钱。然后对于这个时间点后面新的交易,我们还是按照之前交易记录发送的方式来同步了。』
『哦,猪爸爸,听起来有点糊涂,你能在详细解释一下吗。』
『好的,兔小姐,我这里简单举一个例子,假设我们就一个客户,这个客户进行了 100 次交易,也就是我们现在有 100 条交易记录,100 次交易之后,客户的金钱是 100 块钱,我们只需要告诉新的网点这个客户是 100 块钱就行了。然后当这个客户继续进行第 101 次交易的时候,我们仅仅需要将 101 这次的交易记录发给新的网点就可以了。』
『哦,我明白了,猪爸爸。』
Snapshot 虽然简单,但需要注意,假设 3 个节点,然后新加入了一个节点,如果 Leader 在给新的 Follower 发送 Snapshot 的时候,另一个 Follower 当掉了,这时候整个系统是没法工作了,只有等 Follower 完全收完 Snapshot 之后才能恢复。为了解决这个问题,我们可以引入 Learner 的状态,也就是新加入的 Learner 节点是不能算 Quorum 的,它不能投票。只有 Leader 确认这个 Learner 接受完了 Snapshot,能正常同步 Raft Log 了,才会考虑将其变成正常的可以 Vote 的节点。
Joint Consensus
虽然上面一次进行一个成员变更的方式已经能在生产环境中满足大部分情况,但还有一种 corner case 我们是没有办法解决的。假设现在我们有 3 个 IDC,用 A,B,C 来表示,每个 IDC 里面有两台机器,就是 A1,A2,B1,B2,C1,C2。现在有一个 Raft 副本在 A1,B1,C1 上面,这时候,如果我们发现 A1 压力比较大,要将副本转移到 A2 上面,那么有两种办法:
移除 A1,增加 A2
增加 A2,移除 A1
但无论是上面哪一种方法,都会有风险,譬如第一种,当 A1 移除之后,如果 B1 或者 C1 当掉,那么整个集群是不可用的。而对于第二种,A2 增加之后,如果这时候整个 IDC A 当掉,那么整个 Raft 集群也是不可用的了。也就是说,我们虽然将数据放在了 3 个 IDC 上面,但在一些情况下面,如果一个 IDC 整个当掉,都可能引起 Raft 集群不可用。
我们可以通过 Learner 的方式缓解这个问题,也就是先增加 A2,但 A2 是 Learner,只有 A2 完全追上了,我们才将 A2 给变成 Voter,然后在移掉 A1。但这个方式只是能减少不可用的概率,并不能完全防止,所以最好的做法就是支持 Joint Consensus 算法。
这个算法其实比较简单,相对于上面的一次成员变更的算法,它只引入了一个过渡状态,叫做 joint consensus。当一个 Leader 收到成员变更的请求的时候,他首先会将 C-old 和 C-new 都放在 joint consensus 里面(我们叫做 C-old-new),作为一个 Raft Log 发送给其他的 Followers。当节点收到 Log,不需要等待 Log 被 committed,就可以使用最新的 C-new 配置了,但这时候,仍然只有 C-old 里面的集群能进行 Vote。如果这时候 Leader 当掉了,新选出来的节点 要不在 C-old 里面,要不在 C-old-new 里面,因为我们前面没约定 C-old-new 这个 Log 必须 committed。但无论是哪一种 Leader,C-new 这边的集群都不可能单边决策的。
当 C-old-new 被 committed 之后,就进行了 joint consensus 状态,在这个状态里面:
Log 会被复制到所有在两个 configurations 里面的节点上面;
在两个 configuration 里面的节点都可能被选为 Leader;
但只有 C-old 里面 majority 和 C-new 里面 majority 都同意,才能选出 Leader 和进行 Log 提交。
当进入 joint consensus 之后,Leader 就可以再次提交一个新的 C-new Raft Log,仍然是只要其他节点收到了这个 Log,就可以使用新的 Configuration 了,当 C-new 这个 Log 被 committed 了,那么 C-old 就没用了,不在 C-new 的节点就可以直接关闭。这套流程就能保证在任意时候,C-old 和 C-new 不会出现单边投票的情况。
虽然 joint consensus 很强大,但现在用的最多的仍然是一次成员变更的方法,毕竟很简单,而 joint consensus 我只在 LogCabin 中看到过,所以这里并没有很详细的介绍。一些 corner case 的处理大家可以直接去看论文了。
那没有 joint consensus,一些极端的 corner case 怎么办呢?可能就先忍忍呗,或者使用 5 副本,甚至用 7 副本。
小结
成员变更我认为算是 Raft 里面最难的概念,尤其是在 Raft 的 Paper 里面,重点就提到的是 joint consensus 算法,其实比较让人难以理解。这里其实就体现了一个工程上面的取舍,虽然我知道理论上面 100% 的事情怎么做,但为了更加简单,我可以稍微放低一点要求。
TiKV 和 etcd 现在都是没有用 joint consensus 的,但我们现在在开始添加 Learner,后面如果真的遇到了其他的 corner case,会不会考虑一下,没准也不是不可能的事情
Raft成员变化(Membership Change)的更多相关文章
- 代码的坏味道(10)——发散式变化(Divergent Change)
坏味道--发散式变化(Divergent Change) 发散式变化(Divergent Change) 类似于 霰弹式修改(Shotgun Surgery) ,但实际上完全不同.发散式变化(Dive ...
- Java学习之多态---类成员变化
类成员 一.成员变量 编译时:变量(f)所属类(Fu)中是否有成员变量,有:编译成功,没有:编译失败 运行时:变量(f)所属类(Fu)中是否有成员变量,运行该类(Fu)中的成员变量 class Fu ...
- Raft 一致性算法论文译文
本篇博客为著名的 RAFT 一致性算法论文的中文翻译,论文名为<In search of an Understandable Consensus Algorithm (Extended Vers ...
- Raft协议--中文论文介绍
本篇博客为著名的 RAFT 一致性算法论文的中文翻译,论文名为<In search of an Understandable Consensus Algorithm (Extended Vers ...
- 解读Raft(四 成员变更)
将成员变更纳入到算法中是Raft易于应用到实践中的关键,相对于Paxos,它给出了明确的变更过程(实践的基础,任何现实的系统中都会遇到因为硬件故障等原因引起的节点变更的操作). 显然,我们可以通过sh ...
- Raft论文《 In Search of an Understandable Consensus Algorithm (Extended Version) 》研读
Raft 论文研读 说明:本文为论文 < In Search of an Understandable Consensus Algorithm (Extended Version) > 的 ...
- 学习Raft算法的笔记
Raft是一种为了管理日志复制的一致性算法.它提供了和Paxos算法相同的功能和性能,但是它的算法结构和Paxos不同,使得Raft算法更加容易理解并且更容易构建实际的系统.为了提升可理解性,Raft ...
- 分布式:2PC,3PC,Paxos,Raft,ISR [转]
本文主要讲述2PC及3PC,以及Paxos以及Raft协议. 两类一致性(操作原子性与副本一致性) 2PC协议用于保证属于多个数据分片上的操作的原子性.这些数据分片可能分布在不同的服务器上,2PC协议 ...
- 从分布式一致性到共识机制(二)Raft算法
春秋五霸说开 春秋五霸,是指东周春秋时期相继称霸主的五个诸侯,“霸”,意为霸主,即是诸侯之领袖.典型的比如齐桓公,晋文公,春秋时期诸侯国的称霸,与今天要讨论的Raft算法很像. 一.更加直观的Raft ...
随机推荐
- Redis 很屌,不懂使用规范就糟蹋了
这可能是最中肯的 Redis 使用规范了 码哥,昨天我被公司 Leader 批评了. 我在单身红娘婚恋类型互联网公司工作,在双十一推出下单就送女朋友的活动. 谁曾想,凌晨 12 点之后,用户量暴增,出 ...
- 洛谷 P3600 - 随机数生成器(期望 dp)
题面传送门 我竟然独立搞出了这道黑题!incredible! u1s1 这题是我做题时间跨度最大的题之一-- 首先讲下我四个月前想出来的 \(n^2\log n\) 的做法吧. 记 \(f(a)=\m ...
- IDEA+maven+javafx(java 1.8)入坑记录
序 好久没写博客了,主要是因为懒,写博客真的是个难坚持的事.但今天登上来看了看,之前记录ctf写的wp竟然点击量这么多了,突然让我有了继续写下去的动力. 这段时间遇到了好多事,中间也有想过写几篇文章记 ...
- 使用dumi生成react组件库文档并发布到github pages
周末两天玩了下号称西湖区东半球最牛逼的react文档站点生成工具dumi,顺带结合github pages生成了react-uni-comps文档站, 一套弄下来,感觉真香,现在还只是浅尝,高级的特性 ...
- A Child's History of England.40
Excommunication was, next to the Interdict I told you of at the close {end} of the last chapter, the ...
- day20 系统优化
day20 系统优化 yum源的优化 yum源的优化: 自建yum仓库 使用一个较为稳定的仓库 # 安装华为的Base源 或者使用清华的源也可以 wget -O /etc/yum.repos.d/Ce ...
- JavaScript设计模式,单例模式!
单例设计模式:保证一个类仅有一个实例,并且提供一个访问它的全局访问点.有些对象只需要一个,这时可用单例模式. 传统的单例模式 和new 创建对象的调用不一样 调用者要调用xxx.getInstance ...
- 【leetcode】122.Best Time to Buy and Sell Stock II(股票问题)
You are given an integer array prices where prices[i] is the price of a given stock on the ith day. ...
- keil 生成 bin 文件 gd32为例
fromelf --bin --output .\update\GD32F4xZ.bin .\Output\GD32450Z_EVAL.axf代表使用的keil内的工具代表输出公式,..表示: 输出 ...
- Maven pom.xml报错解决
用Maven建了一个web工程,总是在pom.xml头的地方报错: 大概是: Original error: Could not transfer artifact org.hamcrest:hamc ...