leadership transfer能夠把raft group中的leader身份轉給其中一個follower。這個功能能夠用來作負載均衡,好比能夠把leader放在性能更好的機器或者離客戶端更近的機器上。node
對於一個大規模分佈式系統來講,負載均衡很是重要。然而raft自己在選主方面必需要求新主包含全部的意境committed的log,從這點上看,在選主階段,不能加入自定義的選主邏輯。而paxos協議不太同樣,paxos對選主沒有要求,任何一個成員均可以成爲主,選主協議能夠本身實現。paxos leader當選後,從其餘成員把commit的log拉過來便可。因此爲了這個feature,raft做者提出了一個方案做爲raft的擴展。網絡
大概原理就是保證transferee(transfer的目標follower)擁有和原leader有同樣新的日誌,期間須要停寫,而後給transferee發送一個特殊的消息,讓這個follower能夠立刻進行選主,而不用等到election timeout,正常狀況下,這個follower的term最大,當選,原來的leader變爲備。app
仍是同樣看看etcd實現的raft library怎麼作,省略無關代碼負載均衡
首先應用經過以下函數來啓動leader transfer,其中lead是當前的leader,transferee是目標leader,在任意一個成員上調用便可。分佈式
func (n *node) TransferLeadership(ctx context.Context, lead, transferee uint64) { select { // manually set 'from' and 'to', so that leader can voluntarily transfers its leadership case n.recvc <- pb.Message{Type: pb.MsgTransferLeader, From: transferee, To: lead}: case <-n.done: case <-ctx.Done(): } }
跑raft的goroutine從recvc中拿出message,首先作各類各樣的檢查,好比是否已經有transfer leader正在進行中,若是正在進行,目標是誰,而後作相應的處理。若是沒有,則調用一下代碼:函數
r.leadTransferee = leadTransferee if pr.Match == r.raftLog.lastIndex() { r.sendTimeoutNow(leadTransferee) r.logger.Infof("%x sends MsgTimeoutNow to %x immediately as %x already has up-to-date log", r.id, leadTransferee, leadTransferee) } else { r.sendAppend(leadTransferee) }
首先將目標leader保存在leadTransferee中,標示着有transfer正在進行,後續若是有請求propose進來,會檢查:性能
if r.leadTransferee != None { r.logger.Debugf("%x [term %d] transfer leadership to %x is in progress; dropping proposal", r.id, r.Term, r.leadTransferee) return }
這裏至關於停寫。ui
回到上面:日誌
r.campaign(campaignTransfer)
raft爲了防止出現網絡分區的狀況下,candidate頻繁增長term從而致使term爆炸,在選主的時候新增長了一個PreVote階段,經過了這個階段纔會真正開始Vote,這裏,因爲transferee明確知道是transfer,就沒有必要採用這種兩階段的選主,因此傳入的參數是campaignTransfercode
// Transfer leadership is in progress. if m.From == r.leadTransferee && pr.Match == r.raftLog.lastIndex() { r.logger.Infof("%x sent MsgTimeoutNow to %x after received MsgAppResp", r.id, m.From) r.sendTimeoutNow(m.From) }
若是發現transferee已經日誌最新,則一樣,給transferee發送MsgTimeoutNow
最後,看看etcd如何調用:
func (s *EtcdServer) transferLeadership(ctx context.Context, lead, transferee uint64) error { now := time.Now() interval := time.Duration(s.Cfg.TickMs) * time.Millisecond plog.Infof("%s starts leadership transfer from %s to %s", s.ID(), types.ID(lead), types.ID(transferee)) s.r.TransferLeadership(ctx, lead, transferee) for s.Lead() != transferee { select { case <-ctx.Done(): // time out return ErrTimeoutLeaderTransfer case <-time.After(interval): } } // TODO: drain all requests, or drop all messages to the old leader plog.Infof("%s finished leadership transfer from %s to %s (took %v)", s.ID(), types.ID(lead), types.ID(transferee), time.Since(now)) return nil }
調用TransferLeadership後,每隔一段時間檢查是否transfer成功,要麼超時,直接返回。