ceph 网络模块 源码分析 三
第一篇和第二篇主要讲了ceph网络模块的基材框架:Messenger和Dispatcher.本篇主要讲解下Messenger的主要实现方式之一: SimpleMessenger,它也是目前最稳定的也,应用于生产环境的实现.
SimpleMessenger
Messenger是一个接口,它定义的了消息发送和接受的等各种接口.SimpleMessenger是Messenger的一个具体实现,它由三个重要的模块组成:
- Accepter: 负责监听客户端连接请求
- DispatchQueue: 接收消息的缓冲队列
- Pipe: 负责具体的接受和发送消息
Accepter
Accepter比较简单,它继承自Thread类,主要负责监听客户端的连接请求.它有两个重要函数bind()和entry().前者负责socket的创建,绑定和监听.后者使用poll监听socket上的请求连接,如果有新请求到来,那么调用Messenger的add_accept_pipe(sd)把socket传递给Pipe,用于数据通信.
int Accepter::bind(const entity_addr_t &bind_addr, const set<int>& avoid_ports)
{
...
// create socket
listen_sd = ::socket(family, SOCK_STREAM, 0);
...
// bind socket and ip addr
::bind(listen_sd, (struct sockaddr *) &listen_addr.ss_addr(), listen_addr.addr_size());
...
// listen
::listen(listen_sd, 128);
return 0;
}
void *Accepter::entry()
{
...
struct pollfd pfd;
pfd.fd = listen_sd;
pfd.events = POLLIN | POLLERR | POLLNVAL | POLLHUP;
while (!done) {
...
int r = poll(&pfd, 1, -1);
...
if (pfd.revents & (POLLERR | POLLNVAL | POLLHUP))
break;
if (done) break;
...
// accept
entity_addr_t addr;
socklen_t slen = sizeof(addr.ss_addr());
// 接受连接请求
int sd = ::accept(listen_sd, (sockaddr*)&addr.ss_addr(), &slen);
if (sd >= 0) {
...
// 将accept()返回的socket传递给Pipe类,用于数据通信.
msgr->add_accept_pipe(sd);
}
return 0;
}
Pipe
Pipe是SimpleMessenger中最复杂的组件,它主要包括两个线程:reader_thread和writer_thread,顾名思义,前者负责从socket中读取数据,后者负责向socket中写入数据.在ceph中,对于每一个用于数据通信的socket,或者一个连接,都会新建一个Pipe对象,并启动reader_thread线程.然后将相应的Pipe对象添加到SimpleMessenger的Pipe列表中,.这主要在Messenger的add_accept_pipe()函数中实现.但是一个连接2个线程,对于存在大量连接的系统来说,无疑是资源的浪费.
Pipe *SimpleMessenger::add_accept_pipe(int sd)
{
lock.Lock();
Pipe *p = new Pipe(this, Pipe::STATE_ACCEPTING, NULL);
p->sd = sd;
p->pipe_lock.Lock();
p->start_reader(); // 启动Pipe的读线程接收数据
p->pipe_lock.Unlock();
pipes.insert(p);
accepting_pipes.insert(p);
lock.Unlock();
return p;
}
Pipe的reader_thread线程读取的数据或者消息的处理有两种策略,一种是快速处理,它由reader_thread线程直接交给dispatcher.ms_fast_dispatch()进行处理,另一种是添加到缓冲队列DispatchQueue中,然后由dispatch_thread线程交给dispatcher.ms_dispatch()进行处理.这两种方式相对,前一种办法效率比较高,但是需要消息本身满足一定的条件.
DispatchQueue
DispatchQueue是用于消息缓冲的类,它主要用于缓冲Pipe中reader_thread线程接受的消息.它也定义了一个dispatch_thread线程,用于处理dispatch_queue队列中的消息.它主要是调用dispatcher.ms_dispatch()对消息进行处理,如下所示:
void DispatchQueue::entry() {
...
pre_dispatch(m);
// 调用Dispatcher的ms_dispatch处理消息
msgr->ms_deliver_dispatch(m);
post_dispatch(m, msize);
...
}
SimpleMessenger的启动
这里以monitor守护进程的启动为例.
Messenger *msgr = Messenger::create();
msgr->bind()
Monitor *mon = new Monitor();
...
msgr->start()
mon->init()
- 新建Messenger实例,然后bind相应的socket用于数据通信.
- 新建mon实例,它是Dispatcher的子类,用于实际的数据处理.
- 启动Messenger主要用于启动其中的readper线程,用于回收过期不用的pipe.
- 最后在monitor的初始化中启动以上介绍accepter线程和dispatch_queue线程.
int Monitor::init() {
...
messenger->add_dispatcher_tail(this);
...
}
void Messenger::add_dispatcher_tail(Dispatcher *d) {
bool first = dispatchers.empty();
dispatchers.push_back(d);
if (d->ms_can_fast_dispatch_any())
fast_dispatchers.push_back(d);
if (first)
ready();
}
void SimpleMessenger::ready()
{
ldout(cct,10) << "ready " << get_myaddr() << dendl;
dispatch_queue.start();
lock.Lock();
if (did_bind)
accepter.start();
lock.Unlock();
}
- 最后是reader_thread和writer_thread线程的启动.读线程是服务端接受连接请求的时候启动的,即前面介绍的add_accept_pipe()中.写线程的服务端调用send_message()发送消息的过程中启动的,在connect_rank()函数中实现
SimpleMessenger的终止
msgr->wait();
delete msgr;
delete mon;
当进程中止的时候,会调用Messenger的wait()函数,它会依次关闭accepter, reader_thread, writer_thread, dispatch_thread, reaper_thread以及其他相关线程,然后delete msgr和mon即可.