分布式基石|最难 paxos 和最易 raft ?
扫描二维码
随时随地手机看文章
什么是一致性协议?
注意,今天是大白话随便聊聊,目的是直白的了解 raft 是什么,不用太抠理论定义。什么是一致性协议?字面理解就是让某些东西保持一致的协议嘛。什么是一致?大白话就是内容完全相同呗。以存储场景举例,假设有三个磁盘文件,大小为 1M ,如果三个文件 1M 的数据都完全相同,那么这可以说这文件的数据是一致的。一致性还分了不同的等级,如线性、因果、最终一致性等等,而且如果站在不同的系统层面来看,承诺的一致性也会有所不同。这些今天都不重要,重要的是我们知道了:一致性协议就是用来达到一致的协议呗。有两个最出名的一致性协议:paxos 和 raft 。数学上已经严格证明了 paxos 的正确性,只要严格遵守它协议的约束,就能保证在分布式的恶劣环境下多副本数据的一致。我们来看一下吧!
paxos 协议
paxos 是 Leslie Lamport 大神于 1990 年提出的一致性协议。它解决的问题是一个分布式系统如何就某个值(决议)达成一致。
划重点:paxos 协议本质是确定一个值。论文《The part-time parliarment》提到的 paxos 里面有两个重要角色:
- Proposer:提议发起者
- Acceptor:提议接受者
第三定律:如果一轮编号为 Bbal 的投票,多数派中任意一位成员曾投过 Bbal 编号小的票(B'),那么 Bdec == B'dec;
上面就是 paxos 最核心的内容,但是说实话,每一个字都看得懂,但是连起来就不知道啥意思?
paxos 到底能做啥?这个我们存储系统有啥关系?它为啥那么难懂?paxos 难就难在于它没告诉大家,这个东西能用来做啥,映射不到现实,就无法产生共鸣。我们先接受一个事实:paxos 的本质是确定一个值,且一旦这个值确定之后,后续无论怎么投票,无论发生什么,这个值保持不变。那我就比以前更懵逼了!怎么越说越糊涂了了,说好的做一个分布式存储服务吗?存储服务应该允许可以写入任何数据,且可以 Update 的嘛。确定一个不能变的值有啥用?
paxos 的工程化
我们下面尝试将 paxos 工程化,将它具现化到现实的工程实现。
1 确定一个值,有啥用?
回到最开始的问题,确定一个值对我们有啥用?我们来简要发散下 paxos 工程化的思路。paxos 本质:确定一个值,现在把这里面参与的角色打包起来,Proposer,Acceptor,Proposal 等等组成的抽象的集合:paxos instance,称为 paxos 实例:划重点:每个实例必须是完全独立,投票互不干涉,即可。一个 instance 确定一个值,多个 instance 确定多个值。这些值不断的被确定(永不更改),形成了一个值序列,这有啥用?
2 确定多个值有啥用?
接着上面,我们现在有了一系列永远无法被修改了值序列,有啥用?存储服务的基本特点是允许存储任何数据,并且能够增删改。哪还有啥用?这一个个值序列像不像一个东西:日志!这个跟 rocksdbdb,leveldb 的 wal 日志是不是差不多意思了?我们应用这些日志就能得到一致性的输出。所以我们还缺个啥?状态机嘛。
3 加个状态机就起飞了
什么是状态机?状态机全称为有限状态机。它接收条件的触发,由一种状态转变为新的状态。初始状态相同,输入的一系列事件相同,那么它最终的状态一定相同。这可太常见了,比如 rocksdb,leveldb 等等 lsm 存储,它们数据先写 append log ,通过重放日志到达的系统状态一定是一致的。这种状态机的应用模式可不仅限于存储服务。到这,我相信童鞋们已经很豁然开朗了,只要我们通过 paxos 来产生分布式一致的有序的操作日志,加上状态机的配合,实现一个分布式存储服务必然不是问题。通过不停的确定一个个值,形成一个有序的操作系列,配合状态机的应用,这,就是 paxos 的工程化方向。
4 活锁的问题怎么解决?
对于 paxos 来说,Proposer 和 Acceptor 角色是可以重叠的,每个节点既可以是 Proposer,也可以是 Acceptor ,或者两者都是。这带来了非常大的灵活,每一个 Proposer 都可以递交协议(写入数据),但由于最终只能确定一个值,那么这会导致非常多的无效功,这期间是使用类似乐观锁来解决那些冲突的提议。比如说,A 刚递交一个提案,B 就递交一个新提案导致 A 的提案被否定了,然后 A 又迅速递交一个提案,形成了一种类似活锁的状态,这时间就浪费了呀。怎么解决?问题根因在于可以提案的点太多,大家都是平等的。那么统一声音才能解决这个问题。于是Leader 就应运而生。通过某种方法指定一个节点为 Leader ,只有一个节点能递交提案,这样就解决了混乱问题,效率提随之提升(这就是 Multi-Paxos )。
5 paxos 工程化小结
小结一下,如果要将一个 paxos 工程化落地,衍生了哪些东西:
- paxos 本质是确定一个值,把参与确定这个值的角色打包称为一组实例( paxos instance );2.不同实例之间决议互不干扰。多组 paxos 实例确定多个值,形成一组操作序列,也是就日志 ;
- 日志 状态机 可以成为任何有意义的工程系统;
- 为了解决递交提案混乱可能引发的效率问题(比如活锁),可以通过指定 Leader 角色来解决;
raft 协议
终于到了 raft 协议,raft 的论文开篇就是这么一段话:
Raft is a consensus algorithm for managing a replicated log. It produces a result equivalent to (multi-)Paxos, and it is as efficient as Paxos, but its structure is different from Paxos;raft 证明和 paxos 等价,raft 是一种日志复制的一致性算法。看懂了吗?raft 的着眼点就是 日志 状态机 的方向。划重点:raft 天生就是 paxos 协议工程化的一种样子。如下图:图里交代了关键模块:
- 客户端( Client ):就是用户嘛,写入数据的就是它喽;
- 一致性模块( Consensus Module ):负责写入 log,并且把 log 复制到其他节点;
- 状态机( State Machine ):输入 log ,推进变更系统状态;
- raft 就是管理日志复制的算法;
- 日志 状态机 就能落地一个一致性的系统应用;
- 集群角色有分类,Leader 作为唯一的写入点,所有日志复制是 Leader 到 Follower 单项传输;
- Leader 的选举;
- 日志的复制;
- 正确性的保证(约束条件);
- 选出一个 Leader ;
- 把 Leader 的日志复制分发到 Follower 节点;
- 数据怎么读写?
- 节点扩缩容怎么搞?
1 Leader 选举
角色转变:简单看下 raft 协议中关于 Leader 选举的部分。下面是角色转化图,非常清晰:图里至少能得到这么几点知识点:
- 系统开始每个节点都是从 Follower 角色开始;
- 定时器超时之后,角色转变为 Candidate ,开始竞选 Leader;
- Candidate 如果获得多数人的支持,那么选举成功,角色转变为 Leader 。如果选举失败,那么退为 Follower ;
- 无 Leader 状态(选举中);
- 正常状态( Leader );
- 任期编号;
- 当前日志的最新位置;
- 先比 term ,谁更大谁就新;
- 举个例子,Follower 节点保存的任期是 4,Candidate 发过来的是 3 ,这种就直接拒绝了;
- 如果任期相同,那么就比较 index ,index 谁更大就新;
- 举个例子,对面发过来的 index 是 7,我本地是的 8 ,那么就多说了,拒绝;
2 日志复制
日志复制有几个特点:
- 日志传输为单向传输,Leader 到 Follower ;
- Leader 永远不会改写或者删除自己的日志,永远只做 Append ;
- 日志内容一切以 Leader 为主,哪怕是强制覆盖 ;
- x = 4
- y = 7
- a 时刻:Leader 为 S1( 黑框的为 Leader ),它有着最新的日志 index:2 ,虽然最新的 index:2 并没有 committed(复制到多数),只复制到了 S2 ;
- b 时刻:S1 挂了,S5 被选举为 Leader ,任期为 3 ,并且 Client 还递交了一个写入;
- c 时刻:S5 挂了,S1 被重新选举为 Leader ,任期为 4,这个时候它复制日志,把 index:2 的日志复制给了 S1,S2,S3 ,这是满足了 quorum (但注意了,这个系统千万不能认为 commit 了,且往后看)。并且 Client 还递交了一个写入在 index:3 的位置;
- d 时刻:S1 挂了,S5 被重新选举为 Leader(S2,S3,S4 都会投票),于是把 index:2 的日志强制覆盖到所有节点;
- e 时刻:这个时刻是一种假设,假设说,S1 在 c 时刻的时候在挂掉之前把任期 4,index:3 的日志复制到多数节点,那结果又不一样了。这种场景系统可以认为 index:3 被 commit 了,index:2 则是被间接 commit 了;
3 状态机
这部分其实是最简单的,状态机做的事情我们叫做 apply 。apply 的内容则是各个业务自行解释,举个例子,如下的日志,这是一个典型的 kv 系统的样子:日志 apply 完之后,系统状态为:
x = 4
y = 7
划重点:日志里面的内容由业务自行解释,raft 只保证日志复制是完全一致的。4 Propose 递交
用户的入口就是从递交 Propose 开始,由 Leader 接收用户请求,然后封装成日志的样子,经过了 commit( 确定这个值 )之后就能对外承诺。思考一个小问题:集群只有一个 Leader ,如果请求发给了 Follower 呢?难不成 Client 还要专门记录谁是 Leader ?也没关系,Follower 可以透明转发给 Leader 。Leader 处理好之后,回应即可。划重点:还是那句话,只由 Leader 来发起,就算发给了 Follower ,请求也会转发 Leader。
5 成员变更
成员变更一般分为两种场景:
- 单节点变更
- 多节点变更
- S1,S2 认为 S1 是 Leader,在原有 3 节点的集群中,满足多数,合法 ;
- S3,S4,S5 认为 S3 是 Leader ,在新的 5 节点集群中满足多数,合法;
- 最开始集群配置( S1,S2,S3 ),我们暂且叫做 C_old ;
- 递交两条集群变更的日志,Add S4,Add S5 ,Leader 向所有 S1,S2,S3 广播日志;
- 所有节点( S1,S2,S3 )收到这两条日志,则代表这两条日志被 commit 了,于是 apply 这两条日志,apply 的行为:集群配置变更为( S1,S2,S3,S4,S5 )