【遇到的问题:问题现象及影响】
目前在阅读 TiKV 的源码,看的是 TiKV 库主线的代码。
关于 Leader 与 Follower 的心跳机制,了解这块代码逻辑的大佬能否指点一下?
当前的几个发现,
1、raft-rs 库中 RawNode::ping() 应该是执行心跳发送的,猜测是 tikv 库中PeerTick::CheckPeerStaleState 这个 tick 任务在 raft group 无业务时执行心跳操作
2、Prewrite 这个业务请求处理过程中有更新心跳信息的操作,猜测是有业务请求的情况下通过业务请求更新了心跳信息
基于以上两点拼凑出了一些心跳机制,不确定有没有遗漏或者看错的地方
register_raft_base_tick() 这个函数是 raft 心跳的动力来源
on_raft_base_tick() 这个函数是处理的地方
raft-rs 可以理解成 吃 raft 消息,吐 raft 消息,至于 raft 消息怎么发送给目标端,都是由外面处理的。
比如说 这样调用一次 self.fsm.peer.raft_group.tick(),其实相当于利用 raft 库生成一个新的raft消息
最终消息是在 collect_ready 里面从 let mut ready = self.raft_group.ready(); 取出来,发送给目标端的。
tikv 处理消息的主逻辑基本上是这些:
pub fn poll(&mut self) {
while run && self.fetch_fsm(&mut batch) {
if batch.control.is_some() {
let len = self.handler.handle_control(batch.control.as_mut().unwrap());
if batch.control.as_ref().unwrap().is_stopped() {
batch.remove_control(&self.router.control_box);
} else if let Some(len) = len {
batch.release_control(&self.router.control_box, len);
}
}
for (i, p) in batch.normals.iter_mut().enumerate() {
let res = self.handler.handle_normal(p);
}
self.handler.light_end(&mut batch.normals);
self.handler.end(&mut batch.normals);
}
}
}
班门弄斧,有不准确的希望大家批评指正:我主要讲讲我理解
你好,我这两天看了心跳这块的代码,对机制有了一些了解:
1、register_raft_base_tick() 函数是用来注册 Tick 任务的,其中的定时任务包括了心跳任务。在 TiKV 服务启动时每个 Region 都会调用这个函数来注册定时任务;
2、注册的定时任务都保存在 BatchSystem 的数据结构里,BatchSystem 的 poll() 函数循环驱动状态机处理请求,在每一次循环中调用 RaftPoller::light_end() 函数处理一些轻量级任务,其中就包括注册的 Tick 任务,向 Region 对应的状态机发送 PeerMsg::Tick(PeerTick::Raft) 消息;
3、状态机收到消息后在 poll() 函数驱动下会经过你提到的 on_raft_base_tick() 函数到 raft-rs 库的 RawNode::tick() 函数,然后当前 Peer 是 Leader 的话就会去判断是否需要发送心跳消息。默认至少 2s 发送一次心跳。
以上是我梳理的心跳机制,如果有错误的地方,还请大佬帮忙指正
心跳这块我还有几个点没想明白,有没有路过的大佬给指点一下
1、我注意到代码中有多个地方调用 register_raft_base_tick() 函数进行注册,虽然有定时任务去重的识别,但为什么要在这么多的地方去注册呢?
2、在业务请求繁忙的情况下,通过 RaftPoller::light_end() 函数默认至少 1ms 发送一次 PeerMsg::Tick(PeerTick::Raft) 消息,这么频繁的驱动心跳任务,是为了在达到心跳消息间隔后尽可能快的发送心跳消息吗?
3、除了 RaftPoller::light_end() 函数会驱动定时任务执行,poll() 函数中接收需要驱动的状态机的地方 Poller::fetch_fsm() 函数在没有接收到状态机时也有可能驱动,但我觉得条件很苛刻,很难走到,不知道这块逻辑是为了适配什么情况