正如前面分析newraft()函式時所看到的,當節點的raft例項建立完成之後,當前節點處於follower狀態。為了方便介紹,這裡假設集群為新建集群,且集群啟動後所有節點都會處於follower狀態。另外,集群中所有節點的wal日誌檔案等也都是空的, 所以wal日誌重放也不會得到任何資訊,節點的初始term 值也都是0。另外,我們還假設當前集群開啟了pre vote模式以及checkquorum模式。
集群啟動一段時間之後,會有乙個follower節點的選舉計時器超時, 此時就會建立msghup訊息(其中term為0)並呼叫raft.step()方法。raft.step()方法是raft模組處理各類訊息的入口。raft.step()方法主要分為兩部分: 第一部分是根據term值對訊息進行分類處理,第二部分是根據訊息的型別進行分類處理。raft.step()方法中相關的**片段如下所示。
func (r *raft) step(m pb.message) error
switch m.type
//檢測是否有未應用的entryconfchange記錄,如果有就放棄發起選舉的機會
return nil
}//檢測當前集群是否開啟了prevote模式,如採開啟了,則允切換到呼叫raft.campaign()方法切換當前節點的角色, 發起prevote
r.logger.infof("%x is starting a new election at term %d", r.id, r.term)
if r.prevote else
} else
case pb.ms**ote, pb.msgprevote://對ms**ote和msgprevote訊息的處理(
default://對於其他型別訊息的處理
r.step(r, m)
} return nil
}
在raft.campaign()方法中除了完成狀態切換還會向集群中的其他節點傳送相應型別的訊息,例如,如果當前follower節點要切換成precandidate狀態,則會傳送msgprevote訊息。
follower 節點在選舉計時器超時的行為: 首先它會通過tickelection()建立msghup訊息並將其交給raft.step()方法進行處理; raft.step()方法會將當前follower節點切換成precandidate狀態, 然後建立msgprevote型別的訊息, 最後將該訊息追加到raft.msgs欄位中, 等待上層模組將其發迭出去。
當集群中其他節點(此時集群中其他節點都處於follower狀態〉收到msgprevote(其term字段值為1 )訊息之後, 經過網路層處理及相關驗證之後, 最終也會呼叫raft.step()方法進行處理。下面我們回到raft.step()方法中,分析其中關於msgprevote訊息處理的相關**:
func (r *raft) step(m pb.message) error
} switch else
} switch m.type
//當前節點在參與預選時,會綜合下面幾個條件決定是否投票(在raft協議的介紹中也捉到過).
//1、當前節點是否已經投過桑
//2、 msgprevote訊息傳送者的任期號是否更大
//3、當前節點投票給了對方節點
//4、 msgprevote訊息傳送者的raftlog中是否包含當前節點的全部entry記錄, isuptodate()方法在前面介紹過了,這裡不再贊這
if (r.vote == none || m.term > r.term || r.vote == m.from) && r.raftlog.isuptodate(m.index, m.logterm) )
if m.type == pb.ms**ote
} else )
} default:
r.step(r, m)
}
msgprevote訊息的處理過程:raft.step()方法首先檢測該msgprevote訊息是否為leader節點遷移時發出的訊息及其他合法性檢測,決定當前節點是否參與此次選舉;之後當前節點會根據自身的狀態決定是否將其選票投給msgprevote訊息的傳送節點。
precandidate節點會收到集群中其他節點返回的msgprevoteresp訊息,其中的term欄位與precandidate節點的term值相同。在raft.step()方法中沒有對term值相等的msgprevoteresp訊息做特殊的處理,而是直接交給了raft.step欄位指向的函式進行處理。在前面分析的狀態切換方法(become方法〉中,可以看到raft.step宇段會根據節點的狀態指向不同的訊息處理函式, 在precandidate狀態的節點中,該欄位指向了stepcandidate()方法。stepcandidate()函式對msgprevoteresp訊息的處理邏輯:
func stepcandidate(r *raft, m pb.message) else
switch m.type else
case len(r.votes) - gr:
r.becomefollower(r.term, none)//贊同與拒絕相等時,無法獲取半數以上的票數,當前節點切換成follower狀態,等待下一輪的選舉(或預選)
} case pb.msgtimeoutnow:
r.logger.debugf("%x [term %d state %v] ignored msgtimeoutnow from %x", r.id, r.term, r.state, m.from)
}}
當precandidate狀態節點收到半數以上的投票時,會通過rcampaign()方法發起正式選舉,其中會通過raft.becomecandidate()方法將當前節點切換成candidate狀態,井向剩餘其他節點傳送ms**ot巳訊息
precandidate 狀態節點收到半數以上的投票之後, 會發起新一輪的選舉, 即向集群中的其他節點傳送ms**ote訊息。當集群中其他節點收到ms**ote訊息之後,也是交由raft.step()方法進行處理的, 其中根據term 值進行分類處理的部分與前面介紹的msgprevote處理類似
case m.term > r.term:
if m.type == pb.ms**ote || m.type == pb.msgprevote
} switch else
}
raft.step()方法中根據訊息型別進行分類處理的**片段中,除了檢測當前節點是否投票及傳送ms**oteresp訊息,還會重置當前節點的選舉超時計時器並更新ra位vote欄位
與對msgprevoteresp訊息的處理類似,ms**oteresp訊息也是由raft.stepcandidate()方法處理的
func stepcandidate(r *raft, m pb.message) else
switch m.type else
case len(r.votes) - gr:
r.becomefollower(r.term, none)
} case pb.msgtimeoutnow:
r.logger.debugf("%x [term %d state %v] ignored msgtimeoutnow from %x", r.id, r.term, r.state, m.from)
}}
r.foreachprogress(func(id uint64, _ *progress)
})}pr := r.getprogress(to)
if pr.ispaused()
m := pb.message{}//建立待傳送的訊息
m.to = to//設定目標節點的id
//根據當前leader節點記錄的next查詢發往指定節點的entry記錄(ents)及next索引對應的記錄的term值(term)
term, errt := r.raftlog.term(pr.next - 1)
ents, erre := r.raftlog.entries(pr.next, r.maxmsgsize)
if errt != nil || erre != nil else
} }r.send(m)}
ETCD 原始碼學習 Raft 協議介紹(二)
raft協議是分布式一致性協議的一種,所以在了解raft協議之前,我們首先要知道一致性協議的作用。1.一致性協議用於解決分布式環境下多副本之間資料一致性的問題的。2.主要包括兩個部分,一是 leader 選舉,二是日誌同步。3.一致性協議主要包括paxos zab raft及 gossip 等 le...
ETCD 原始碼學習 Raft 協議選舉過程(六)
在 etcd 原始碼學習過程,不會講解太多的原始碼知識,只講解相關的實現機制,需要關注原始碼細節的朋友可以自行根據文章中的提示,找到相關原始碼進行學習 1.follower 節點擊舉計數器超時,觸發 msghup 訊息,follower 節點接收此訊息時,會發起一輪選舉。2.當 leader 節點擊...
raft協議問題
角色 raft通過選舉leader並由leader節點負責管理日誌複製來實現多副本的一致性。在raft中,節點有三種角色 角色轉換如下圖所示 節點的狀態時通過心跳包來進行維持的。產生的原因 如果乙個follower在election timeout的時間裡沒有收到leader的資訊,就進入新的ter...