MsgSnap消息
通過前面介紹的raft.sendAppend()方法可知,在Leader節點嘗試向集羣中的Follower節點發送MsgApp消息時,如果查找不到待發送的Entry記錄(即該Follower節點對應的Progress.Next指定的Entry記錄),則會嘗試通過MsgSnap消息將快照數據發送到Follower節點,Follower節點之後會通過快照數據回覆其自身狀態,從而可以與Leader節點進行正常的Entry記錄複製。例如當Follower節點宕機時間較長,就會出現上述發送MsgSnap消息的場景。
Leader發送MsgSnap消息的主要流程如下:
1.上述兩次raftLog查找出現異常時(獲取不到需要發送的Entry記錄),就會形成MsgSnap消息,將快照數據發送到指定節點。
2.向該節點發送MsgSnap類型的消息
3.將目標Follower節點對應的Progress切換成ProgressStateSnapshot狀態
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查找出現異常時,就會形成MsgSnap消息,將快照數據發送到指定節點。
if !pr.RecentActive { //如果該節點已經不存活,則退出(RecentActive爲true表示該節點存活)
r.logger.Debugf("ignore sending snapshot to %x since it is not recently active", to)
return
}
m.Type = pb.MsgSnap //將消息設置成MsgSnap,爲後續發送快照做準備
snapshot, err := r.raftLog.snapshot() //獲取快照數據
if err != nil { //異常檢測,如果獲取快照數據異常,則終止整個程序
if err == ErrSnapshotTemporarilyUnavailable {
r.logger.Debugf("%x failed to send snapshot to %x because snapshot is temporarily unavailable", r.id, to)
return
}
panic(err) // TODO(bdarnell)
}
if IsEmptySnap(snapshot) {
panic("need non-empty snapshot")
}
m.Snapshot = snapshot //設置MsgSnap消息的Snapshot字段
sindex, sterm := snapshot.Metadata.Index, snapshot.Metadata.Term //獲取快照的相關信息
r.logger.Debugf("%x [firstindex: %d, commit: %d] sent snapshot[index: %d, term: %d] to %x [%s]",
r.id, r.raftLog.firstIndex(), r.raftLog.committed, sindex, sterm, to, pr)
pr.becomeSnapshot(sindex)//將目標Follower節點對應的Progress切換成ProgressStateSnapshot狀態,其中會用Progress.PendingSnapshot字段記錄快照數據信息
r.logger.Debugf("%x paused sending replication messages to %x [%s]", r.id, to, pr)
} else {
//發送MsgApp消息略
}
}
}
//發送前面創建的MsgApp消息,raft.send()會設置MsgApp消息的Term值,並將其追加到raft.msgs中等待發送。
r.send(m)
}
Follower處理MsgSnap消息
看到Follower節點接收到MsgSnap消息,會將選舉計時器置爲0防止發生選舉,並調用handleSnapshot函數重建當前節點的raftLog日誌。
func stepFollower(r *raft, m pb.Message) error {
switch m.Type {
......
case pb.MsgSnap:
r.electionElapsed = 0 //重置electionElapsed,防止發生選舉
r.lead = m.From //設置Leader的id
r.handleSnapshot(m) //通過MsgSnap消息中的快照數據,重建當前節點的raftLog
}
}
1.獲取快照的元數據索引值(Index)和任期號(Term)
2.調用restore函數去重建和恢復raftLog
3.如果重建成功則返回MsgAppResp消息,Index爲當前日誌最新的索引值
4.如果重建失敗則返回MsgAppResp消息,Index爲當前日誌已提交位置
func (r *raft) handleSnapshot(m pb.Message) {
sindex, sterm := m.Snapshot.Metadata.Index, m.Snapshot.Metadata.Term //獲取快照的元數據
if r.restore(m.Snapshot) { //返回值表示是否通過快照數據進行重建
r.logger.Infof("%x [commit: %d] restored snapshot [index: %d, term: %d]",
r.id, r.raftLog.committed, sindex, sterm)
/*
向Leader節點返回MsgAppResp消息(Reject始終爲false)。該MsgAppResp消息作爲MsgSnap消息的響應,與前面介紹的MsgApp消息並無差別,
*/
r.send(pb.Message{To: m.From, Type: pb.MsgAppResp, Index: r.raftLog.lastIndex()})
} else {
r.logger.Infof("%x [commit: %d] ignored snapshot [index: %d, term: %d]",
r.id, r.raftLog.committed, sindex, sterm)
r.send(pb.Message{To: m.From, Type: pb.MsgAppResp, Index: r.raftLog.committed})
}
}
1.如果快照中的Index小於當前raftLog已提交位置則返回false
2.根據快照數據的元數據查找匹配的Entry記錄,如果存在,則表示當前節點已經擁有了該快照中的全部數據,所以無需後續的重建
3.如果快照中的Learners中包含當前節點id,則返回false。因爲normal節點不能變成learner
4.通過raftLog.unstable記錄該快照數據,同時重置相關字段
5.清空raft.prs字段,並根據快照的元數據進行重建
func (r *raft) restore(s pb.Snapshot) bool {
//快照元數據的索引小於等於當前raftLog的已提交的位置返回false
if s.Metadata.Index <= r.raftLog.committed {
return false
}
//根據快照數據的元數據查找匹配的Entry記錄,如果存在,則表示當前節點已經擁有了該快照中的全部數據,所以無需後續的重建
if r.raftLog.matchTerm(s.Metadata.Index, s.Metadata.Term) {
r.logger.Infof("%x [commit: %d, lastindex: %d, lastterm: %d] fast-forwarded commit to snapshot [index: %d, term: %d]",
r.id, r.raftLog.committed, r.raftLog.lastIndex(), r.raftLog.lastTerm(), s.Metadata.Index, s.Metadata.Term)
r.raftLog.commitTo(s.Metadata.Index)
return false
}
// The normal peer can't become learner.
if !r.isLearner {
for _, id := range s.Metadata.ConfState.Learners {
if id == r.id {
r.logger.Errorf("%x can't become learner when restores snapshot [index: %d, term: %d]", r.id, s.Metadata.Index, s.Metadata.Term)
return false
}
}
}
r.logger.Infof("%x [commit: %d, lastindex: %d, lastterm: %d] starts to restore snapshot [index: %d, term: %d]",
r.id, r.raftLog.committed, r.raftLog.lastIndex(), r.raftLog.lastTerm(), s.Metadata.Index, s.Metadata.Term)
//通過raftLog.unstable記錄該快照數據,同時重置相關字段
r.raftLog.restore(s)
//清空raft.prs字段,並根據快照的元數據進行重建
r.prs = make(map[uint64]*Progress)
r.learnerPrs = make(map[uint64]*Progress)
r.restoreNode(s.Metadata.ConfState.Nodes, false)
r.restoreNode(s.Metadata.ConfState.Learners, true)
return true
}
MsgSnapStatus消息和MsgUnreachable消息
這裏還有兩個本地消息與MsgSnap消息相關,它們分別是MsgSnapStatus消息和MsgUnreachable消息。如果Leader發送MsgSnap消息出現異常,則會調用Raft接口的ReportUnreachable()函數和ReportSnapshot()函數發送MsgSnapStatus消息和MsgUnreachable消息。下面看一下Leader對這兩類消息的處理。
func stepLeader(r *raft, m pb.Message) error {
switch m.Type {
case pb.MsgSnapStatus:
//檢驗節點對應的狀態是否是ProgressStateSnapshot
if pr.State != ProgressStateSnapshot {
return nil
}
if !m.Reject { //之前發送的MsgSnap消息時出現異常
pr.becomeProbe()
r.logger.Debugf("%x snapshot succeeded, resumed sending replication messages to %x [%s]", r.id, m.From, pr)
} else {
//發送MsgSnap消息失敗,這裏會清空對應的Progress.PendingSnapshot字段
pr.snapshotFailure()
pr.becomeProbe()
r.logger.Debugf("%x snapshot failed, resumed sending replication messages to %x [%s]", r.id, m.From, pr)
}
// If snapshot finish, wait for the msgAppResp from the remote node before sending
// out the next msgApp.
// If snapshot failure, wait for a heartbeat interval before next try
/*
無論MsgSnap消息發送是否失敗,都會將對應的Progress切換成ProgressStateProbe狀態,之後單條發送消息進行試探
暫停Leader節點向Follower節點繼續發送消息,如果發送MsgSnap消息成功了,則待Leader節點收到相應的響應消息(MsgAppResp消息),即可繼續發送後續的
MsgApp消息,Leader節點對MsgAppResp消息的處理已經介紹過了,如果發送MsgSnap消息失敗了,
則Leader節點會等到收到MsgHeartbeatResp消息時,纔會重新開始發送後續消息
*/
pr.pause()
case pb.MsgUnreachable:
// During optimistic replication, if the remote becomes unreachable,
// there is huge probability that a MsgApp is lost.
//當Follower節點變得不可達,如果繼續發送MsgApp消息,則會丟失大量消息
if pr.State == ProgressStateReplicate {
pr.becomeProbe() //將Follower節點對應的Progress實例切換成ProgressStateProbe狀態
}
r.logger.Debugf("%x failed to send message to %x because it is unreachable [%s]", r.id, m.From, pr)
}
}