源码解读etcd heartbeat,election timeout之间的拉锯

2371458dd7f90bbfbb8c0102e6c706a5.gif

转一个我在知乎上回答的有关raft election timeout/ heartbeat  interval 的回答吧。

665925f48ed2e324597b45db1a10298d.png

答:准确来讲:election是timeout,而heartbeat 是interval, 这样就很容易理解了。

heartbeat interval 是leader 安抚folower的时间,这个时间间隔是体现在leader上,是leader发送心跳的周期 (我xxxx ms 来一次)。

election timeout 是follower能容忍多久没收到心跳开始骚动的时间 (我等你xxxx ms,没来我就起义)。

为压制follower随时起义的骚动,heartbeat timeout 一般小于 election timeout。

37be449a820a23b256531cc58e3b5e18.png

楼主说两个配置超时,都会成为候选者,实际上,heartbeat interval/election timeout 是一个此消彼长的拉锯。

  1. 想象一个刚初始化的集群,大家都是follower,没有heartbeat压制, 各follower节点的election timeout之后开始骚动。

  2. 在一次选举周期没有选出leader,很可能是选票瓜分了, 需要发起新的选举;为缓解选票瓜分的情况, 每个节点的election timeout骚动时间是随机的。

  3. 发生网络分区的时候, 少数派分区的follower收不到leader 的安抚,是不是又要起义,这个时候election timeout也起作用了。

我们结合etcd的默认配置和源码理解:

目前etcd默认heartbeat = 100ms, election = 1000ms

https://github.com/etcd-io/etcd/blob/5fd69102ce785136aeb3168c56adce7957b99e2d/raft/raft.go#L1718

raft 为节点定义了以下状态:

const (
    StateFollower StateType = iota
    StateCandidate
    StateLeader
    StatePreCandidate
    numStates
)

becomeLeader 注册了定期发送心跳的动作  r.tick = r.tickHeartbeat ;

becomeFollower becomeCandidate becomePreCandidate 都注册了(没收到安抚而)起义的动作 r.tick = r.tickElection;

我们以follower节点为例:

func (r *raft) becomeFollower(term uint64, lead uint64) {
 r.step = stepFollower
 r.reset(term)
 r.tick = r.tickElection
 r.lead = lead
 r.state = StateFollower
 r.logger.Infof("%x became follower at term %d", r.id, r.Term)
}
  • r.reset(term)==> r.resetRandomizedElectionTimeout() 会接受传播过来的term,并计算随机选举超时时间。

func (r *raft) resetRandomizedElectionTimeout() {
 r.randomizedElectionTimeout = r.electionTimeout + globalRand.Intn(r.electionTimeout)
}

从上面源码看出,etcd默认配置产生的节点随机超时时间是 [1000,2000]ms。

  • r.tickElection 会判断:如果当前经历的时间electionElapsed大于随机超时时间,就开始起义,并重置electionElapsed时间。

func (r *raft) tickElection() {
 r.electionElapsed++

 if r.promotable() && r.pastElectionTimeout() {
  r.electionElapsed = 0
  if err := r.Step(pb.Message{From: r.id, Type: pb.MsgHup}); err != nil {
   r.logger.Debugf("error occurred during election: %v", err)
  }
 }
}

func (r *raft) pastElectionTimeout() bool {
 return r.electionElapsed >= r.randomizedElectionTimeout
}

becomePreCandidate 没有r.reset(term)动作,这是一个预投票状态,也称prevote,这也是etcd的常见面试题。

prevote 是论文作者为解决“分区后少数派leader重新加入集群,因为高term导致集群瞬间不稳定”的提出的方案,etcd 默认加入prevote机制: 在成为真正意义的候选者之前不自增term,先预投票,因为其他节点一直收到心跳,并不会起义,故该节点预投票拿不到多数投票,等到该节点收到leader心跳,自行降为follower,term和Leader一致,   现在这一机制已经插入到每次follower-->Candidate之间。

switch m.Type {
 case pb.MsgHup:
  if r.preVote {
   r.hup(campaignPreElection)
  } else {
   r.hup(campaignElection)
  }
368fe798a9a8ec5807713311d368aae9.png

Prevote是一个典型的2PC协议,第一阶段先征求其他节点是否同意选举,如果同意选举则发起真正的选举操作,否则降为Follower角色。这样就避免了网络分区节点重新加入集群,触发不必要的选举操作。

  • https://www.jianshu.com/p/1496228df9a9

  • https://github.com/etcd-io/etcd

d85d8f66dbaabe3e62321c9dd6012e75.gif

年终总结:2021技术文大盘点  |  打包过去,面向未来

项目总结:麻雀虽小,五脏俱全

理念总结:实话实说:只会.NET,会让我们一直处于鄙视链、食物链的下游

云原生系列: 什么是云原生?

点“0091bfb6e75607a158802a239dc66c19.gif戳“在看cb46f49549915e1e3c1bf38bdf0611d1.gif

体现态度很有必要!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有态度的马甲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值