ETCD数据库源码分析——从raftNode的start函数说起_初始化

如上图所示,raftNode是真正操纵ETCD RAFT API的模块,充当etcd-raft模块与上层模块之间交互的桥梁,其定义了如下成员。其中msgSnapC通道用于接收和发送快照、applyc通道用于发送待应用的Entry记录、readStateC用于向上层模块发送ReadState、ticker用于向raft模块发送定时脉冲tick、td用于检测发往同一节点的两次心跳消息是否超时。raftNodeConfig后续再说,该篇主要说明ticker模块。

type raftNode struct {
lg *zap.Logger
tickMu *sync.Mutex
raftNodeConfig
msgSnapC chan raftpb.Message // a chan to send/receive snapshot
applyc chan toApply // a chan to send out apply
readStateC chan raft.ReadState // a chan to send out readState
ticker *time.Ticker // utility
td *contention.TimeoutDetector // contention detectors for raft heartbeat message
stopped chan struct{}
done chan struct{}
}

初始化raftNode结构体的函数是newRaftNode,raftNodeConfig包含了已经初始化的网络transport、预写日志Storage、raftStorage以及最重要的raft模块,这些模块提供相应的接口给raftnode使用以协助完成raft log发送、持久化、多数复制等功能。

func newRaftNode(cfg raftNodeConfig) *raftNode {    
raft.SetLogger(lg) // 初始化logger(略)
r := &raftNode{ lg: cfg.lg, tickMu: new(sync.Mutex),
raftNodeConfig: cfg, td: contention.NewTimeoutDetector(2 * cfg.heartbeat), // set up contention detectors for raft heartbeat message. expect to send a heartbeat within 2 heartbeat intervals.
readStateC: make(chan raft.ReadState, 1),
msgSnapC: make(chan raftpb.Message, maxInFlightMsgSnap),
applyc: make(chan toApply),
stopped: make(chan struct{}), done: make(chan struct{}),
}
// 这里是重点
if r.heartbeat == 0 { r.ticker = &time.Ticker{} // 如果heartbeat为零,则使用默认&time.Ticker{}
} else { r.ticker = time.NewTicker(r.heartbeat) } // 否则,需要设定时间间隔为r.heartbeat
return r
}

start函数会启动一个协程运行主要的业务逻辑,这里我们会看到如果定时器超时时,select会检测到该通道,调用raftNode接口的tick函数。该tick函数会获取tickMu锁,然后调用Node接口提供的Tick函数,也就是图中的raft.Node提供的Tick函数。

func (r *raftNode) start(rh *raftReadyHandler) {
internalTimeout := time.Second
go func() {
defer r.onStop()
islead := false
for {
select {
case <-r.ticker.C:
r.tick()

// raft.Node does not have locks in Raft package
func (r *raftNode) tick() {
r.tickMu.Lock()
r.Tick()
r.tickMu.Unlock()
}

raft.Node提供的Tick函数会向n.tickc通道中写入struct{}{},而在raft.Node中会有协程执行run函数,如图中raft.Node的run协程所示,其会执行监听n.tickc通道的代码,然后执行​​n.rn.Tick()​​​。而这里​​n.rn.Tick()​​执行的是rawnode所包含成员raft结构体的Tick成员函数,该函数会根据raft角色执行不同的tick函数:tickElection或tickHeartbeat。

ETCD数据库源码分析——从raftNode的start函数说起_成员函数_02

// Tick advances the internal logical clock by a single tick.
func (rn *RawNode) Tick() {
rn.raft.tick()
}