Redis IO 多路复用机制

Redis IO 多路复用机制
  • 基于linux select/epoll
    • select:最大支持1024个文件描述符,在描述符较多情况下性能较差,水平触发
    • poll:poll与select基本相同,只是没有文件描述符的限制
    • epoll:文件描述符为系统上限,在描述符较多情况下性能较好,同时支持水平与边缘触发
  • 内核可同时监听多个监听套接字和 多个已连接套接字
  • 一旦内核监听到套接字上有数据返回,立刻交给redis线程处理数据
水平触发
  • 只要文件描述符关联的读内核缓冲区非空,有数据可以读取,就一直发出可读信号进行通知
  • 当文件描述符关联的内核写缓冲区不满,有空间可以写入,就一直发出可写信号进行通知
  • 如果系统中有大量不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率
边缘触发
  • 当文件描述符关联的读内核缓冲区由空转化为非空的时候,则发出可读信号进行通知
  • 当文件描述符关联的内核写缓冲区由满转化为不满的时候,则发出可写信号进行通知。
  • 如果这次没有把数据全部读写完(如读写缓冲区太小),它不会再次通知你
  • 直到该文件描述符上出现第二次可读写事件才会通知你
  • 这种模式比水平触发效率高,系统不会充斥大量不关心的就绪文件描述符
使用Linux epoll模型的水平触发模式
  • 当socket可写时,会不停的触发socket可写的事件,如何处理?
  • 当需要向socket写数据时,将该socket加入到epoll等待可写事件。
  • 接收到socket可写事件后,调用write或send发送数据,当数据全部写完后, 将socket描述符移出epoll列表,这种做法需要反复添加和删除。
  • 边缘模式就可以直接搞定,并不需要用户层程序的补丁操作。

select

  • Linux将进程分类为两个队列,阻塞队列和工作队列。
  • 网络应用程序调用linux read读取数据时,如果没有数据就一直阻塞
  • 假设当进程执行到read时,A进程就从工作队列中移到该socket的等待队列中,A进程就被阻塞。
  • 阻塞期间,如果来数据了,中断处理就会将数据装入接收队列,然后唤醒A进程,移动到工作队列中。
read阻塞原理:进程分为“运行”和“等待”等几种状态。
  • 当进程 A 执行到创建 socket 的语句时,操作系统会创建一个由文件系统管理的 socket 对象。
  • 这个 socket 对象包含了发送缓冲区、接收缓冲区与等待队列等成员。
  • 当程序执行到 read 时,操作系统会将进程 A 从工作队列移动到该 socket 的等待队列中,那么进程A就不会被执行,也不会占用 CPU 资源。
  • 当 socket 接收到数据后,操作系统将该 socket 等待队列上的进程重新放回到工作队列,该进程变成运行状态
  • 但是每个read方法只能监控一个socket
如何从阻塞队列移到工作队列:

网卡将数据写入内存后,网卡产生一个中断,处理器立即停止它正在做的事,然后跳转到内存中预定义的中断程序开始执行。

select的原理:
  • 使用一个数据存放socket

  • 如果数组(代码中写死了数组长度为1024)中的所有socket都没有数据,进程就被挂起

  • 直到有socket收到数据,唤醒进程。

假如程序同时监视如下图的 sock1、sock2 和 sock3 三个 socket,那么在调用 select 之后,操作系统把进程 A 分别加入这三个 socket 的等待队列中。

Redis IO 多路复用机制_第1张图片

当任何一个 socket 收到数据后,中断程序将唤起进程,所谓唤起进程,就是将进程从所有的等待队列中移除,加入到工作队列里面

Redis IO 多路复用机制_第2张图片

当进程 A 被唤醒后,它知道至少有一个 socket 接收了数据。程序只需遍历一遍 socket 列表,就可以得到就绪的 socket。

缺点:
  • 每次调用select都需要将进程加入到所有要监控的socket的等待队列中
  • 每次唤醒都要从所有的队列中移除;
  • 被唤醒后,进程并不知道哪些socket有数据,需要遍历。

epoll

  • epoll将等待队列和阻塞进程分开了
    • 使用epoll_ctl维护等待队列
    • 使用epoll_wait阻塞进程
  • epoll内部维护了一个就绪队列,收到数据的socket直接加入就绪队列
    • 当 epoll 监听的 socket 状态发生改变(变为可读或可写)时,就会把就绪的 socket 添加到就绪队列中
    • 当进程被唤醒,只要获取就绪队列就能知道哪些socket收到数据了。
  • epoll内部使用红黑树保存所有监听的socket
    • 添加和查找元素的时间复杂度为 O(log n)

当某个进程调用 epoll_create 方法时,内核会创建一个 eventpoll 对象。

eventpoll 对象也是文件系统中的一员,和 socket 一样,它也会有等待队列。

rdllist:

保存已经就绪的文件列表。

rbr:

使用红黑树来管理所有被监听的文件。红黑树节点是epitem

创建 epoll 对象后,可以用 epoll_ctl 添加或删除所要监听的 socket。

以添加 socket 为例,如果通过 epoll_ctl 添加 sock1、sock2 和 sock3 的监视,内核会将 eventpoll 添加到这三个 socket 的等待队列中。

当 socket 收到数据后,中断程序会给 eventpoll 的“就绪列表”添加 socket 引用。如下图展示的是 sock2 和 sock3 收到数据后,中断程序让 rdlist 引用这两个 socket。

Redis IO 多路复用机制_第3张图片

eventpoll 对象相当于 socket 和进程之间的中介,socket 的数据接收并不直接影响进程,而是通过改变 eventpoll 的就绪列表来改变进程状态。

当 socket 接收到数据,中断程序一方面修改 rdlist,另一方面唤醒 eventpoll 等待队列中的进程,进程 A 再次进入运行状态。也因为 rdlist 的存在,进程 A 可以知道哪些 socket 发生了变化。

你可能感兴趣的:(redis,数据库,linux)