我们都知道Zookeeper基于Paxos算法的ZAB协议拥有崩溃恢复和消息广播两种模式,而且有效避免了脑裂。这使得其成为目前最优秀的分布式一致性协议之一。但是除此之外还有一个同样脱胎于Paxos算法的Raft协议。
Raft协议是工程上使用较为广泛的强一致性、去中心化、高可用的分布式协议。 目前 etcd,consul都是基于Raft协议进行数据同步。 Raft协议和Zookeeper的ZAB协议一样脱胎于Paxos协议。 以此可以看出来Paxos协议在分布式协议学术理论届方面的地位。为什么是说是学术理论届呢?这是因为Paxos协议的出了名的难懂,这使得他的应用受到限制。不同于Paxos协议的难以理解。 Raft协议的主要特点就是易于理解。
节点角色
要想理解Raft协议,先要理解Raft协议中节点的可能担任的三个角色:
- Leader:领导者,该角色会一直工作到出现问题,Leader复制处理所有客户端的请求,并定期向集群中Followers发送心跳以维持任期。
- Follower:Follower只接收集群内部的服务器的消息。对于集群外部的客户端请求,Follower会将请求转发给Leader,由Leader处理。
- Candidate:候选者,如果Follower在心跳周期内没有收到Leader的心跳,那么Follower就会变成Candidate,发起新一轮的选举。
从这就能看出Paxos,ZAB,Raft协议的关联性。 几乎都是一个模子刻出来的。
选举Leader
发起选举
每个节点生成一个倒计时(一个在150毫秒到300毫秒之间的随机值,相较于ZAB的固定值,随机数来做一定的排序,一定程度上能建少选举轮数),倒计时结束成为Candidate,Candidate设置本地任期为初始任期.先为自己投上一票,然后发送带着这个初始任期的消息给其他节点启动选举。
进行投票
其他节点收到这个选举消息后,会判定这个消息所指的任期自己是否已经投票过,如果投过票,则不再投票。如果未投过,则会判断发起选举的Candidate是否知道的比自己少(这个后续的保证数据一致性会详细说)。如果知道的比自己的少,则不投票给这个Candidate, 否则就将自己的票投给这个Candidate。
经历这个阶段,Candidate会面临三种状况:
- 得到过半的投票,成为leader。之后给所有节点发送自己在term任期当选的消息。
- 收到别的节点不小于自己term任期已当选的消息,则自己切换为Follower
- 一段时间内依然没收到过半的投票(比如平票导致僵持),重新发起选举。
选举成功
Leaders周期性的发送心跳给Follower。Follower节点收到心跳就会重置自身的倒计时时间,以此来维护任期。
平票处理
如果当前节点的有效节点是偶数个(无论是集群节点是偶数还是网络分区或节点故障导致的偶数节点可用),就可能出现两个甚至多个在同一个任期内的节点在相近的时间结束倒计时,然后都变成Candidate带着相同的新任期发起投票,因为每个Follower在一个任期只能投一次票,那么就可能出现这些Candidate获得同样的票数获得平票。 此时就会重启倒计时,开启新一轮的选举。整个集群会重复这个过程,一直到有节点获得大多数的投票成为Leader.
Leader异常重新选举
Leader故障后,Follower无法收到来自心跳。自然也就无法再重置自己的超时时间,当Followers的超时时间结束后,就会变成Candidate节点,并将本地任期trem加1.先为自己投上一票,然后发送带着这个新任期的消息给其他节点重新启动选举。
如图,虽然一个节点故障,剩下的节点依然可以得到半数的选票,成为新的Leader。
Leader选举的约束
- 同一任期内最多只能投一票,先来先得
- 选举人必须比自己知道的更多(比较term,log index)
数据同步(Log Replication 日志复制)
Raft的数据同步通过日志复制实现。即任何一个节点以相同的初始状态,以相同的顺序执行相同的日志,就能得到一致的数据结果。
一旦我们选出了领导者,Leader会通过心跳发送相同 Append Entries message来完成所有节点的同步。
客户端仅可与Leader进行数据交互提交数据变更。
首先客户端发送一个变更请求给Leader,Leader会将这个变更应用到自己的日志上,然后这个变更会随着下次心跳(注意是下次心跳,而不是马上发送)发送给Follower节点。Follower收到消息后会将变更应用到自己的日志上,之后发送ACK给Leader, Leader收到超过半数Follower的ACK后自身会Commit此次变更,然后给客户端响应。 这个Commit消息会在下次心跳时一起同步给所有Follower完成集群数据的同步。
应对网络分区
面对网络分区,Raft 依然可以保证数据的同步。
如图,因为网络分区,[A,B] 无法与[C,D,E]进行网络通信,此时[C,D,E]因为无法收到Leader的心跳重置超时时间,就会按照上文中的异常选举流程进行选举,选举出新的Leader节点E。而B因为网络分区的原因并不知道已经
此时的客户端发送变更有两种情况,
- 发送给老Leader(B):这是有可能的,可能之前的客户端依然与Leader:B保持连接,他是不知道已经有新的Leader被选出的,因此消息依然会发送给B。可以看到此种情况下,B虽然能正常接收并处理请求,但是因为网络分区,B一直无法收到过半Follower的ACK, 所以他也就一直无法执行Commit并给客户端响应。
- 发送给新Leader(E): E选举成功后新建立的连接会被指向他。可以看到此种情况下E能收到过半的Follower响应,所以能按照正常的日志复制流程完成数据同步。但是在raft的一些实现或者raft-like协议中,leader如果收不到majority节点的消息,那么可以自己step down,自行转换到follower状态。
分区结束后的数据恢复
此时A,B节点收到新Leader:E的心跳,发现已经有更高的任期了,B就会降级自己为Follower。 同时A&B将回滚自己未提交的条目,并与新Leader:E的日志相匹配。 以此完成了集群数据的一致性。
Log Replication的约束
- 一个log被复制到大多数节点,就是committed,保证不会回滚
- leader一定包含最新的committed log,因此leader只会追加日志,不会删除覆盖日志
- 不同节点,某个位置上日志相同,那么这个位置之前的所有日志一定是相同的
- Raft永远不会从比自己低的任期副本内同步之前的日志
更多
可以通过这个演示动画,更生动的了解Raft协议。
PS:
【JAVA核心知识】系列导航 [持续更新中…]
关联导航:35:一致性协议:2PC,3PC与Paxos
关联导航:36:ZooKeeper的ZAB协议
关联导航:38:Zookeeper的使用与典型应用场景
欢迎关注…
参考资料:
Raft 协议
一文搞懂Raft算法
http://thesecretlivesofdata.com/raft/#home