ceph早些版本默认的网络框架是SimpleMessenger,但是SimpleMessenger是多线程模型,对于较大的集群,会产生很多线程,对主机的要求也比较高。当前的版本(mimic)默认的messenger框架是AsyncMessenger,AsyncMessenger可以选择多路IO复用的epoll模型来处理连接和消息,这大大的减少了由于连接数过多带来的主机资源消耗。下面以OSD的ms_public来介绍AsyncMessenger网络模块。
创建ms_public,并初始化内部的一些模块
(1)创建AsyncMessenger
Messenger *ms_public = Messenger::create(g_ceph_context, public_msg_type,
entity_name_t::OSD(whoami), “client”,
getpid(),
Messenger::HAS_HEAVY_TRAFFIC |
Messenger::HAS_MANY_CONNECTIONS);
在create函数中会根据public_msg_type来选择创建不同类型的Messenger,Mimic默认为"async+posix",因此会选择AsyncMessenger,返回如下:
return new AsyncMessenger(cct, name, type, std::move(lname), nonce);
(2)初始化内部的一些模块
stack = NetworkStack::create(cct, type); //stack为PosixNetworkStack
return std::make_shared<PosixNetworkStack>(c, t);//t = "posix" PosixNetworkStack的构造函数, 其继承了NetworkStack类
PosixNetworkStack::PosixNetworkStack(CephContext *c, const string &t)
: NetworkStack(c, t)
{
vector<string> corestrs;
get_str_vec(cct->_conf->ms_async_affinity_cores, corestrs); //为空,所以下面的for循环不进入
for (auto & corestr : corestrs) {
string err;
int coreid = strict_strtol(corestr.c_str(), 10, &err);
if (err == "")
coreids.push_back(coreid);
else
lderr(cct) << __func__ << " failed to parse " << corestr << " in " << cct->_conf->ms_async_affinity_cores << dendl;
}
}
//运行PosixNetworkStack构造函数之前,要运行NetworkStack构造函数
NetworkStack::NetworkStack(CephContext *c, const string &t): type(t), started(false), cct(c)
const uint64_t InitEventNumber = 5000;
num_workers = cct->_conf->ms_async_op_threads; //3
for (unsigned i = 0; i < num_workers; ++i) { //3
Worker *w = create_worker(cct, type, i); //type为posix
return new PosixWorker(c, i);
/*
PosixWorker(CephContext *c, unsigned i) : Worker(c, i), net(c) {}
*/
w->center.init(InitEventNumber, i, type); //EventCenter center;
type = t;
idx = i;
driver = new EpollDriver(cct); //explicit EpollDriver(CephContext *c): epfd(-1), events(NULL), cct(c), size(0) {}
driver->init(this, n); //n=5000
events = (struct epoll_event*)malloc(sizeof(struct epoll_event)*nevent); //nevent就是InitEventNumber 5000
memset(events, 0, sizeof(struct epoll_event)*nevent);
epfd = epoll_create(1024);
size = nevent;
file_events.resize(n);
nevent = n; //n就是InitEventNumber
int fds[2];
pipe(fds);
notify_receive_fd = fds[0];
notify_send_fd = fds[1];
net.set_nonblock(notify_receive_fd);
fcntl(sd, F_GETFL)
fcntl(sd, F_SETFL, flags | O_NONBLOCK) //固定用法,在原来的设置的基础上加一个O_NONBLOCK
net.set_nonblock(notify_send_fd); //实现同上
workers.push_back(w); //vector<Worker*> workers; 定义在NetworkStack上
}
1)AsyncMessenger中含有一个PosixNetworkStack变量,其继承NetworkStack类,在初始化PosixNetworkStack变量的时候会执行NetworkStack的构造函数
2)在NetworkStack构造函数中会创建PosixWorker,个数由ms_async_op_threads参数取控制,默认为3。每个PosixWorker包含一个center(EventerCenter),center中实现了底层epoll网络模型。
3)每个epoll模型最多可包含5000个描述符,每次最多可同时监听1024个描述符。同时每个center还包含一对pipe变量(notify_receive_fd和notify_send_fd)和对应的回调函数。epoll最开始会监听notify_receive_fd描述符,监听notify_receive_fd描述符是为了处理extern事件的(下面会说到),注意这里只是做了一些初始化操作,仍然没有开始监听事件。
启动PosixNetworkStack中处理线程process_events
stack->start(); //启动处理线程process_events
for (unsigned i = 0; i < num_workers; ++i) { //3
if (workers[i]->is_init())
continue;
std::function<void ()> thread = add_thread(i);
spawn_worker(i, std::move(thread));
threads.resize(i+1);
threads[i] = std::thread(func); //创建线程,并调用func,即开辟线程运行上面的sambda表达式
}
started = true;
lk.unlock();
for (unsigned i = 0; i < num_workers; ++i)
workers[i]->wait_for_init();
std::unique_lock<std::mutex> l(init_lock); //condition_variable在notify时,会解锁锁定的unique_lock,这里加一个while(!init)是为了防止wait_for_init提前运行完导致上面还没运行的线程卡死
while (!init)
init_cond.wait(l);
对于每一个PosixWorker,会做如下操作
1)利用add_thread返回真正的处理函数,spawn_worker负责创建线程并执行add_thread返回的处理函数。workers[i]->wait_for_init();是等待线程必要部分初始化完毕。
2)add_thread函数的调用栈如下
std::function<void ()> thread = add_thread(i);
Worker *w = workers[i];
return [this, w]() {
char tp_name[16];
sprintf(tp_name, "msgr-worker-%u", w->id);
ceph_pthread_setname(pthread_self(), tp_name);
const unsigned EventMaxWaitUs = 30000000;
w->center.set_owner(); //前面在center
owner = pthread_self(); //获得thread的ID,和pthread_create返回的一样
global_centers = &cct->lookup_or_create_singleton_object<EventCenter::AssociatedCenters>("AsyncMessenger::EventCenter::global_center::" + type, true);
global_centers->centers[idx] = this;
if (driver->need_wakeup())
notify_handler = new C_handle_notify(this, cct); //仅仅从notify_receive_fd读出数据,没有其他作用
create_file_event(notify_receive_fd, EVENT_READABLE, notify_handler); //notify_receive_fd是管道中的接收端,给该描述符增加EVENT_READABLE事件
EventCenter::FileEvent *event = _get_file_event(fd);
return &file_events[fd];
driver->add_event(fd, event->mask, mask);
op = cur_mask == EVENT_NONE ? EPOLL_CTL_ADD: EPOLL_CTL_MOD;
ee.events = EPOLLET;
add_mask |= cur_mask; /* Merge old events */
if (add_mask & EVENT_READABLE)
ee.events |= EPOLLIN;
if (add_mask & EVENT_WRITABLE)
ee.events |= EPOLLOUT;
ee.data.u64 = 0;
ee.data.fd = fd;
epoll_ctl(epfd, op, fd, &ee)
event->mask |= mask;
if (mask & EVENT_READABLE)
event->read_cb = ctxt; //设置回调函数,前面传进来的notify_handler
if (mask & EVENT_WRITABLE)
event->write_cb = ctxt;
w->initialize();
//void PosixWorker::initialize(){} 为空函数
w->init_done();
init_lock.lock();
init = true;
init_cond.notify_all(); //这里是通知下面的wait_for_init退出
init_lock.unlock();
while (!w->done) { //主要处理函数
ceph::timespan dur;
int r = w->center.process_events(EventMaxWaitUs, &dur);
numevents = driver->event_wait(fired_events, &tv);
retval = epoll_wait(epfd, events, size, tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
numevents = retval;
fired_events.resize(numevents);
for (j = 0; j < numevents; j++) {
int mask = 0;
struct epoll_event *e = events + j;
if (e->events & EPOLLIN) mask |= EVENT_READABLE;
if (e->events & EPOLLOUT) mask |= EVENT_WRITABLE;
if (e->events & EPOLLERR) mask |= EVENT_READABLE|EVENT_WRITABLE;
if (e->events & EPOLLHUP) mask |= EVENT_READABLE|EVENT_WRITABLE;
fired_events[j].fd = e->data.fd;
fired_events[j].mask = mask;
return numevents;
for (int j = 0; j < numevents; j++)
event = _get_file_event(fired_events[j].fd);
return &file_events[fd];
if (event->mask & fired_events[j].mask & EVENT_READABLE)
rfired = 1;
cb = event->read_cb;
cb->do_request(fired_events[j].fd);
if (event->mask & fired_events[j].mask & EVENT_WRITABLE)
if (!rfired || event->read_cb != event->write_cb)
cb = event->write_cb;
cb->do_request(fired_events[j].fd);
if (external_num_events.load()) {
external_lock.lock();
deque<EventCallbackRef> cur_process;
cur_process.swap(external_events); //拷贝外部事件
external_num_events.store(0); //外部事件技术清零
external_lock.unlock();
numevents += cur_process.size();
while (!cur_process.empty()) {
EventCallbackRef e = cur_process.front();
ldout(cct, 30) << __func__ << " do " << e << dendl;
e->do_request(0); //对于asyncmessage套接字绑定,则会调用C_submit_event::do_request
void do_request(uint64_t id) override {
f(); //对于asyncmessage套接字绑定,则会调用[this, &listen_addr, &opts, &r]() { r = worker->listen(listen_addr, opts, &listen_socket);} lambda表达式
//进而调用int PosixWorker::listen(entity_addr_t &sa, const SocketOptions &opt, ServerSocket *sock)
int listen_sd = net.create_socket(sa.get_family(), true);
s = ::socket(domain, SOCK_STREAM, 0)
::setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)
net.set_nonblock(listen_sd)
(flags = fcntl(sd, F_GETFL));
fcntl(sd, F_SETFL, flags | O_NONBLOCK)
net.set_close_on_exec(listen_sd);
net.set_socket_options(listen_sd, opt.nodelay, opt.rcbuf_size);
::bind(listen_sd, sa.get_sockaddr(), sa.get_sockaddr_len());
::listen(listen_sd, cct->_conf->ms_tcp_listen_backlog);
*sock = ServerSocket(std::unique_ptr<PosixServerSocketImpl>(new PosixServerSocketImpl(net, listen_sd))); //赋值 listen_socket
lock.lock();
cond.notify_all(); //通知等待的线程完成
done = true;
bool del = nonwait;
lock.unlock();
if (del)
delete this;
}
cur_process.pop_front();
}
}
w->perf_logger->tinc(l_msgr_running_total_time, dur);
}
w->reset();
w->destroy();
可以看到线程处理函数做了如下工作
1)设置notify_receive_fd描述符监听事件以及回调函数:程序中利用create_file_event函数来给notify_receive_fd(pipe中的接收端)设置EVENT_READABLE事件,同时设置对应的回调函数为notify_handler(C_handle_notify),这个回调函数仅仅是将pipe中的数据读出来,并无其他作用,因此它的功能仅仅是唤醒epoll_wait所在的等待线程(同时去处理extern事件)。这里有一个关键性的变量file_events(FileEvent),其保存了描述符对应的事件mask和读写回调函数。
2)调用init_done通知wait_for_init退出等待,因为wait_for_init会等待线程处理函数必要部分初始化结束,因此init_done会调用init_cond.notify_all();通知wait_for_init退出。
3)process_events负责执行epoll_wait,epoll_wait负责监听描述符到来的事件,如果所监听的描述符有事件发生,就从file_events中获取对应的回调函数。然后如果external_num_events不为0,就说明有external事件发生,然后运行external_events的回调函数,套接字的绑定就是用external事件来完成的。
4)注意此时默认有三个PosixWorker,因此有三个线程处理线程调用epoll_wait监听事件,但只有一个会监听listenfd。
processor初始化
unsigned processor_num = 1;
if (stack->support_local_listen_table()) //false
processor_num = stack->get_num_worker();
for (unsigned i = 0; i < processor_num; ++i) //1
processors.push_back(new Processor(this, stack->get_worker(i), cct)); //运行构造函数
Processor::Processor(AsyncMessenger *r, Worker *w, CephContext *c)
: msgr(r), net(c), worker(w),
listen_handler(new C_processor_accept(this)) {}
1)PosixNetworkStack利用epoll,因为posix在kernel有一个全局的监听tabel,如果一个线程bind一个端口,则其他的线程同时也会察觉到这个事件,因此不需要本地监听表。每个processors对应一个posixwork和listen_handler。
ms_public 监听描述符绑定
(1)
ms_public->bind(paddr) //entity_addr_t paddr = g_conf->get_val<entity_addr_t>("public_addr"); “-”
for (auto &&p : processors)
p->bind(bind_addr, avoid_ports, &bound_addr);
/*默认情况下是没有指定地址和端口的*/
for (int port = msgr->cct->_conf->ms_bind_port_min; port <= msgr->cct->_conf->ms_bind_port_max; port++) //6800-7300
listen_addr.set_port(port);
worker->center.submit_to(worker->center.get_id(), [this, &listen_addr, &opts, &r](){r = worker->listen(listen_addr, opts, &listen_socket);}, false);
EventCenter *c = global_centers->centers[i]; //默认有三个PosixWorker,第一个有Process,这里只用了第一个
C_submit_event<func> event(std::move(f), false);
c->dispatch_event_external(&event);
external_events.push_back(e); //将event插入到external_events中,让process_events去处理
bool wake = !external_num_events.load();
uint64_t num = ++external_num_events;
if (!in_thread() && wake)
wakeup();
char buf = 'c';
int n = write(notify_send_fd, &buf, sizeof(buf));
event.wait(); //等待上面的事件完成
while (!done)
cond.wait(l);
*bound_addr = listen_addr; //已经绑定完成的地址
_finish_bind(bind_addr, bound_addr); //bind为期望绑定的地址,默认为空
set_myaddr(bind_addr);
my_inst.addr = a;
if (bind_addr != entity_addr_t())
learned_addr(bind_addr);
entity_addr_t t = peer_addr_for_me;
t.set_port(my_inst.addr.get_port());
t.set_nonce(my_inst.addr.get_nonce());
my_inst.addr = t
if (get_myaddr().get_port() == 0)
set_myaddr(listen_addr);
init_local_connection();
_init_local_connection();
local_connection->peer_addr = my_inst.addr;
local_connection->peer_type = my_inst.name.type(); //my_inst.name = w;
local_connection->set_features(CEPH_FEATURES_ALL);
ms_deliver_handle_fast_connect(local_connection.get());
for (list<Dispatcher*>::iterator p = fast_dispatchers.begin(); p != fast_dispatchers.end(); ++p)
(*p)->ms_handle_fast_connect(con);//会调用OSD::ms_handle_fast_connect(Connection *con) 没看懂是干啥的
Session *s = static_cast<Session*>(con->get_priv());
did_bind = true;
1)对于PosixNetworkStack,只有一个processor。默认情况下,集群没有配置地址和端口,因此processor会尝试从ms_bind_port_min(6800)到ms_bind_port_max(7300)端口号依次尝试绑定,绑定过程如2)。
2)执行worker->center.submit_to将lambda表达式插入到external_events中。然后发生数据给notify_send_fd(这里只发生了一个字符’c’)通知epoll_wait所在线程去处理这个external事件。lambda表达式会执行worker->listen,如下
//对于asyncmessage套接字绑定,则会调用[this, &listen_addr, &opts, &r]() { r = worker->listen(listen_addr, opts, &listen_socket);} lambda表达式
//进而调用int PosixWorker::listen(entity_addr_t &sa, const SocketOptions &opt, ServerSocket *sock)
int listen_sd = net.create_socket(sa.get_family(), true);
s = ::socket(domain, SOCK_STREAM, 0)
::setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)
net.set_nonblock(listen_sd)
(flags = fcntl(sd, F_GETFL));
fcntl(sd, F_SETFL, flags | O_NONBLOCK)
net.set_close_on_exec(listen_sd);
net.set_socket_options(listen_sd, opt.nodelay, opt.rcbuf_size);
::bind(listen_sd, sa.get_sockaddr(), sa.get_sockaddr_len());
::listen(listen_sd, cct->_conf->ms_tcp_listen_backlog);
*sock = ServerSocket(std::unique_ptr<PosixServerSocketImpl>(new PosixServerSocketImpl(net, listen_sd))); //赋值 listen_socket
对于asyncmessage套接字绑定,就是调用linux系统提供的接口去绑定套接字
(2)给ms_messenger添加dispathcer
client_messenger->add_dispatcher_head(this); //this就是osd
client_messenger->add_dispatcher_head(&mgrc);
add_dispatcher_head调用栈如下
bool first = dispatchers.empty();
dispatchers.push_front(d);
if (d->ms_can_fast_dispatch_any()) //dispather返回为false,若为实现则为false OSD为True
fast_dispatchers.push_front(d);
if (first) //第一次加入dispatcher
ready();
stack->ready(); //PosixNetworkStack没有实现,空函数
for (auto &&p : processors) //貌似只有一个
p->start();
if (listen_socket) { //在上面套接字绑定的时候赋值了
worker->center.submit_to(worker->center.get_id(), [this]() {
worker->center.create_file_event(listen_socket.fd(), EVENT_READABLE, listen_handler); }, false);
dispatch_queue.start();
dispatch_thread.create("ms_dispatch");//启动线程
dq->entry(); //dq指向dispatch_queue 负责处理 mqueue 的信息
local_delivery_thread.create("ms_local"); //启动线程
dq->run_local_delivery(); //dq指向dispatch_queue 负责处理 local_messages 的信息
1)首先将该dispatcher加入到dispatchers中,然后将dispatcher加入到fast_dispatchers中(若不能处理fast_dispatch,则不用,osd可以处理)。
2)如果是第一次调用add_dispatcher_head,则需要调用ready(),ready对每个processor会将监听套接字加入到epoll,具体是执行
worker->center.create_file_event(listen_socket.fd(), EVENT_READABLE, listen_handler); }, false);
其中listen_socket就是上面绑定完成的要结字,listen_handler是processor创建的时候赋值的C_processor_accept。
3)当有新的连接时,epoll监听到,然后就会调用C_processor_accept,其会调用Processor::accept(),其调用栈如下
pro->accept(); //Processor::accept()
while (true)
Worker *w = worker; //一个worker对应一个processor
w = msgr->get_stack()->get_worker(); //取引用最少的posix worker
listen_socket.accept(&cli_socket, opts, &addr, w); //listen_socket的类型为PosixServerSocketImpl(net, listen_sd)
int sd = ::accept(_fd, (sockaddr*)&ss, &slen);
handler.set_close_on_exec(sd);
handler.set_nonblock(sd);
handler.set_socket_options(sd, opt.nodelay, opt.rcbuf_size);
out->set_sockaddr((sockaddr*)&ss); //赋值给 addr
handler.set_priority(sd, opt.priority, out->get_family());
std::unique_ptr<PosixConnectedSocketImpl> csi(new PosixConnectedSocketImpl(handler, *out, sd, true));
*sock = ConnectedSocket(std::move(csi)); //赋值给 cli_socket
msgr->add_accept(w, std::move(cli_socket), addr);
AsyncConnectionRef conn = new AsyncConnection(cct, this, &dispatch_queue, w);
read_handler = new C_handle_read(this); //下面会用到
write_handler = new C_handle_write(this);
wakeup_handler = new C_time_wakeup(this);
tick_handler = new C_tick_wakeup(this); ///////还有很多状态!!!!!!!!
conn->accept(std::move(cli_socket), addr);
cs = std::move(socket);
socket_addr = addr;
state = STATE_ACCEPTING;
center->dispatch_event_external(read_handler); //同上,将外部事件加入到external_events,并给notify_recv_fd发生数据,唤醒处理线程
//然后线程调用read_handler 回调函数,结合STATE_ACCEPTING状态,开始将已连接的描述符加入centers,如上
accepting_conns.insert(conn);
1)寻找引用次数最少的PosixWorker,以此来实现三个worker的负载均衡
2)调用linux系统提供的accept来接受新的连接请求,并设置描述符选项
3)调用msgr->add_accept来创建一个AsyncConnection,其用来表示一个连接,AsyncConnection包含了dispatch_queue线程、PosixWorker,和对应的center。在AsyncConnection的构造函数中赋值一些回调函数。
4)调用conn->accept(std::move(cli_socket), addr);将描述符对应的要监听的事件、连接状态加入到external_events,并给notify_recv_fd发生数据,唤醒处理线程,执行read_handler回调函数,这个回调函数就负责数据的收发了。
开启dispatch_queue
dispatch_queue.start();
dispatch_thread.create("ms_dispatch");//启动线程
dq->entry(); //dq指向dispatch_queue 负责处理 mqueue 的信息
local_delivery_thread.create("ms_local"); //启动线程
dq->run_local_delivery(); //dq指向dispatch_queue 负责处理 local_messages 的信息
由之前套接字连接的处理可以知道,一个AsyncConnection会关联一个dispatch_queue,因此这里的dispatch_thread和local_delivery_thread就负责给ms_public转发消息。
总结
AsyncMessenger实例在创建的时候会首先初始化PosixNetworkStack实例,PosixNetworkStack继承NetworkStack,因此也会调用NetworkStack的构造函数,在其构造函数中会创造ms_async_op_threads(默认为3)个PosixWorker实例worker,每个worker包含一个EventCenter实例center和NetHandler实例net,在NetworkStack构造函数中会调用center.init来设置epoll,并用net来设置管道(pipe)的接收端的配置项,并利用create_file_event函数将管道的接收端notify_receive_fd加入到epoll所监听的描述符中,除此之外还会把notify_receive_fd和其对应的要监听的事件mask以及回调函数加入到file_events中。
对每一个work,AsyncMessenger构造函数还会运行它的线程函数,worker的线程函数是add_thread函数返回的lambda表达式,并在通过调用spawn_worker函数运行。work的线程函数会把自己的center注册到全局的global_centers中。如果线程函数中的epoll_wait返回,则首先会检测numevents是不是大于0,如果大于0就说明监听的描述符有事件到来,就利用活跃描述符从file_events中取出对应的回调函数,然后会继续处理external_events中的事件,因为不是所有的事件都能直接通过描述符来通知线程函数的,这里要注意一下前面注册notify_receive_fd就是让线程函数去处理external_events的,因此notify_receive_fd回调函数仅仅是读取pipe中的数据,所以发送数据给notify_receive_fd就是唤醒线程函数(退出epoll_wait),让其处理external_events的事件。
AsyncMessenger构造函数的最后,还会实例化一个Processor,并加入到processors中,Processor会关联一个worker,Processor内有listenfd的回调函数listen_handler(new C_processor_accept(this))
在上面一些工作做完之后,程序会绑定套接字,其是通过AsyncMessenger的bind函数实现的,bind函数会利用processor的bind函数来去做具体的绑定工作,如果配置文件中没有指定地址,则processor的bind函数会尝试从ms_bind_port_min(6800)到ms_bind_port_max(7300)的端口号,直到成功。对一个端口号,会调用
worker->center.submit_to(worker->center.get_id(), [this, &listen_addr, &opts, &r](){r = worker->listen(listen_addr, opts, &listen_socket);}, false);
来将上面的lambda表达式插入到external_events中,并给processor关联的work的notify_send_fd发送唤醒数据,让关联的work的线程函数去处理bind任务。
最后就是给messenger添加dispatcher,dispatcher负责转发messenger的消息的。添加dispatcher是通过add_dispatcher_head来实现的。其将dispatcher加入到dispatchers和fast_dispatchers中。如果是第一次调用add_dispatcher_head,还会processor的start函数,start函数负责将前面以及绑定结束listen_socket对应的描述符加入到epoll的监听中,其是通过下面代码实现的
worker->center.submit_to(worker->center.get_id(), [this]() {worker->center.create_file_event(listen_socket.fd(), EVENT_READABLE, listen_handler); }, false);
其回调函数为listen_handler,当有新连接请求到达时,第一个worker中的线程函数就会去处理,然后利用下面的语句来获得引用最少的worker
w = msgr->get_stack()->get_worker();
这里是为了实现三个worker的负载平衡。 用引用最少的worker去处理新的已连接描述符,并负责这个描述符的数据收发。
最后在add_dispatcher_head中启动dispatch_thread和local_delivery_thread两个线程。