如果不了解etcd-raft集群中各个节点间怎样通信的可以参考下etcd-raft 2.3.7 raft peer间的交互通信流程
raft的leader的选举,依赖于心跳包的超时,etcd-raft的周期性心跳信号由定时器产生,该定时器在扩展的raftNode(raft.node实现raft.Node接口)类中启动,由于raftNode节点一般为扩展etcd-raft算法的结合需求的可定制类,之所以把tick的产生信号放在raftNode主要是方便修改心跳超时时间时不用修改raft协议核心代码,可以见EtcdServer的raftNode:
// etcd-2.3.7/etcdserver/raft.go
// start prepares and starts raftNode in a new goroutine. It is no longer safe
// to modify the fields after it has been started.
// TODO: Ideally raftNode should get rid of the passed in server structure.
func (r *raftNode) start(s *EtcdServer) {
// ...
heartbeat := 200 * time.Millisecond
if s.cfg != nil {
heartbeat = time.Duration(s.cfg.TickMs) * time.Millisecond
}
// set up contention detectors for raft heartbeat message.
// expect to send a heartbeat within 2 heartbeat intervals.
r.td = contention.NewTimeoutDetector(2 * heartbeat)
go func() {
var syncC <-chan time.Time
defer r.onStop()
islead := false
for {
select {
case <-r.ticker:
r.Tick()
//....
} // select
} // for
}()
}
etcd-raft算法模块接收这个信号是在,raft.node.run方法中,通过node.ntick channel接收然后调用raft.raft的tick:
// etcd-2.3.7/raft/node.go
// raftNode就是调用node的这个方法往里面写入周期的心跳信号
func (n *node) Tick() {
select {
case n.tickc <- struct{}{}:
case <-n.done:
}
}
func (n *node) run(r *raft) {
// ...
for {
// ...
select {
// ...
case <-n.tickc:
r.tick()
// ...
} // select
} // for
}
raft.node在收到tickc的信号之后最终会调用raft.tick,由于raft.tick是一个函数变量,在该raft处于不同的角色时tick指向的函数不一样,由于本节记录的选举,所以当前节点应该处于candidate阶段,这个阶段应该调用的是tickElection,从下面几个函数里面 可得到相关tick的变化信息:
// etcd-2.3.7/raft/raft.go
func (r *raft) becomeFollower(term uint64, lead uint64) {
r.step = stepFollower
r.re