源碼解讀etcd heartbeat,election timeout之間的拉鋸
- 2022 年 5 月 24 日
- 筆記
- Cloud Native
轉一個我在知乎上回答的有關raft election timeout/ heartbeat interval 的回答吧。
答:準確來講: election是timeout,而heartbeat 是interval, 這樣就很容易理解了。
heartbeat interval 是leader 安撫folower的時間,這個時間間隔是體現在leader上,是leader發送心跳的周期 (我xxxx ms 來一次)。
election timeout 是follower能容忍多久沒收到心跳開始騷動的時間 (我等你xxxx ms,沒來我就起義)。
為壓制follower隨時起義的騷動,heartbeat timeout 一般小於 election timeout。
樓主說兩個配置超時,都會成為候選者,實際上,heartbeat interval/election timeout 是一個此消彼長的拉鋸。
-
想像一個剛初始化的集群,大家都是follower,沒有heartbeat壓制, 各follower節點的election timeout之後開始騷動。
-
在一次選舉周期沒有選出leader,很可能是選票瓜分了, 需要發起新的選舉; 為緩解選票瓜分的情況, 每個節點的election timeout騷動時間是隨機的。
-
發生網絡分區的時候, 少數派分區的follower收不到leader 的安撫,是不是又要起義,這個時候election timeout也起作用了。
我們結合etcd的默認配置和源碼理解:
目前etcd默認heartbeat = 100ms, election = 1000ms
//github.com/etcd-io/etcd/blob/5fd69102ce785136aeb3168c56adce7957b99e2d/raft/raft.go#L1718
raft 為節點定義了以下狀態:
const (
StateFollower StateType = iota
StateCandidate
StateLeader
StatePreCandidate
numStates
)
becomeLeader 註冊了定期發送心跳的動作 r.tick = r.tickHeartbeat
;
becomeFollower becomeCandidate becomePreCandidate 都註冊了(沒收到安撫而)起義的動作 r.tick = r.tickElection
;
我們以follower節點為例:
func (r *raft) becomeFollower(term uint64, lead uint64) {
r.step = stepFollower
r.reset(term)
r.tick = r.tickElection
r.lead = lead
r.state = StateFollower
r.logger.Infof("%x became follower at term %d", r.id, r.Term)
}
r.reset(term)==> r.resetRandomizedElectionTimeout()
會接受傳播過來的term,並計算隨機選舉超時時間。
func (r *raft) resetRandomizedElectionTimeout() {
r.randomizedElectionTimeout = r.electionTimeout + globalRand.Intn(r.electionTimeout)
}
從上面源碼看出,etcd默認配置產生的節點隨機超時時間是 [1000,2000]ms。
r.tickElection
會判斷:如果當前經歷的時間electionElapsed
大於隨機超時時間,就開始起義,並重置electionElapsed
時間。
func (r *raft) tickElection() {
r.electionElapsed++
if r.promotable() && r.pastElectionTimeout() {
r.electionElapsed = 0
if err := r.Step(pb.Message{From: r.id, Type: pb.MsgHup}); err != nil {
r.logger.Debugf("error occurred during election: %v", err)
}
}
}
func (r *raft) pastElectionTimeout() bool {
return r.electionElapsed >= r.randomizedElectionTimeout
}
becomePreCandidate 沒有r.reset(term)動作,這是一個預投票狀態,也稱prevote
,這也是etcd的常見面試題。
prevote 是論文作者為解決「分區少數派重新加入集群,因為高term導致集群瞬間不穩定」的提出的方案,etcd 默認加入prevote機制, 在成為真正意義的候選者之前不自增term,先預投票,因為其他節點一直收到心跳,並不會起義,故該節點預投票拿不到多數投票,等到該節點收到leader心跳,自行降為follower,term和Leader一致, 現在這一機制已經插入到每次follower–>Candidate之間。
switch m.Type {
case pb.MsgHup:
if r.preVote {
r.hup(campaignPreElection)
} else {
r.hup(campaignElection)
}