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成功,要么超时,直接返回。

相关文章
相关标签/搜索