一句话总结:Raft协议理解后可以简单记忆为 选举机制 + binlog数据结构(事件回放) + 两阶段提交同步binlog
Raft协议动画演示:http://thesecretlivesofdata.com/raft/
基于Raft协议实现的分布式系统是单主的,所有的读写操作都路由到Leader节点进入,实现强一致性。按CAP理论理解,是一个CP+多数派高可用A的系统。集群数量是在集群启动时配置好了的,才能进行多数派的判断,不支持动态加入节点。
选举机制:
节点状态:Follower、Candidate、Leader
初始所有节点都处于Follower状态,一定超时时间后,节点将自己变为Candidate状态,并向其他节点发出投票请求,若其收到大多数其他节点的投票,则其变为Leader状态。Follower收到任何消息会重置超时时间。一个新的选举周期会在某个Follower等待超时并重新发起选举。
这个超时时间是随机的,通常是150ms~300ms,每个节点由于随机通常不一样。 超时时间不同可以让某个Follower先发起选举,避免同时抢占让自己称为Leader。
Split Vote场景:假设有4个节点(非2f+1个,可能一台挂掉),就是有两个Follower同时达到超时时间同时变为Candidate状态,它们都投票给自己,然后剩下两台一人给一个Candidate各投一票(注意一个term中每台只能投票一次,接收到CandidateA投票请求会投给它,再接收到CandidateB投票请求则会丢弃),此时两个Candidate各得两票,又都没达到多数派。此种场景两个Candidate都不会变成Leader,等待超时时间过后随机生成新超时时间(应该不会再碰撞)发起新一轮投票。
如果某台Follower接收不到Leader消息变为Candidate,其他Follower还能跟Leader通信,此时怎么处理? Follower会拒绝投票,只有其也接收不到Leader消息超时变为Candidate状态后才能投票。
写事务机制:
Leader节点收到写请求后,此时事务不提交,将请求封装成操作+数据的binlog格式,再将binlog分发给其他节点。Leader节点阻塞等待,待收到大多数节点回复写成功消息,Leader节点则提交事务,向client调用者返回写请求成功,并向Follower节点发出commit消息,Follower节点收到commit消息则提交本地事务。这是一个两阶段提交过程。
Leader发送binlog不是一接收到写请求就发送,而是按照固定的心跳周期单个或批量发送。
疑问:两阶段提交的公共问题,假设Follower在commit阶段接收不到commit消息或自身执行失败怎么办?
日志重放机制:
日志不一致有两种情况,节点或网络故障后重新加入集群,binlog比Leader要少,这种情况比较好理解,leader向该节点补上缺失的binlog。还有一种特殊情况节点开始是Leader,收到写请求写binlog后突然挂掉,其他节点变为Leader,其实该节点比新Leader多了些uncommit的binlog。这种情况新节点会从后往前逐个去找binlog索引相同的位置,然后覆盖掉其后的日志。
脑裂自愈机制:
假设5台节点(2f+1) A节点已为主,此时网络分区即脑裂,一边3台C、D、E,一边2台A、B,A Leader在2台区域,3台区域无主会重新选举假设C成为Leader。此时client给Leader A发送写请求,Leader A无法提交因为接收不到f+1即3台节点的回应。而client给Leader C由于有3台可以提交。Leader A会向client返回写失败但数据仍保持一致性。
脑裂自愈,假设网络分区消除,5节点重新可以互相通信,此时A和B一旦接收消息并发现消息中的term比自身的term大,则将自己step down为Follower角色,并以消息中携带该term的主C为Leader。携带老旧binlog索引向Leader C请求最新数据,Leader C接收到请求根据binlog索引向其发送缺失的最新binlog数据。
参考:
https://raft.github.io/
http://thesecretlivesofdata.com/raft/
https://github.com/maemual/raft-zh_cn/blob/master/raft-zh_cn.md