TiKV 源码阅读 Leader 和 Follower 之间的心跳机制求助

【遇到的问题:问题现象及影响】
目前在阅读 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);
        }
    }
}

班门弄斧,有不准确的希望大家批评指正:我主要讲讲我理解

2 个赞

你好,我这两天看了心跳这块的代码,对机制有了一些了解:
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 发送一次心跳。
以上是我梳理的心跳机制,如果有错误的地方,还请大佬帮忙指正 :blush:

心跳这块我还有几个点没想明白,有没有路过的大佬给指点一下 :thinking:
1、我注意到代码中有多个地方调用 register_raft_base_tick() 函数进行注册,虽然有定时任务去重的识别,但为什么要在这么多的地方去注册呢?
2、在业务请求繁忙的情况下,通过 RaftPoller::light_end() 函数默认至少 1ms 发送一次 PeerMsg::Tick(PeerTick::Raft) 消息,这么频繁的驱动心跳任务,是为了在达到心跳消息间隔后尽可能快的发送心跳消息吗?
3、除了 RaftPoller::light_end() 函数会驱动定时任务执行,poll() 函数中接收需要驱动的状态机的地方 Poller::fetch_fsm() 函数在没有接收到状态机时也有可能驱动,但我觉得条件很苛刻,很难走到,不知道这块逻辑是为了适配什么情况

1 个赞