Linux网络编程模型和Ceph Async 模型探讨

IO多路复用

所谓的I/O多路复用,就是可以监控多个socket上的IO请求。允许多个socket在可读或可写准备好时,应用能被通知到,这样应用就可以一次非阻塞的处理多个socket相关的IO请求。

IO多路复用的有三种实现方式:

Select

I/O复用模型早期用select实现。

int select (int n, 
            fd_set *readfds, 
            fd_set *writefds, 
            fd_set *exceptfds, 
            struct timeval *timeout);

select 函数可以实现IO多路复用。但是select在大规模网络环境下有如下的缺点:

  1. 可监控的socket有限(内部用数组保存)
  2. 要遍历所有的fd集合来确定是否相关事件
  3. 大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。

poll

int poll(struct pollfd *fds, nfds_t nfds, int timeout);  

其有如下特点:
1. 监控的socket没有限制(内部用链表,当socket数量多时,性能下降明显)
2. 和select一样,要遍历所有的socket来检查相关事件的产生
3.epoll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd

epoll

epoll是目前使用最广泛的IO多路复用机制。它克服了select 和 epoll 的一些缺点。

  1. 其内部使用了 红黑树和链表机制,通过红黑树高效管理大量fd
  2. 通过内核的回调机制,直接把产生事件的fd添加到内部的链表中。从而不需要遍历所有的fd,直接返回发生fd
  3. 支持水平触发(Level Triger)和边沿触发(Edge Trigger)两种模式

水平触发指的是:当有事件发生时,如果应用没有完该IO,系统会不断的产生相关事件提醒应用去处理。

边沿触发指的是:当有事件发生时,只产生一次通知事件,需要应用一次性把该事件处理完。

在大规模高性能的网络编程中,一般都采用边沿触发的模式。

Epoll的使用流程如下

  1. 调用函数 epoll_create()系统调用。此调用返回一个fd 。
  2. epoll_ctl()系统调用。通过此调用向epoll对象中添加、删除、修改感兴趣的事件,返回0标识成功,返回-1表示失败。
  3. epoll_wait()系统调用。通过此调用收集收集在epoll监控中已经发生的事件。

IO 多路复用的模式

两种I/O多路复用模式:Reactor和Proactor

Reactor

Reactor 用于同步IO
对于linux, epoll 是同步操作,所以只能用Reactor模式, 其核心在于:当通过epoll触发事件后,只是表明数据就绪, 需要自己去在socket上接收数据。

Proactor

Proactor用于异步IO
对于Windows, IOCP是异步操作。当由事件触发时,其数据已经从socket上接收完毕并拷贝到应用的缓存区中,数据接收已经完成,通知应用可以使用该数据。

IO 多路复用多线程模型

Half-sync/Half-async模型

本模型主要实现如下:

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

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

Linux网络编程模型和Ceph Async 模型探讨_第1张图片

如图所示,系统有一个监听线程,一般为主线程 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 模型

Linux网络编程模型和Ceph Async 模型探讨_第2张图片

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

Linux网络编程模型和Ceph Async 模型探讨_第3张图片

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

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

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

你可能感兴趣的:(ceph)