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