MsgApp消息
上小结我们看到节点发起选举的相关消息,当候选人通过选举成为新的Leader后,首先会调用becomeLeader方法初始化相关状态,然后会调用bcastAppend方法,该方法主要向集群中其他节点广播MsgApp消息。消息的简略流程如下:
becomeLeader方法的主要功能:
作用:节点赢得选举变成Leader节点
1.检测当前节点如果是Follower则panic
2.将step设置成stepLeader
3.重置raft实例的Term、心跳计时器、选举计时器、选举超时时间等字段
4.将Leader设置成当前节点的id
5.更新当前节点的角色
6.保守地将pendingConfIndex设置为日志中的最后一个索引
7.向当前节点的raftLog中追加一条空的Entry记录
func (r *raft) becomeLeader() {
// TODO(xiangli) remove the panic when the raft implementation is stable
//检测当前节点的状态,禁止从Follower状态切换成Leader状态
if r.state == StateFollower {
panic("invalid transition [follower -> leader]")
}
//将step字段设置成stepLeader
r.step = stepLeader
r.reset(r.Term) //重置raft实例的Term等字段
r.tick = r.tickHeartbeat //tick字段设置成tickHeartbeat方法
r.lead = r.id //将leader字段当前节点的id
r.state = StateLeader //更新当前节点的角色
ents, err := r.raftLog.entries(r.raftLog.committed+1, noLimit)
if err != nil {
r.logger.Panicf("unexpected error getting uncommitted entries (%v)", err)
}
// Conservatively set the pendingConfIndex to the last index in the
// log. There may or may not be a pending config change, but it's
// safe to delay any future proposals until we commit all our
// pending log entries, and scanning the entire tail of the log
// could be expensive.
/*
保守地将pendingConfIndex设置为日志中的最后一个索引。
可能有也可能没有挂起的配置更改,但是在我们提交所有挂起的日志条目之前延迟任何未来的提议是安全的,
并且扫描日志的整个尾部可能很昂贵。
*/
if len(ents) > 0 {
r.pendingConfIndex = ents[len(ents)-1].Index
}
//向当前节点的raftLog中追加一条空的Entry记录
r.appendEntry(pb.Entry{Data: nil})
r.logger.Infof("%x became leader at term %d", r.id, r.Term)
}
bcastAppend方法
该方法的主要作用是向集群中的其他节点发送MsgApp(或MsgSnap)消息,这里我们先忽略MsgSnap消息,后面会详细介绍。
func (r *raft) bcastAppend() {
r.forEachProgress(func(id uint64, _ *Progress) {
if id == r.id {
return
}
r.sendAppend(id)
})
}
sendAppend方法的处理流程
1.获取目标节点的Progress,如果是暂停状态,则返回 2.创建待发送的消息实体m,并设置目标节点的ID 3.获取Next-1对应的记录的Term值,获取需要发送的Entries 4.上述两次raftLog查找出现异常时,就会形成MsgSnap消息,将快照数据发送到指定节点。 5.否则,发送MsgSnap类型的消息 6.发送前面创建的MsgApp或MsgSnap类型的消息,raft.send()会设置MsgApp消息的Term值,并将其追加到raft.msgs中等待发送。
func (r *raft) sendAppend(to uint64) {
pr := r.getProgress(to) //获取目标节点的Progress
if pr.IsPaused() { //检测当前节点是否可以向目标节点发送消息
return
}
m := pb.Message{} //创建待发送的消息
m.To = to //设置目标节点的ID
term, errt := r.raftLog.term(pr.Next - 1) //pr.Next是下个待复制Entry的索引位置,获取Next索引对应的记录的Term值
ents, erre := r.raftLog.entries(pr.Next, r.maxMsgSize) //获取需要发送的Entry记录(ents)
if errt != nil || erre != nil { // 上述两次raftLog查找出现异常时,就会形成Ms