写点什么

TIKV BatchSystem 概述

  • 2024-03-15
    北京
  • 本文字数:3567 字

    阅读完需:约 12 分钟

作者: ylldty 原文来源:https://tidb.net/blog/bdf740f2

前言

TIKVBatchSystem 是实现 MultiRaft 的关键模块。在阅读本文章前,大家可以先参考一些官方博客:


TiKV 源码阅读三部曲(一)重要模块


TiKV 源码解析系列文章(二)raft-rs proposal 示例情景分析


TiKV 源码解析系列文章(十七)raftstore 概览


TiKV 源码解析系列文章(十八)Raft Propose 的 Commit 和 Apply 情景分析


官方的文章基本已经把 RAFT 的使用和 RaftStore 原理讲的比较清晰了。


通过这些文章,大家应该比较了解 RaftStore 的大概流程。在此基础上,本系列文章将会进一步对其细节进行个人的分析。


同时个人认为这个文章对 BatchSystem 的架构图比较清晰明了。


TiKV 的 BatchSystem



下面我们将要详细说明这个模块各个组件的细节与作用。

BatchSystem 与邮政业务系统

对于之前接触过 RAFT 的同学来说,大家都比较了解整个 RAFT 几乎所有的流程都是以 Msg 来驱动的,可以参考:TiKV 源码解析系列文章(二)raft-rs proposal 示例情景分析


我们可以把整个 BatchSystem 当做一个全国范围的邮局业务系统,把 Msg 当做用户想要邮寄的信件。接下来我们大概描述一下邮局业务系统各个组件的作用。


由于特殊的原因,这个邮件系统做出了限制,同一个时间全国内发往一个省 (Region) 的信件,一次只能送一个:即前一个信件 (Msg) 没有处理完毕前,发往同一个省的信件只能排队等待。不同的省可以并发邮寄。

FSM

我们可以简单的把 Msg 当做一个信件,而把 FSM 当做信封。和现实世界有些不同,并不是每个信件都有一个信封供它使用。而是每个省只有一个信封可用,有几个省就有几个信封。


FSM 信封上面提前标明了目标省地址 (ReginID),不断重复使用。



其结构是这样的:


pub struct Fsm<EK: KvEngine, ER: RaftEngine> {    ...    receiver: Receiver<Msg>,    ...}
复制代码


结构非常简单,receiver 仅仅是一个 channel,用于存放 Msg


特别的,有一种信件 (ControlMsg) 比较特殊,它有专门的信封 (ControlFsm) 对其提供邮寄服务。

Mailbox

有了信件 (Msg) 与信封 (FSM),我们还需要邮箱 (Mailbox)。和信封类似,也是每个省 (Region) 仅有一个邮箱 (Mailbox) 可用。


每当邮箱 (Mailbox) 收到一个信件 (Msg),那么它负责找到对应省的信封 (FSM),把信件 (Msg) 放到信封 (FSM) 里面去,再将信封 (FSM) 投递到邮局的运转中心去。


当然我们前面也说过,可能对应省的信封 (FSM) 正在运送其他信件 (Msg),这个时候需要告知邮寄失败。



其结构为:


pub struct BasicMailbox<Owner: Fsm> {    sender: mpsc::LooseBoundedSender<Owner::Message>,    state: Arc<FsmState<Owner>>,}
复制代码


sender 对应上面 FSMreceiver,负责把 Msg 传递给 FSM


state 可以看做 FSM 对象和 FSM 当前的状态(即是否正在运送其他 Msg),Msg 发送给 FSM 后,Mailbox 还需要将 FSM 发送给 运转中心


特殊信件 (ControlMsg) 拥有特殊信封 (ControlFsm),自然也有特殊的 Mailbox 对应。

Scheduler

前一个小节里面 Mailbox 所说的运转中心指的就是 Scheduler


Scheduler 的作用非常简单,它负责接收信封 FSM,然后再投递给多个 快递车。我们前面说过,信封FSM 的数量和省 Regions 的数量是一对一的,但是为了节约成本,快递车的数量可能是少于省 Regions 的数量的。Scheduler 收到信封 FSM 后,就负责找到空闲的快递车,然后将 FSM 投递到快递车,让它送到对应的省地址去。


因此,其结果也很简单:


pub struct Scheduler<N: Fsm, C: Fsm> {    pub(crate) sender: Sender<FsmTypes<N, C>>,    ...}
复制代码


无非就是一个用于发送 FSMsender,其实就是一个 channel 的发送端。快递车哪个空闲了,就通过 channel 的接收端获取 FSM 来进行处理。


特别地,对于特殊信件 (ControlMsg) 特殊信封 (ControlFsm),自然有特殊的 Scheduler 对应:ControlScheduler

Poller

上面 Scheduler 所说的 快递车 就是这个 PollerPoller 的数量是可配置的,如果系统负载量不大,快递车没有必要拥有那么多,平白造成空转系统消耗。


多个快递车 Poller 共享运转中心 Scheduler channel 的任务屏幕 (receiver)。只要 Scheduler 通过 sender 在任务屏幕 (receiver) 展示了信件 FSM 运送任务,多个 Poller 就需要通过任务屏幕 (receiver) 竞争获取。



同时 Pollerhandle 成员变量需实现两个重要的接口:handle_normalhandle_control。这两个接口用于识别信封上面的省地址 (RegionID),进而向该省 (Region Raft ) 投递信件 (Msg)


因此,其结构为:


pub struct Poller<N: Fsm, C: Fsm, Handler> {    ...    pub fsm_receiver: Receiver<FsmTypes<N, C>>,    pub handler: PollHandler,    ...}
pub trait PollHandler<N, C>: Send + 'static { /// This function is called when the control FSM is ready. /// /// If `Some(len)` is returned, this function will not be called again until /// there are more than `len` pending messages in `control` FSM. /// /// If `None` is returned, this function will be called again with the same /// FSM `control` in the next round, unless it is stopped. fn handle_control(&mut self, control: &mut C) -> Option<usize>;
/// This function is called when some normal FSMs are ready. fn handle_normal(&mut self, normal: &mut impl DerefMut<Target = N>) -> HandleResult; }}
复制代码

Router

目前好像邮局业务系统的整个流程都比较清晰了,用户将信件 (Msg) 投递给对应省 (Region) 的邮箱 MailboxMailbox 将信件放入信封 FSM,然后把信封发送给邮局运转中心 Scheduler,运转中心 SchedulerFSM 放到任务屏幕,多个快递车 (Poller) 竞争任务,然后使用自己的 handleFSM 进行处理,最后将信件 (Msg) 成功运送到 RegionRaft 系统。


但是好像还有一个问题,用户手里只有信件,也知道想要投递到哪个省 Region,但是每次都要去找对应 Region 的邮箱 (Mailbox) 好像有点麻烦。


因此,组件 Router 专门负责路由 Mailbox 这一个环节,用户只需要将信件 (Msg) 交给 RouterRouter 帮忙定位邮箱 (Mailbox) 的位置,对邮箱 (Mailbox) 进行投递操作。



其结构为:


pub struct Router<N: Fsm, C: Fsm, Ns, Cs> {    normals: Arc<DashMap<u64, BasicMailbox<N>>>,    pub(super) control_box: BasicMailbox<C>,        pub(crate) normal_scheduler: Ns,    pub(crate) control_scheduler: Cs,
...}
pub fn try_send( &self, addr: u64, msg: N::Message, ) -> Either<Result<(), TrySendError<N::Message>>, N::Message> { let mut msg = Some(msg); let res = self.check_do(addr, |mailbox| { let m = msg.take().unwrap(); match mailbox.try_send(m, &self.normal_scheduler) { Ok(()) => Some(Ok(())), ... } }); match res { CheckDoResult::Valid(r) => Either::Left(r), CheckDoResult::Invalid => Either::Left(Err(TrySendError::Disconnected(msg.unwrap()))), CheckDoResult::NotExist => Either::Right(msg.unwrap()), } }
复制代码


可以看到,Router 基本包含了 Mailboxscheduler 组件。


关键函数是 try_send,参数是 MsgRegionID,该函数负责传递 RegionID 调用 check_do 获取具体某个 mailbox,然后向 mailbox 传递 Msgscheduler 进行消息投递,完成整个 BatchSystem 对某个 Region Raft 的消息传达。


除此以外,对于特殊的 ControlMsgRouter 还有 send_control 接口。

BatchSystem

好了,讲到这里,BatchSystem 的整体架构已经比较清楚了:


  • MailboxFSM 共用一套 channelsenderreceiver,用于 Msg 的传递;

  • schedulerPoller 共用一套 channelsenderreceiver,用于 FSM 的传递

  • Router 包含 Mailboxscheduler 组件


由于 Router 已经包含了 Mailboxscheduler 组件,Mailbox 包含了 FSM 组件,因此 BatchSystem 的结构只需要 RouterPoller


pub struct BatchSystem<N: Fsm, C: Fsm> {    router: BatchRouter<N, C>,    ...    pool_state_builder: Option<PoolStateBuilder<N, C>>,}
复制代码

总结

TIKVRaftStore 系统承担着 Multi Raft 模块多个 Region Msg 的流通与交互,是保证分布式数据库的强力保障。本文为了让大家更加直观的理解 RaftStore 系统,采用了邮政系统来类比。但是实际上,RaftStore 系统不止本文所描述的如此简单,还涉及到 RaftLogEngineRaftApplySystem 等等子功能。我们有机会会在接下来的系列文章继续详细展开描述。


发布于: 40 分钟前阅读数: 4
用户头像

TiDB 社区官网:https://tidb.net/ 2021-12-15 加入

TiDB 社区干货传送门是由 TiDB 社区中布道师组委会自发组织的 TiDB 社区优质内容对外宣布的栏目,旨在加深 TiDBer 之间的交流和学习。一起构建有爱、互助、共创共建的 TiDB 社区 https://tidb.net/

评论

发布
暂无评论
TIKV BatchSystem 概述_TiDB 底层架构_TiDB 社区干货传送门_InfoQ写作社区