ceph Async模块提供一种异步收发消息的机制,最底层的异步机制可以查看博文 Ceph源码分析之Async模块:1、异步通信核心模块EventCenter+Epoll,上层的收发消息机制可以查看博文Ceph源码分析之Async模块:2、上层通信模型。因此这里主要介绍的是整个异步消息机制的中间层,即ceph抽象网络协议栈。在L版的ceph中,支持3种Async类型的网络协议栈
- PosixNetworkStack
- RMDAStack
- DPDKStack
本文主要介绍的是基于Posix协议栈的收发消息模型
收发消息交互模型图
下面介绍下几个重要的模块及流程
Processor
Processor是Messenger用来同worker交互的层,每个processor实例对接一个worker实例,从上面我们可以看出实际上Messenger是可以通过stack同worker交互的,那设计Processor主要是为了将那些跟消息传输无关的操作接口封装起来(比如:端口bind,accept连接),这样stack只管做消息传输类型接口的封装即可。
AsyncConnection
每个来自客户端的连接都会分配一个AsyncConnection实例来维护底层已经建立的socket连接,Messenger端会维护一个全局的连接表,用来供上层应用使用。
- 负责已经建立的连接socket的读写事件及对于的读写事件回调处理,包含了ceph协议栈状态机以及对消息的处理。
- 读写是直接调用的系统的read/write,不经过Net_handler模块
- fast 类型dispatcher,直接将MSG会直接转给Messenger
- 普通类型的dispatcher,将MSG放入DispatcherQueue mqueue队列
- 每个连接都有一个worker,而这个worker就是当初接受连接的worker,一个worker会负责所有从它建立的连接。
AsyncConnection的维护的ceph状态机待后续分析
DispatchQueue
DispatchQueue的职责就是维护mqueue队列,将mqueue队列的内容按优先级转给Messenger
- 消息队列mqueue
开放enqueue入队列接口,用来将MSG放入队列mqueue队列(优先级队列)。 - 线程
DispatchQueue内部通过一个线程去处理mqueue,在mqueue没有数据时,会进入线程条件变量等待使进程睡眠。如果有数据入队列时,则会唤醒。
Net_handler
从上图可以看出,Net_handler模块只是负责部分socket的操作,基于这些基础的socket操作,封装了几个接口,比如:
- create_socket
- connect
- generic_connect
- nonblock_connect
- set_close_on_exec
- set_nonblock
- set_priority
- set_socket_options
对于已经建立连接的socket的读写则是有AsyncConnection直接通过PosixConnectedSocketdImpl直接调用read/write接口
Worker
Worker被设计寓意一个线程的意思,每个Worker通过EventCenter维护一个自己的事件表。
NetworkStack
NetworkStack被设计用来接受不同的类型的网络传输,比如dpdk、rdma、posix,上图是以PosixStack为例子画的。每个PosixStack主要用来负责管理worker,比如批量创建worker、开启worker监听、开启worker线程、关闭worker线程等。
bind阶段
bind阶段不会涉及到event center事件,它只是在每个worker中新建监听socket,并将worker新建的socket封装后传给每个processor的listen_socket变量。bind之后ceph还不会接受来自客户端的连接请求,接受连接请求的是在ready阶段,Messenger的bind阶段对应的是每个worker线程的listen阶段,worker线程的listen阶段的操作步骤,如下:
- net._create_socket
- net.set_nonblock
- net.set_close_on_exec
- net.set_socket_options
- ::bind
- ::listen
ready阶段
应用添加dispatcher给Messenger的时候,会触发Messenger进入ready()阶段,ready阶段是真正开始接受连接并处理消息的阶段。ready阶段分为两个步骤:
- stack->start()
根据worker数,创建对应的std::thread线程,线程函数负责处理当前worker的event center事件处理 - processor->start()
调用每个processor的start方法。
- 给每个worker线程创建监听socket的fd读事件
- 读事件的回调则是通过accept新建一个连接socket,并将新连接socket上传给AsyncMessenger,AsyncMessenger会给新的连接socket分配一个AsyncConnection实例,并将新连接socket添加到accepting_conns中。分配完AsyncConnection实例之后,会马上让连接进入消息处理阶段,这里存在一个线程死锁的逻辑:不要直接在当前线程上下文下去开始处理消息,理由是当前线程上下文会占了一个从Messenger带来的线程锁,而如果在这个上下文下处理消息,一旦消息处理异常导致线程挂死,意味着Messenger的那把线程锁将无人释放,从而导致整个worker集群全部阻塞在加锁状态。因此这里需要添加一个external外部事件,让worker线程先行退出当前堆栈,等待下次事件处理的时候再处理接受消息,因为这时候的线程上下文是不带锁的。
结束语
Async系列博文只是指出了大家在理解ceph Async网络模块的时候一些痛点,本系列博文的受众更偏向于打算自己阅读一遍或者正在阅读ceph Async模块的人。这个系列的3篇都是作者在阅读Async模块之后理解和总结出来的一些痛点于大家分享,对于AsyncConnection的状态机这块等待后续有时间再更新。