Redis中的多路复用

72 阅读4分钟

多路复用

socket 套接字

使用 socket 套接字进行网络通信的时候需要进行三次握手和四次挥

阻塞 IO

原理

服务端和客户端在进行连接或者 read/write 操作时,都会先阻塞住,等待操作完成后再进行下一步。

类比

老师收作业,有十个同学举手,老师只能收一个,回来,再收一个再回来,如此把作业收完。如果没有作业,一直处于等待状态。

非阻塞IO

原理

服务端和客户端在进行连接或者read/write操作时,不会阻塞住,会继续进行轮询查询,直到有响应到来。

类比

老师,收作业,有十个同学举手,老师不知道哪个同学举手,每次从头开始收作业,作业收完后,老师依旧要视察有没有举手。

select/poll

用户态和内核态

操作系统中将空间分为用户空间和内核空间,规定内核空间中只能操作一些相对安全的指令,防止用户空间的进行不安全的操作将操作系统搞崩掉。 用户空间向内核空间发送指令,cpu 状态会从用户态变为内核态,反之从内核态变为用户态,两态之间的转变非常消耗 cpu 资源。

原理

开始时会将文件描述符 fd 从用户空间复制到内核空间中。

缺点是如果 fd 数量过大,复制行为比较耗时。

非阻塞 I0 在进行读写操作时并不会等待服务端将信息发送过来之后再开始,他的内部实际一个事件循环,也就是死循环,会不断检查内核空间中的所有文件描述符 fd 的状态。当检查到已就绪的fd时,会将此fd告诉用户空间已准备就绪可以进行处理。此时用户空间拿到此fd之后就去处理。

缺点是要不断在内核空间和用户空间轮询fd,以查找到准备就绪的fd去处理。

类比

老师收作业,有10个学生在举手,老师不知道收谁的作业,只能每次从头开始找举手的同学,然后把作业收上来。

优点

优点不用频繁在用户态和内核态之间转换,节省cpu开销

poll 与 select 的不同点

  1. poll 中内核空间中保存 fd 的结构由数组变为链表,不再受 select 单线程中最大保存 fd 的数组大小为 1M 的限制。
  2. poll 中引入了就绪事件参数,用于保存就绪事件,使得返回就绪参数给用户空间之后,不用再因为内核空间改变 select 中保存 fd 数组的值而重新重置 fd 数组。

epoll

原理

引入 epoll_create 创建一个 epollEvent。

  1. epollEvent里面定义 rbn 红黑树用于保存用户空间传入的 fd,大大提高 fd 的删改查的速度。
  2. epollEvent 中具有就绪列表,用于保存就绪的 fd。
  3. epollEvent 中具有一个等待队列,用于保存闲置的线程。

引入epoll_ctl 用于操作epollEvent。

当用 epoll_create 创建了一个 epollEvent 以后,可以使用 epoll_ctl 将用户空间的fd存入到 epolEvent 中的 rbn 数据结构中。

引入 epoll item 用于主动将就绪列表中的 fd 回传到用户空间。

epoll item 中定义了一个重要的结构 pwglist 关联了一些回调函数用于调用等待队列中的线程,用于判断就绪列表中有没有就绪事件,如果有,传回用户空间进行处理。

流程

  1. 客户端发送了一条数据并通过网卡传到内存。
  2. cpu 调用中断方法将 socket 放入就绪列表并生成 epoll_item。
  3. epoll_item 中的回调函数启动唤醒等待队列中的线程判断就绪空间中的 fd 并返回。
  4. 调用 epoll_wait 将就绪列表中的所有 fd 返回进行处理

优点

解决以上方法的所有缺点

类比

老师收作业,有 10 个同学举手,然后把这 10 个同学的作业一起收类比上来。