1. 前言
常见的一致性协议主要有:PaxOS、Raft、ZAB、PacificA等。同PaxOS,Raft也不考虑拜占庭将军问题(Byzantine failures,注:比特币采用工作量证明PoW和股权证明PoS解决了拜占庭将军问题)。
2. 名词
Distributed Consensus | 分布式一致性 |
---|---|
Distributed Consensus Algorithms | 分布式一致性算法,PaxOS是分布式一致性算法的鼻祖,绝大多数的实现要么基于PaxOS,要么受PaxOS影响(如Zookeeper)。 |
PaxOS | 就分布式系统中的某个值(在PaxOS中叫决议,即Proposer)达成一致的算法。PaxOS是分布式一致性协议的鼻祖,在Google的三大件未推出之前少为人知,1989年莱斯利·兰伯特提出。Leslie Lamport是大神的英文名,出生于1941年2月7日纽约。兰伯特目前就职于微软研究院,他也是排版系统LaTeX的作者,2008年获得冯诺依曼奖,2013年获得图灵奖,麻省理工学院学士和布兰戴斯大学博士。最早的实现为Google内部使用的Chubby。 |
Raft | 木筏协议,一种共识算法,旨在替代PaxOS,相比容易理解许多。斯坦福大学的两个博士Diego Ongaro和John Ousterhout两个人以易懂为目标设计的一致性算法,2013以论文方式发布。由于易懂,不从论文发布到不到两年的时间,即出现大量的Raft开源实现。为简化PaxOS的晦涩,它采用分而治之的办法,将算法分为三个子问题:选举(Leader Election)、日志复制(Log Replication)、安全性(Safety)和集群成员动态变更(Cluster Membership Changes)。 |
ZAB | Zookeeper采用的一致性算法,全称“ZooKeeper Atomic Broadcast”,即Zookeeper原子广播协议,实为Zookeeper版本的PaxOS。 |
PacificA | 微软2008年提出的日志复制协议,与PaxOS和Raft不同,PacificA并不提供Leader选举,因此需要结合PaxOS或Raft,分布式消息队列Kafka的ISR算法类似于PacificA。小米开源的KV系统Pegasus基于PacificA实现。 |
Viewstamped Replication | 简称VR,最初被提出是作为数据库中的一部分工作,2012年作为单独的分布式共识算法再次发表。 |
Log Replication | 日志复制 |
Leader Election | 领导选举 |
Log | 代表一个修改操作 |
Entry | (日志)条目(代表一个修改操作),Log在Raft中的实体,即一个Entry表示一条Log |
Term | 任期(可简单理解成租约,但稍有不同),是一个从0开始递增的整数值,每次发起选举时增一,最终所有节点的Term值是相同的。等同于ZAB协议中的zxid的高32位,即ZAB中的Epoch。 |
Index | 和Term密切相关,Term针对的是选举,而Index针对的是一条Log,它的值也是从0开始递增。同样最终所有节点的Index也相同,如果两个节点的Term和Index均相同,则这两个节点的数据是完全一致的。等同于ZAB协议中的zxid的低32位。 |
Node | 节点 |
Single Node | 单节点 |
Multiple Nodes | 多节点 |
Leader | 领导(唯一接受修改操作的) |
Follower | 跟随者(投票参与者) |
Candidate | 候选人(唯一具有发起选举的) |
Vote | 投票 |
Heal | 恢复,比如恢复网络分区(Heal the network partition) |
Step Down | 下台,当一个Leader发现有更大的Term时,自动降为Follower |
Logic Clock | 逻辑时钟,由Lampert提出,原因是分布式中无法使用精准的时钟维护事件的先后顺序,方法是为每个消息(或日志)加上一个逻辑时间戳。在ZAB协议中,每个消息均由唯一的zxid标识。zxid是一个64位无符号整数值,被分成两部分:高32位是Epoch,低32位是一个从0开始的递增值。类似于Raft的Term,每次选举新Leader,Epoch值增一,但同时zxid值清0。而Raft也类似,实际也是两部分组成,Term对应于Epoch,Index对应于zxid的低32位。 |
Quorum | 法定人数 |
Brain Split | 脑裂,同一个网络的节点被分成了两个或多个不互通的部分 |
3. 什么是分布式一致性?
多个节点(Multiple Nodes)的数据完全相同,即为分布式一致性。但因为多个节点通过网络互联,并不一定时刻可用,而服务不能因为某些节点(特别是少数节点)不可用时,导致整个系统不可用。
Raft将节点分成三种角色:Leader、Follower和Candidate,一个节点可为三种角色中的任意一种,但同一时刻只会为其中一种角色。正常情况下,只有Leader和Follower两种角色的节点,只有在选举时才会出现Candidate角色节点,而且可能多个节点同时处于Candidate角色同时竞争选举。
4. Raft选举
4.1. 什么是Leader选举?
Raft协议中,一个节点有三个状态:Leader、Follower和Candidate,但同一时刻只能处于其中一种状态。Raft选举实际是指选举Leader,选举是由候选者(Candidate)主动发起,而不是由其它第三者。
并且约束只有Leader才能接受写和读请求,只有Candidate才能发起选举。如果一个Follower和它的Leader失联(失联时长超过一个Term),则它自动转为Candidate,并发起选举。
发起选举的目的是Candidate请求(Request)其它所有节点投票给自己,如果Candidate获得多数节点(a majority of nodes)的投票(Votes),则自动成为Leader,这个过程即叫Leader选举。
在Raft协议中,正常情况下Leader会周期性(不能大于Term)的向所有节点发送AppendEntries RPC,以维持它的Leader地位。
相应的,如果一个Follower在一个Term内没有接收到Leader发来的AppendEntries RPC,则它在延迟随机时间(150ms~300ms)后,即向所有其它节点发起选举。
采取随机时间的目的是避免多个Followers同时发起选举,而同时发起选举容易导致所有Candidates都未能获得多数Followers的投票(脑裂,比如各获得了一半的投票,谁也不占多数,导致选举无效需重选),因而延迟随机时间可以提高一次选举的成功性。
4.2. 选举的实现
在Raft中,和选举有关的超时值有两个:
选举超时(Election Timeout) | Follower等待成为Candidate的时间,为随机时间,随机范围为150ms~250ms。如果在超时之前收到了其它的AppendEntries,则重置选举超时重新计时,否则在选举超时后,Follower成为Candidate,投票给自己(Votes for itself)并发起新的选举任期(Term),发送RequestVote给所有其它节点,以请求投票给自己。如果在这个Term时间内仍然没有获得投票,则重置选举超时,以准备重新发起选举。而一旦获得多数节点的投票,则成为Leader。成为Leader后,定期性的发送AppendEntries给所有Followers,以维持Leader地位。发送AppendEntries的间隔叫作心跳超时。 |
---|---|
心跳超时(Heartbeat Timeout) | Follower会应答每一个AppendEntries,一个选举任期(Election Term)会一直存在,直到有新的Follower成为Candidate。Leader节点通过定期发送AppendEntries维护自己的Leader地位,定期的间隔时长即为心跳超时时长。在Raft中,心跳方向是:Leader -> Follower。 |
如果一个Follower在一个Term时间内没有收到AppendEntries(并非要求是来自Leader的AppendEntries,其它Candidates也一样有效),则它成为Candidate,并在等待选举超时(Election Timeout)后发起选举。
要求获得多数节点的投票,可以保证每个任期(Term)内只有一个Leader。如果两个Candidates同时发起选举,则会分割投票,这样需要再重新发起选举。Raft设计“选举超时”,可以降低两个或多个Candidates同时发起选举的概率,减少连续重选举。
Redis的选举有个借鉴意义,在Redis集群中,不同节点可有不同权重,权重大的优先获得发起选举几率。在Redis中,权重是指复制节点数据的新旧程度,而对Raft来说可考虑机架机房等作为权重。
4.3. Term和Lease比较
Raft中的Term可以简单看成Lease(租约),但概念上仍然是有区别的。Lease通常是一个节点向一个中心化节点请求,而Term是无中心化的。
如果没有发生选举超时,则Term的值不会发生变化,否则至少增一。
4.4. 选举图示
每个节点的Term值初始化为1(圆圈中的数字),每个节点选举超时时间为一个150ms~300ms的随机值(实际实现可以调整,但这个值是发明者测试得到的优值,详情请参见两位斯坦福大学教授的论文):
节点S2最先发起选举成为Leader,在发起选举之前,它需要先自增自己的Term值,因此Term值由1变成2,同时其它节点的Term值也需要更新为比自己大的Term值(如果一个Leader遇到一个更大的Term,它需要主动降为Follower):
人工将节点S1和S2停掉,这时两者不可用。这会导致其它正常的节点S3、节点S4和节点S5在选举超时时间内收不到来自Leader的心跳(实为AppendEntry),因此会触发节点S3、节点S4和节点S5发起选举(究竟哪个节点发起选举,视哪个节点最先达到选举超时):
因为节点S2的数据处于未提交状态,因为它的已提交Index值要小于S3和S5,它没有最新的数据,不可能成为新的Leader。新的Leader必然在S3和S5中产生。先强制重置S2的选举超时时长,以让出优先发起选举:
虽然S3和S5均有最新的数据,但因为S3的Term小于S5的Term,因此只有S5才能成为Leader,这样最新的Term值将为5 1为6:
这个时候恢复节点S1,Term值是否会改变?
一个Leader节点,如果遇到比自有更大的Term值,则主动放弃Leader身份,并使用原Term替代自己的Term,然后转变为Candidate重新开始选举。
非Leader节点也是一样的,只要遇到比自己大的Term,都会使用更大的Term替代自己的Term。这样能保证节点均能正常通信时,Leader节点总是持有最大的Term。
4.5. 选举总结
能成为Leader的条件:
1) 有最大的Term;
2) 如果Term相同,则有最大的Index;
3) 如果Term相同,Index也相同,就看谁最先发起;
4) 最先发起者也不一定成为Leader,还得看谁最先获得多数选票。
一个Leader主动放弃Leader条件:
1) 遇到比自己更大的Term,这个时候会转为Follower。
5. Raft日志复制
5.1. 什么是日志复制?
Leader收到一个修改请求后,先将该请求记录到本地日志,这条日志在Raft中叫作Enry,此时处于未提交状态(Uncommitted),然后复制Entry给所有的Followers。当多数Followers节点确认已完成该Entry的写入后,Leader本地提交该Entry,响应客户端成功,这个过程叫日志复制(Log Replication)。如果一个Entry不能复制到多数节点,则该Entry状态一直为未提交,如果发生Leader转换,有可能被覆盖。
5.2. 日志复制的实现
正常的复制不需要理解,主要看异常时的复制处理。让节点S5成为Leader,然后停掉节点S2、S3和S4,然后客户端再请求写操作,但此时由于写操作不能得到多数节点的确认,所以无法提交(不能应用到状态机中):
先停掉节点S1和S5,再恢复节点S2、S3和S4,并让节点S3成为Leader,然后客户端发起一笔写操作。让写操作在三个节点S2、S3和S4中得到确认,这样该笔写操作为已提交状态(Committed),可以看到变成如下状态了。显然节点S1和节点S5上存在了两笔脏数据:
恢复节点S1:
停止节点S3,构造条件让S1成为新的Leader:
5.3. 脑裂时的复制
网络分裂时,出现两个Leader:
具有多数节点的一方仍然能够继续,但少数一方已不可用,这个时候从节点B读不到最新的数据:
当网络恢复后,节点B发现更大的Term,自动从Leader将为Follower,同时回滚未提交的Entries,集群又重新实现一致。
6. 概念对比
Raft | PaxOS (Multi PaxOS) | ZAB | PacificA | Viewstamped Replication |
---|---|---|---|---|
获得多数同意 | 获得多数同意 | 获得多数同意 | 需所有都同意 | 获得多数同意 |
强Leader(领导),总是包含了最全最新的日志,日志无空洞,随机化简化Leader选举(Redis也采用了这个方法,并且加了节点权重)。 | Proposer(提议者),允许有多个Proposer,并且不一定最包含了最全最新的日志,日志可存在空洞 | 强Leader(领导) | Primary(主) | Primary |
Follower(跟随者) | Acceptor(接受者) | Follower(跟随者,参与投票) | Secondary(副),接收Primary的心跳,一个Primary对多个Secondary | Backup |
Candidate(候选者) | Learner(学习者) | Looking(竞选状态) | ||
Observing(观察者,不参与投票,只同步状态) | ||||
Entry | 达到一致的叫Value(即决议),达到一致前叫提议 | 类似PaxOS | ||
Term(任期) | Ballot(选票) | Epoch(纪元),为txid的高32位(注:Redis中也叫Epoch,作用类似) | View | |
Index(序号) | Proposal Number | txid的低32位 | Serial Number |
7. 共性探讨
所有的并不是完全孤立的,而是存在很多共性。
7.1. PacificA和HBase和Kafka
PacificA和HBase均依赖于PaxOS这样的设施,HBase依赖于Zookeeper,PacificA同样依赖于类似的配置管理服务。
如同HBase依赖Zookeeper来发现Keys的位置信息,PacificA依赖配置管理服务发现分片的位置。
Kafka其实也类似,早期Kafka直接将位置信息存储在了Zookeeper中,遭遇了Zookeeper吞吐瓶颈,于是采取和HBase类似的做法,将位置信息作为一个特殊的Partition。
7.2. Redis&Raft&ZAB&PaxOS
Redis同Raft、ZAB和PaxOS并不是一类东西,但核心均有Quorum的朴素思想,即服从多数(多数人理解的即为真理),而且均依赖于一个递增的值来达到一致性状态,这个递增的值在Redis和ZAB中叫Epoch,Raft中叫Term。
8. 总结浓缩
8.1. 三种角色
1) Leader(领导者)
2) Follower(跟随者)
3) Candidate(候选人)
8.2. 两种RPC
1) AppendEntry(心跳和写操作)
2) RequestVote(请求投票)
8.3. 两个超时时间
中文名 | 英文名 | 主要问题 | 小技巧 |
---|---|---|---|
选举超时 | Election Timeout | 选票分裂造成活锁 | 随机时间(Redis非强一致,另加了权重) |
心跳超时 | Heartbeat Timeout | 不能小于选举超时时长 |
9. 参考
1) https://translate.google.cn
2) https://raft.github.io/raft.pdf
3) https://raft.github.io/
4) http://thesecretlivesofdata.com/raft/
5) PacificA:微软设计的分布式存储框架
6) 分布式共识(Consensus):Viewstamped Replication、Raft以及Paxos
7) https://static.googleusercontent.com/media/research.google.com/en//archive/paxos_made_live.pdf