etcd raft如何實現leadership transfer

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

回到上面:日誌

  • 若是transferee和leader的log同樣新,則給transferee發送MsgTimeoutNow類型的消息,告訴transferee能夠當即選主,不須要等到election timeout。transferee端:
r.campaign(campaignTransfer)

raft爲了防止出現網絡分區的狀況下,candidate頻繁增長term從而致使term爆炸,在選主的時候新增長了一個PreVote階段,經過了這個階段纔會真正開始Vote,這裏,因爲transferee明確知道是transfer,就沒有必要採用這種兩階段的選主,因此傳入的參數是campaignTransfercode

  • 若是leader發現transferee的日誌落後,則給transferee append日誌,leader在收到響應MsgAppResp後,會檢查:
// 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成功,要麼超時,直接返回。

相關文章
相關標籤/搜索