raft的log replication是在了leader选举之后进行的,leader的选举可以参考这篇文章etcd-raft的leader选举
raft的Heartbeat是leader在选举成功后巩固自己地位和同步信息的一种方式,日志的复制大多数情况下适合Heartbeat是同步进行的。当一个raft节点选举成为leader后,该节点周期性执行的tick函数指向了raft.tickHeartbeat,对于leader而言主要是通知follower自己还活着,与此同时leader在收到follower对Heartbeat的响应之后,又会向日志比较落后的follower发送追加日志请求,进行log replication。
由于发起heartbeat的raft节点处于leader角色,因此,被周期调用的tick将指向tickHeartbeat:
unc (r *raft) tickHeartbeat() {
r.heartbeatElapsed++
// ...
// 如果心跳计时超过了心跳包的发送间隔,就进入发送心跳包流程,并重置心跳计时
if r.heartbeatElapsed >= r.heartbeatTimeout {
r.heartbeatElapsed = 0
r.Step(pb.Message{From: r.id, Type: pb.MsgBeat})
}
}
func (r *raft) Step(m pb.Message) error {
// ...
// Step最终调用raft.step变量指向的函数
// 现阶段当前节点处于leader状态,所以step指向stepLeader
r.step(r, m)
return nil
}
stepLeader中心跳发送代码相关执行流程如下:
func stepLeader(r *raft, m pb.Message) {
switch m.Type {
case pb.MsgBeat:
// 把心跳包广播出去
r.bcastHeartbeat()
return
// ...
}
// 在bcastHeartbeat中循环的把心跳包发送出去
func (r *raft) bcastHeartbeat() {
for id := range r.prs {
if id == r.id {
continue
}
r.sendHeartbeat(id)
r.prs[id].resume()
}
}
func (r *raft) sendHeartbeat(to uint64) {
// r.raftLog.committed 已经拷贝到大多数节点上的日志index
// r.prs[to].Match拷贝到to这个节点的日志最大下标
commit := min(r.prs[to].Match, r.raftLog.committed)
m := pb.Message{
To: to,
Type: pb.MsgHeartbeat,
Commit: commit,
}
//把心跳包发送出去
r.send(m)
}
follower在收到心跳包之后,最终处理心跳的包的流程会通过Step->step->stepFollower:
fu