目录

​前言​

​基本类介绍​

​连接相关的流程介绍​

​Server端监听和接受连接的过程​

​Client端主动连接的过程​

​消息的接收和发送​

​消息的接收​

​消息的发送​

​Ceph Async 模型​

​IO 多路复用多线程模型​

​Half-sync/Half-async模型​

​Leader/Follower​

​ceph Async 模型​



前言

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的接口。

【Ceph 】Async 网络通信源代码分析--研读Ceph Async 模型_事件监听

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; }
};

【Ceph 】Async 网络通信源代码分析--研读Ceph Async 模型_处理事件_02

针对不同的IO多路复用机制,实现了不同的类。SelectDriver实现了select的方式。EpollDriver实现了epoll的网络事件处理方式。KqueueDriver是FreeBSD实现kqueue事件处理模型。

EventCenter

类EventCenter,主要保存事件(包括fileevent,和timeevent,以及外部事件)和 处理事件的相关的函数。


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);

}


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处理相关的事件,其处理流程如下:

  1. 如果有外部事件,或者是poller模式,阻塞时间设置为0,也就是epoll_wait的超时时间。
  2. 默认超时时间为参数设定的超时时间timeout_microseconds,如果最近有时间事件,并且expect time 小于超时时间timeout_microseconds,就把超时时间设置为expect time到当前的时间间隔,并设置trigger_time为true标志,触发后续处理时间事件。
  3. 调用epoll_wait获取事件,并循环调用相应的回调函数处理相应的事件。
  4. 处理到期时间事件
  5. 处理所有的外部事件

在这里,内部事件指的是通过 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();
}

上面的代码是典型的服务端的启动流程:

  1. 绑定服务端地址 msgr->bind(addr)
  2. 添加消息分发类型 dispatcher
  3. 启动 msgr->start()

下面从内部具体如何实现。

  1. 调用processor的bind 函数,对于PosixStack, 只需要一个porcessor就可以了。


int AsyncMessenger::bind(const entity_addr_t &bind_addr)
{ ......
     for (auto &&p : processors) {
         int r = p->bind(bind_addr, avoid_ports, &bound_addr); //每个processors都bind
       }

 ......


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里比较关键的是:

  1. 当还有消息没有发送时, 就把该socket的 fd 添加到 EVENT_WRITABLE 事件中。
  2. 如果没有消息发送, 就把该 socket的 fd 的 EVENT_WRITABLE 事件监听删除

也就是当有发送请求时, 添加外部事件,并触发线程去处理发送事件。当外部事件一次可以完成发送消息的请求时,就不需要添加该fd对应的EVENT_WRITABLE 事件监听。当没有发送完消息时,就添加该fd的EVENT_WRITABLE 事件监听来触发内部事件去继续完成消息的发送。

Ceph Async 模型

IO 多路复用多线程模型

Half-sync/Half-async模型

本模型主要实现如下:

  1. 有一个专用的独立线程(事件监听线程)调用epoll_wait 函数来监听网络IO事件
  2. 线程池(工作线程)用于处理网络IO事件 : 每个线程会有一个事件处理队列。
  3. 事件监听线程获取到 IO事件后,选择一个线程,把事件投递到该线程的处理队列,由该线程后续处理。

这里关键的一点是:如果选择一个线程?一般根据 socket 的 fd 来 hash映射到线程池中的线程。这里特别要避免的是:同一个socket不能有多个线程处理,只能由单个线程处理。

【Ceph 】Async 网络通信源代码分析--研读Ceph Async 模型_回调函数_03

如图所示,系统有一个监听线程,一般为主线程 main_loop 调用 epoll_wait 来获取并产生事件,根据socket的 fd 的 hash算法来调度到相应的 线程,把事件投递到线程对应的队列中。工作线程负责处理具体的事件。

这个模型的优点是结构清晰,实现比较直观。 但也有如下的 不足:

  1. 生产事件的线程(main_loop线程) 和 消费事件的线程(工作者线程)访问同一个队列会有锁的互斥和线程的切换。
  2. main_loop是同步的,如果有线程的队列满,会阻塞main_loop线程,导致其它线程临时没有事件可消费。

Leader/Follower

当Leader监听到socket事件后:处理模式 

1)指定一个Follower为新的Leader负责监听socket事件,自己成为Follower去处理事件 

2)指定Follower 去完成相应的事件,自己仍然是Leader

由于Leader自己监听IO事件并处理客户请求,该模式不需要在线程间传递额外数据,也无需像半同步/半反应堆模式那样在线程间同步对请求队列的访问。

ceph Async 模型

【Ceph 】Async 网络通信源代码分析--研读Ceph Async 模型_事件处理_04

在Ceph Async模型里,一个Worker类对应一个工作线程和一个事件中心EventCenter。 每个socket对应的AsyncConnection在创建时根据负载均衡绑定到对应的Worker中,以后都由该Worker处理该AsyncConnection上的所有的读写事件。

【Ceph 】Async 网络通信源代码分析--研读Ceph Async 模型_处理事件_05

如图所示,在Ceph Async模型里,没有单独的main_loop线程,每个工作线程都是独立的,其循环处理如下:

  1. epoll_wait 等待事件
  2. 处理获取到的所有IO事件
  3. 处理所有时间相关的事件
  4. 处理外部事件

在这个模型中,消除了Half-sync/half-async的 队列互斥访问和 线程切换的问题。 本模型的优点本质上是利用了操作系统的事件队列,而没有自己去处理事件队列

【Ceph 】Async 网络通信源代码分析--研读Ceph Async 模型_处理事件_06

 

Overall structure of Ceph async messenger

【Ceph 】Async 网络通信源代码分析--研读Ceph Async 模型_处理事件_07

【Ceph 】Async 网络通信源代码分析--研读Ceph Async 模型_回调函数_08

 

效果:

【Ceph 】Async 网络通信源代码分析--研读Ceph Async 模型_线程池_09