BatchSystem是TiKV实现multi-raft的基石,本文介绍BatchSystem的实现。BatchSystem本身是一个抽象出来的通用的模块,不牵涉业务逻辑(multi-raft),方便单独介绍。
总体结构 (1)
Fsm
、BasicMailbox
与Scheduler
(2)
Fsm
是一个trait,由业务逻辑来实现。Fsm
的运转是靠Message
来驱动的,所以就有了BasicMailbox
。一个Fsm
和一个BasicMailbox
绑定:BasicMailbox
用于接收驱动Fsm
的Message
;Fsm
是发到BasicMailbox
的Message
的owner。
1 | pub struct BasicMailbox<Owner: Fsm> { |
其中sender
提供发送Message
的功能,state
里面实际上包裹了一个Fsm
(除了Fsm
本身还包含Fsm
的状态)。可以从BasicMailbox
中把Fsm
拿出来(take_fsm
函数),也可以还回去(release
函数)。
当需要驱动Fsm
的时候,就通过以下两个函数向关联的BasicMailbox
发一个Message
:
1 | impl<Owner: Fsm> BasicMailbox<Owner> { |
发送到BasicMailbox
的Message
立即驱动Fsm
吗?不是的,Fsm
经过调度,才能最终被Message
驱动。这也是force_send
和try_send
的第二个参数的用途。这两个函数是这样工作的:把msg
放入sender;把Fsm
从BasicMailbox
里拿出来,扔给Scheduler
,这时Fsm
处于NOTIFYSTATE_NOTIFIED
状态(调度之前,Fsm
在BasicMailbox
中的时候处于NOTIFYSTATE_IDLE
状态。将来,Fsm
被Message
之后就会被还回BasicMailbox
,那时又回到NOTIFYSTATE_IDLE
状态。
Scheduler
里面包含一个channel
的发送端。当BasicMailbox
接收到一个Message
的时候,就把对应的Fsm
扔到这个channel
里。下文再说channel
的接收端以及如何消费里面的Fsm
。
Router
(3)
Router
包含上文介绍过的3个组件:
BasicMailbox
: 一个Control BasicMailbox
和若干个Normal BasicMailbox
。Router
提供的接口是:向Control BasicMailbox
或某个(由参数addr
指定)Normal BasicMailbox
发送一个Message
;除此之外,还有广播,即向把Message
发给所有BasicMailbox
;Fsm
: 每个BasicMailbox
都关联一个Fsm
,即有一个Control Fsm
和若干个Normal Fsm
;Scheduler
: 一个Control Scheduler
和一个Normal Scheduler
;当一个Message
发到一个BasicMailbox
的时候,就把对应的Fsm
拿出来,让Scheduler
调度(即放进Scheduler
的channel
里);
Poller
与PollHandler
(4)
前文说过,每个Scheduler
有一个channel
,有Message
发给一个Fsm
(发送到其关联的BasicMailbox
)的时候,Fsm
就被放进channel
。Poller
就是这个channel
的消费者:
1 | struct Poller<N: Fsm, C: Fsm, Handler> { |
其中fsm_receiver
就是channel
的接收端。一个Poller
就是一个线程,线程中运行Poller::poll()
函数。这个函数是一个大循环,每一轮都是从fsm_receiver
中接收一批Fsm
(可能是Control Fsm
也可能是Normal Fsm
,这些Fsm
上有Message
),由结构体Batch
表示,然后处理它们。BatcySystem的名字就来源于此:一次接收并处理一批Fsm
。详细点说:
- 上面router里有$M$个
Fsm/BasicMailbox
(在TiKV中,一个region对应一个Normal Fsm/BasicMailbox
,此外还有一个Control Fsm/BasicMailbox
); - 下面线程池里有$N$个
Poller
; - 当一个
Fsm
(无论Normal Fsm
还是Control Fsm
)的BasicMailbox
接收到Message
的时候,这个Fsm
就被Scheduler
发给线程池去处理; - 每个线程(
Poller::pool()
)一次拿一批Fsm
来处理,处理完之后再还给BasicMailbox
(等BasicMailbox
再接收到Message
的时候,再处理);
注意,这里面有两个消息通道:
Fsm
被发往Poller
的消息通道:发送端在Scheduler
里,接收端在Poller
里;
Message
被发往Fsm
的消息通道:发送端在BasicMailbox
里,接收端在Fsm
里(PeerFsm
和StoreFsm
的receiver
);
如上所述,Poller::poll()
的主要工作是从fsm_receiver
中接收一批Fsm
并处理它们。但,处理的逻辑是业务层的事,所以这里由一个trait(PollHandler
)表示处理逻辑。由于fsm_receiver
接收到的既有Control Fsm
也有Normal Fsm
,所以PollHandler
定义了handle_control
和handle_normal
接口。
1 | pub trait PollHandler<N, C> { |
Poller
接收到一批Fsm
之后:
- 调用
begin()
; - 对每个
Fsm
调用handle_control()
或handle_normal()
; - 调用
end()
;
这些都是业务层实现的。可以想象,业务层实现handle_normal()
或handle_control()
的时候,应该是从Fsm
接收Message
,处理它,驱动Fsm
。
BatchSystem
(5)
BatchSystem
就是把上述东西组合起来,其spawn
函数的工作就是创建$N$个Poller
实例,并启动线程。
小结 (6)
本文梳理一下BatchSystem的逻辑,后文就不用再陷入这些细节,聚焦于TiKV的业务逻辑:一个消息发给一个PeerFsm
或StoreFsm
的时候,直接去看对应的PollHandler
实现即可。