etcd中raft協議的消息(七)——快照複製消息(MsgSnap消息)

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)
  }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章