redis多路复用原理select、poll、epoll

Redis服务端对于命令的处理是单线程的,但是在I/O层面却可以同时面对多个客户端并发的提供服务,并发到内部单线程的转化通过多路复用框架实现

一个IO操作的完整流程是数据请求先从用户态到内核态,也就是操作系统层面,然后再调用操作系统提供的API,调用相应的设备去获取相应的数据。

当相应的设备准备好数据后,会将数据复制到内核态。数据从相应的设备到内核态的处理方式分为:阻塞和非阻塞。

  阻塞:用户请求会等待数据准备好从操作系统调用相应的设备返回到内核态,如果没有返回处于阻塞状态。

  非阻塞:非阻塞是操作系统接收到一组文件描述符,然后操作系统批量处理这些个文件描述符,然后不管有没有准备好数据都立即返回,

           如果没有对应的准备好的文件描述符,则继续轮询获取准备好数据的文件描述符。

 数据从内核态到用户态的复制数据的处理方式的不同可分为:同步和异步

  同步:用户请求等待数据从内核态向用户态复制数据,在此期间不做其他的事情,次称为同步

  异步:在数据从内核态向用户态复制的过程中,用户请求不会一直处于等待状态而是做其他事情,称为异步

 

redis的多路复用框架使用的非阻塞的数据返回模式

   使用模型有:select、poll、epoll

   首先select、poll都是传递一个 fd的集合调用操作系统的API。其中select 模式有大小的限制 最大只能是1024的fd

   而 poll是传递一个pollfd数组,pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,所以没有大小限制

   select/poll的几大缺点:

   1.每次调用select/poll,都需要把fd集合从用户态copy到内核态,如果是fd集合很大的时候,开销会很大

   2.每次调用select/poll,都要在内核态中循环遍历fd集合,去判断哪些fd准备好了数据,这个是在fd集合很大的情况下面,开销会很大

   3.针对select支持的文件描述符数量太小了,默认是1024

   4.select返回的是整个有句柄的数组,需要全部遍历一遍才知道那些fd有事件的产生

   相比于poll,因为poll使用的是链表保存fd,所以没有fd大小数量的限制,但是其他的缺点依然存在

 epoll的多路复用机制,使用了select和poll都不同的方式,所以就规避了select和poll的缺点

    epoll 支持的fd上限为文件打开数。这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右。

   设想一下如下场景:有100万个客户端同时与一个服务器进程保持着TCP连接。而每一时刻,通常只有几百上千个TCP连接是活跃的(事实上大部分场景都是这种情况)。如何实现这样的高并发?

   在select/poll时代,会把这些fd从用户态复制到内核态,然后让操作系统去查询这些套接字上是否有事件发生,轮询完以后,在将这些句柄集合返回,从内核态复制会用户态,让用户应用程序

   遍历轮询去获取有哪些句柄有事件的发上,这个过程大量的浪费了性能。因此,select和poll 一般只能处理几千的并发。但是epoll的设计和select/poll的设计完全不同。他不需要用户应用程序

   循环遍历所有的句柄数组才能知道有哪些句柄有事件的发生,因此epoll能支持更大的并发。

   epoll 在linux系统内部维护了一个文件系统结构,将select/poll分为了3个部分。

   01)调用epoll_create在linux系统中维护一个epoll对象

   02)调用epoll_ctl向epoll对象中添加100万个fd 套接字

   03)调用epoll_wait收集事件发生的fd

   底层实现

    1.当调用epoll_create时候,会在linux内核维护一个eventpoll结构体,在这个结构体中有两个成员,与epoll的使用方式密切相关

结构体如下:

 struct eventpoll{

   ...........

   /**红黑树的根节点,存储了所有添加到epoll中所需要监控的事件**/

   struct rb_root rb;

       /** 双向链表中存储着所有的通过epoll_wait返回给用户满足条件的事件 **/    

   struct list_head rdlist;

   ..............

 };

   每一个epoll对象都有一个独立的eventpoll结构体,用于存放需要监控的fd事件,这些事件都会挂载在结构体重的红黑树中。而所有添加到eventpoll结构体中,都会与设备(网卡)建立回调

   关系,也就是说,当有事件发生时候,相应的事件会回调这个回调方法。这个回调方法就是ep_poll_callback,他讲发生的事件添加到eventpoll结构体中的rdlist双向链表中

   在epoll中,对于每一个事件,都会建立一个epitem结构体,如下所示:

   struct epitem{

      struct rb_node rbn;//红黑树节点

  struct list_head rdllink;//双向链表节点

  struct epoll_field ffd;//事件句柄信息

  struct eventpoll *ep;//指向所属的eventpoll对象

  struct epoll_event event;//期待发生的事件类型

   };  

当调用event_wait判断是否有事件发生时候,只需要判断 eventpoll 双向链表中,rdlist 有没有epitem对象即可。如果rdlist不为空,则直接将发生的事件复制到用户态。同时将事件数量返回给用户.

评论区
Rick ©2018