目录
前言
基本类介绍
消息的接收
消息的发送
ceph在L版本中把Async网络通信模型做为默认的通信方式。Async实现了IO的多路复用,使用共享的线程池实现异步发送和接收任务。
本文主要介绍Async的的Epoll + 线程池的 实现模型,主要介绍基本框架和关键实现。
本文的思路是首先概要介绍相关的类,在介绍类时主要关注其数据结构和相关的操作。
其次介绍网络通信的核心流程:server端sock的监听和接受连接,客户端如何主动发起连接。消息的发送和接收主要流程。
基本类介绍NetHandler
类NetHandler 封装了Socket的基本的功能。
class NetHandler {
int generic_connect(const entity_addr_t& addr,
const entity_addr_t& bind_addr,
bool nonblock);
CephContext *cct;
public:
explicit NetHandler(CephContext *c): cct(c) {}
//创建socket
int create_socket(int domain, bool reuse_addr=false);
//设置socket 为非阻塞
int set_nonblock(int sd);
//当用exec起子进程时:设置socket关闭
void set_close_on_exec(int sd);
//设置socket的选项:nodelay,buffer size
int set_socket_options(int sd, bool nodelay, int size);
//connect
int connect(const entity_addr_t &addr, const entity_addr_t& bind_addr);
//重连
int reconnect(const entity_addr_t &addr, int sd);
//非阻塞connect
int nonblock_connect(const entity_addr_t &addr, const entity_addr_t& bind_addr);
//设置优先级
void set_priority(int sd, int priority);
}
Worker类
Worker类是工作线程的抽象接口,同时添加了listen和connect接口用于服务端和客户端的网络处理。其内部创建一个EventCenter类,该类保存相关处理的事件。
class Worker {
std::atomic_uint references;
EventCenter center; //事件处理中心, 处理该center的所有的事件
// server 端
virtual int listen(entity_addr_t &addr,
const SocketOptions &opts, ServerSocket *) = 0;
// client主动连接
virtual int connect(const entity_addr_t &addr,
const SocketOptions &opts, ConnectedSocket *socket) = 0;
}
PosixWorker
类PosixWorker实现了 Worker接口。class PosixWorker:public Worker
class PosixWorker : public Worker {
NetHandler net;
int listen(entity_addr_t &sa,
const SocketOptions
&opt,ServerSocket *socks) override;
int connect(const entity_addr_t &addr,
const SocketOptions &opts,
ConnectedSocket *socket) override;
}
int PosixWorker::listen(entity_addr_t &sa, const SocketOptions &opt,ServerSocket *sock)
函数PosixWorker::listen 实现了Server端的sock的功能:底层调用了NetHandler的功能,实现了socket 的 bind ,listen等操作,最后返回ServerSocket对象。int PosixWorker::connect(const entity_addr_t &addr, const SocketOptions &opts, ConnectedSocket *socket)
函数PosixWorker::connect 实现了主动连接请求。返回ConnectedSocket对象。
NetworkStack
class NetworkStack : public CephContext::ForkWatcher {
std::string type; //NetworkStack的类型
ceph::spinlock pool_spin;
bool started = false;
//Worker 工作队列
unsigned num_workers = 0;
vector<Worker*> workers;
}
类NetworkStack是 网络协议栈的接口。PosixNetworkStack实现了linux的 tcp/ip 协议接口。DPDKStack实现了DPDK的接口。RDMAStack实现了IB的接口。
class PosixNetworkStack : public NetworkStack {
vector<int> coreids;
vector<std::thread> threads; //线程池
}
Worker可以理解为工作者线程,其对应一个thread线程。为了兼容其它协议的设计,对应线程定义在了PosixNetworkStack类里。
通过上述分析可知,一个Worker对应一个线程,同时对应一个 事件处理中心EventCenter类。
EventDriver
EventDriver是一个抽象的接口,定义了添加事件监听,删除事件监听,获取触发的事件的接口。
class EventDriver {
public:
virtual ~EventDriver() {} // we want a virtual destructor!!!
virtual int init(EventCenter *center, int nevent) = 0;
virtual int add_event(int fd, int cur_mask, int mask) = 0;
virtual int del_event(int fd, int cur_mask, int del_mask) = 0;
virtual int event_wait(vector<FiredFileEvent> &fired_events, struct timeval *tp) = 0;
virtual int resize_events(int newsize) = 0;
virtual bool need_wakeup() { return true; }
};
针对不同的IO多路复用机制,实现了不同的类。SelectDriver实现了select的方式。EpollDriver实现了epoll的网络事件处理方式。KqueueDriver是FreeBSD实现kqueue事件处理模型。
EventCenter
类EventCenter,主要保存事件(包括fileevent,和timeevent,以及外部事件)和 处理事件的相关的函数。
Class EventCenter { //触发执行外部事件的fd //底层事件监控机制 // Used by internal thread // Used by external thread } |
Class EventCenter {
//外部事件
std::mutex external_lock;
std::atomic_ulong external_num_events;
deque<EventCallbackRef> external_events;
//socket事件, 其下标是socket对应的fd
vector<FileEvent> file_events; //-------------------------------------FileEvent
//时间事件 [expire time point, TimeEvent]
std::multimap<clock_type::time_point, TimeEvent> time_events; //------TimeEvent
//时间事件的map [id, iterator of [expire time point,time_event]]
std::map<uint64_t,
std::multimap<clock_type::time_point, TimeEvent>::iterator> event_map;
//触发执行外部事件的fd
int notify_receive_fd;
int notify_send_fd;
EventCallbackRef notify_handler;
//底层事件监控机制
EventDriver *driver;
NetHandler net;
// Used by internal thread
//创建file event
int create_file_event(int fd, int mask, EventCallbackRef ctxt);
//创建time event
uint64_t create_time_event(uint64_t milliseconds, EventCallbackRef ctxt);
//删除file event
void delete_file_event(int fd, int mask);
//删除 time event
void delete_time_event(uint64_t id);
//处理事件
int process_events(int timeout_microseconds);
//唤醒处理线程
void wakeup();
// Used by external thread
void dispatch_event_external(EventCallbackRef e);
}
1)FileEvent
FileEvent事件,也就是socket对应的事件。
struct FileEvent {
int mask; //标志
EventCallbackRef read_cb; //处理读操作的回调函数
EventCallbackRef write_cb; //处理写操作的回调函数
FileEvent(): mask(0), read_cb(NULL), write_cb(NULL) {}
};
2)TimeEvent
struct TimeEvent {
uint64_t id; //时间事件的ID号
EventCallbackRef time_cb; //事件处理的回调函数
TimeEvent(): id(0), time_cb(NULL) {}
};
处理事件
int EventCenter::process_events(int timeout_microseconds, ceph::timespan *working_dur)
函数process_event处理相关的事件,其处理流程如下:
- 如果有外部事件,或者是poller模式,阻塞时间设置为0,也就是epoll_wait的超时时间。
- 默认超时时间为参数设定的超时时间timeout_microseconds,如果最近有时间事件,并且expect time 小于超时时间timeout_microseconds,就把超时时间设置为expect time到当前的时间间隔,并设置trigger_time为true标志,触发后续处理时间事件。
- 调用epoll_wait获取事件,并循环调用相应的回调函数处理相应的事件。
- 处理到期时间事件
- 处理所有的外部事件
在这里,内部事件指的是通过 epoll_wait 获取的事件。外部事件(external event)是其它投送的事件,例如处理主动连接,新的发送消息触发事件。
在类EventCenter里定义了两种方式向EventCenter里投递外部事件:
//直接投递EventCallback类型的事件处理函数
void EventCenter::dispatch_event_external(EventCallbackRef e)
//处理func类型的事件处理函数
void submit_to(int i, func &&f, bool nowait = false)
AsyncMessenger
类AsyncMessenger 主要完成AsyncConnection的管理。其内部保存了所有Connection相关的信息。
class AsyncMessenger : public SimplePolicyMessenger {
//现在的Connection
ceph::unordered_map<entity_addr_t, AsyncConnectionRef> conns;
//正在accept的Connection
set<AsyncConnectionRef> accepting_conns;
//准备删除的 Connection
set<AsyncConnectionRef> deleted_conns;
}
连接相关的流程介绍
Server端监听和接受连接的过程
这段代码来自 src/test/msgr/perf_msgr_server.cc,主要用于建立Server端的msgr:
void start() {
entity_addr_t addr;
addr.parse(bindaddr.c_str());
msgr->bind(addr);
msgr->add_dispatcher_head(&dispatcher);
msgr->start();
msgr->wait();
}
上面的代码是典型的服务端的启动流程:
- 绑定服务端地址 msgr->bind(addr)
- 添加消息分发类型 dispatcher
- 启动 msgr->start()
下面从内部具体如何实现。
- 调用processor的bind 函数,对于PosixStack, 只需要一个porcessor就可以了。
int AsyncMessenger::bind(const entity_addr_t &bind_addr) ...... |
p->bind的内容:
int Processor::bind(const entity_addr_t &bind_addr, | const set<int>& avoid_ports, | entity_addr_t* bound_addr){ | ... | //向Processor对于的工作者线程 投递外部事件,其回调函数为 worker的 listen函数 | worker->center.submit_to(worker->center.get_id(), | [this, &listen_addr, &opts, &r]() { | r = worker->listen(listen_addr, opts, &listen_socket); | }, false); | ... | } |
当该外部事件被worker线程调度执行后,worker->listen完成了该Processor的listen_socket的创建。
- 添加 dispatcher
void add_dispatcher_head(Dispatcher *d) {
if (first)
ready();
}
在ready 函数里调用了Processor::start函数
在Processor::start函数里,向EventCenter 投递了外部事件,该外部事件的回调函数里实现了向 EventCenter注册listen socket 的读事件监听。 该事件的处理函数为 listen_handeler
void Processor::start()
{
ldout(msgr->cct, 1) << __func__ << dendl;
// start thread
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);
}
}
listen_handler对应的 处理函数为 processor::accept函数,其处理接收连接的事件。
class Processor::C_processor_accept : public EventCallback {
Processor *pro;
public:
explicit C_processor_accept(Processor *p): pro(p) {}
void do_request(int id) {
pro->accept();
}
};
在函数Processor::accept里, 首先获取了一个worker,通过调用accept函数接收该连接。并调用 msgr->add_accept 函数。
void Processor::accept()
{
......
if (!msgr->get_stack()->support_local_listen_table())
w = msgr->get_stack()->get_worker();
int r = listen_socket.accept(&cli_socket, opts, &addr, w);
if (r == 0) {
msgr->add_accept(w, std::move(cli_socket), addr);
continue;
}
}
void AsyncMessenger::add_accept(Worker *w,
ConnectedSocket cli_socket,
entity_addr_t &addr)
{
lock.Lock();
//创建连接,该Connection已经指定了 worker处理该Connection上所有的事件。
AsyncConnectionRef conn = new AsyncConnection(cct, this, &dispatch_queue, w);
conn->accept(std::move(cli_socket), addr);
accepting_conns.insert(conn);
lock.Unlock();
}
Client端主动连接的过程
AsyncConnectionRef AsyncMessenger::create_connect(const entity_addr_t& addr, int type)
{
// 获取一个 worker,根据负载均衡
Worker *w = stack->get_worker();
//创建Connection
AsyncConnectionRef conn = new AsyncConnection(cct, this, &dispatch_queue, w);
//
conn->connect(addr, type);
//添加到conns列表中
conns[addr] = conn;
return conn;
}
下面的代码,函数 AsyncConnection::_connect 设置了状态为 STATE_CONNECTING,向对应的 EventCenter投递 外部外部事件,其read_handler为 void AsyncConnection::process()函数。
void AsyncConnection::_connect()
{
ldout(async_msgr->cct, 10) << __func__ << " csq=" << connect_seq << dendl;
state = STATE_CONNECTING;
// rescheduler connection in order to avoid lock dep
// may called by external thread(send_message)
center->dispatch_event_external(read_handler);
}
void AsyncConnection::process()
{
......
default:
{
if (_process_connection() < 0)
goto fail;
break;
}
}
ssize_t AsyncConnection::_process_connection()
{
......
r = worker->connect(get_peer_addr(), opts, &cs);
if (r < 0)
goto fail;
center->create_file_event(cs.fd(), EVENT_READABLE, read_handler);
}
消息的接收和发送
消息的接收
消息的接收比较简单,因为消息的接收都是 内部事件,也就是都是由 epoll_wait触发的事件。其对应的回调函数 AsyncConnection::process() 去处理相应的接收事件。
消息的发送
消息的发送比较特殊,它涉及到外部事件和内部事件的相关的调用。
int AsyncConnection::send_message(Message *m){
......
//把消息添加到 内部发送队列里
out_q[m->get_priority()].emplace_back(std::move(bl), m);
//添加外部事件给对应的的CenterEvent,并触发外部事件
if (can_write != WriteStatus::REPLACING)
center->dispatch_event_external(write_handler);
}
发送相关的调用函数
void AsyncConnection::handle_write()
ssize_t AsyncConnection::write_message(Message *m,bufferlist& bl, bool more)
ssize_t AsyncConnection::_try_send(bool more)
ssize_t AsyncConnection::_try_send(bool more)
{
......
if (!open_write && is_queued()) {
center->create_file_event(cs.fd(), EVENT_WRITABLE,
write_handler);
open_write = true;
}
if (open_write && !is_queued()) {
center->delete_file_event(cs.fd(), EVENT_WRITABLE);
open_write = false;
if (state_after_send != STATE_NONE)
center->dispatch_event_external(read_handler);
}
}
函数_try_send里比较关键的是:
- 当还有消息没有发送时, 就把该socket的 fd 添加到 EVENT_WRITABLE 事件中。
- 如果没有消息发送, 就把该 socket的 fd 的 EVENT_WRITABLE 事件监听删除
也就是当有发送请求时, 添加外部事件,并触发线程去处理发送事件。当外部事件一次可以完成发送消息的请求时,就不需要添加该fd对应的EVENT_WRITABLE 事件监听。当没有发送完消息时,就添加该fd的EVENT_WRITABLE 事件监听来触发内部事件去继续完成消息的发送。
Ceph Async 模型IO 多路复用多线程模型
Half-sync/Half-async模型
本模型主要实现如下:
- 有一个专用的独立线程(事件监听线程)调用epoll_wait 函数来监听网络IO事件
- 线程池(工作线程)用于处理网络IO事件 : 每个线程会有一个事件处理队列。
- 事件监听线程获取到 IO事件后,选择一个线程,把事件投递到该线程的处理队列,由该线程后续处理。
这里关键的一点是:如果选择一个线程?一般根据 socket 的 fd 来 hash映射到线程池中的线程。这里特别要避免的是:同一个socket不能有多个线程处理,只能由单个线程处理。
如图所示,系统有一个监听线程,一般为主线程 main_loop 调用 epoll_wait 来获取并产生事件,根据socket的 fd 的 hash算法来调度到相应的 线程,把事件投递到线程对应的队列中。工作线程负责处理具体的事件。
这个模型的优点是结构清晰,实现比较直观。 但也有如下的 不足:
- 生产事件的线程(main_loop线程) 和 消费事件的线程(工作者线程)访问同一个队列会有锁的互斥和线程的切换。
- main_loop是同步的,如果有线程的队列满,会阻塞main_loop线程,导致其它线程临时没有事件可消费。
Leader/Follower
当Leader监听到socket事件后:处理模式
1)指定一个Follower为新的Leader负责监听socket事件,自己成为Follower去处理事件
2)指定Follower 去完成相应的事件,自己仍然是Leader
由于Leader自己监听IO事件并处理客户请求,该模式不需要在线程间传递额外数据,也无需像半同步/半反应堆模式那样在线程间同步对请求队列的访问。
ceph Async 模型
在Ceph Async模型里,一个Worker类对应一个工作线程和一个事件中心EventCenter。 每个socket对应的AsyncConnection在创建时根据负载均衡绑定到对应的Worker中,以后都由该Worker处理该AsyncConnection上的所有的读写事件。
如图所示,在Ceph Async模型里,没有单独的main_loop线程,每个工作线程都是独立的,其循环处理如下:
- epoll_wait 等待事件
- 处理获取到的所有IO事件
- 处理所有时间相关的事件
- 处理外部事件
在这个模型中,消除了Half-sync/half-async的 队列互斥访问和 线程切换的问题。 本模型的优点本质上是利用了操作系统的事件队列,而没有自己去处理事件队列
Overall structure of Ceph async messenger
效果: