raft协议是multi paxos协议的实现.Etcd、Consu都使用了raft
1.角色
raft协议中包含这几种角色
领导者:带头大哥1.提出提议,但是不需要确认,因为我是大哥;2.复制日志,数据以大哥为准,3,领导者会定时发送心跳,确定自己的位置.告诉小弟老实呆着,一旦心跳超时,小弟就会重新选举大哥.
跟随者:只要大哥发送心跳,我就老实的同步日志.一旦没有心跳,我就变成候选人,开始发起投票,争取做大哥
候选人:心跳超时后,变成候选人,这是个中间状态.
2.选举
2.1选举时机:
1.心跳超时后,变成候选人,开始进行选举.为了避免节点全都一起选举,不同节点会有随机心跳超时时间.
2.例如机器刚启动时,大家都是follower,心跳超时后,就开始选举
3.优化.为了避免瓜分选票,心跳超时时间随机,当选举超时后,会进入第二轮选举,这个间隔时间也是随机的
2.2 选举原则:
1.每个节点,对每个任期(trem)只能投出一张票.与zab不同,zab是不断修改自己的选票,然后每个节点都有个投票箱,自己计算
2.收到选举请求后,如果该请求的事务日志编号小于自己的机器,那么就不会投票. 要当leader的机器,最起码掌握的东西得比我多吧(log)
3.收到选举请求后,如果该选举编号(term)小于自己的term,那么就不会投票. 我们都在老xi时代了,你还在选举第二任?
4.当一台机器收到多数投票,代表选举成功.
2.3 rpc消息:
raft中有两个rpc:
1.选举rpc:是由候选人在选举期间发起,通知各节点进行投票
2.日志复制rpc:是由领导者发起,用来复制日志和提供心跳消息.
2.4 term
1.如果一个机器收到,任期编号大的机器发来的心跳,那么他就应该知道自己已经out了.要赶紧同步数据.化为follower.
2.当节点收到任期编号比我小的怎么办?丢弃.
2.5 总结:
与multiPaxos相比
1.通过日志编号确定数据是否最新,因此要求数据是连续的
当确认leader之后,leader会同步数据给follower.
3.日志复制:
流程
正常情况:
- client向leader发出一个添加请求
- leader写入appendEntryLog.并标记为uncommit状态.
- 调用appendEntryRpc,拷贝日志项到follower.
- follower接收到日志项,并append到本机.返回给leader success
- leader收到success后,将日志置为committed状态.并apply到状态机.返回给client success.
异常流程:
当拷贝到follower,follower没有响应成功时,有可能是没有拷贝过去,也有可能ack丢失了. leader会不断重试,直到满足过半. 当然这时候,client可能会超时.这条日志可能最后会丢失,也可能会committed,客户端需要实现幂等
先说问题:
1.leader把数据复制到follower,follower大部分返回成功后,leader会apply此条数据到状态机.并返回给client成功的响应.
注意这里:状态机是对于上层来说的,比如你写成功后,再来读取数据,就是从状态机的数据中读,如果你返回给用户成功,但是状态机数据还是旧的,那么就不对了.
那我能不能在leader复制后,就update状态机呢?
不能,如果直接update,返回成功给用户,但是此时leader挂了.咋整.所以需要等大部分都提交,才返回成功.因为大部分提交后,即使此时leader挂了.重新选举时,根据raft协议.只有提议id大的才可以当leader,
就可以保证,之后当选的leader会有这条数据.因此这里也可以知道.选举时比较的日志id,是复制的日志id,而不是commitedId.
2.raft将两阶段提交,优化为1阶段提交.
当一条请求发送给leader时,leader会写一条日志(uncommit),之后复制给follower(uncommit),当过半后,leader会变为committed,那么follower啥时候变为committed,并apply到状态机呢???
如果不是实时的,岂不是在读取的时候,读不出来??
a:通过AppendEntriesRPC同步数据,细节前面已经说了.此时就会把数据应用到状态机.大概逻辑就是:心跳/同步数据 rpc,会携带最新日志id,如果少的话,那么需要进行同步.appendEntryRpc互携带最新日志以及前一条日志(preEntryTerm,preEntryLogId),就可以回溯到 到底从哪条日志不一样了,然后再同步给follower.
b:注意:一致性是说 对于client,发出的读请求,可以获取到集群最新的数据,而不是说,集群每个节点数据都是一致的!!.这个是很容易错误理解的!! 例如etcd,早期是通过读写都走leader节点来保证一致性.3.2版本后,通过readIndex来保证.readIndex是什么,可以参考etcd源码分析系列;
所以一般来讲, leader append log(uncommit)--->copy to followers-->leader apply log &commit the log--->下次心跳,同步数据
参考:
https://zhuanlan.zhihu.com/p/27207160
https://raft.github.io/raft.pdf