用户空间、内核空间#
IO 模型#
《UNIX 网络编程》 总结了五种 IO 模型
- 阻塞 IO / Blocking IO
- 非阻塞 IO / Nonblocking IO
- IO 多路复用 / IO Multiplexing
- 信号驱动 IO / Signal Driven IO
- 异步 IO / AsyncAsynchronous IO
用户空间的用户缓冲区向内核空间的内核缓冲区 等待数据就绪
硬件的硬件设备向内核空间的内核缓冲区 准备数据
用户空间的用户缓冲区向内核空间的内核缓冲区 读取数据
阻塞 IO / Blocking IO#
非阻塞 IO / Nonblocking IO#
IO 多路复用 / IO Multiplexing#
无论是阻塞 IO 还是非阻塞 IO,用户在一阶段都需要 recvfrom 获取数据,但都需要等待数据就绪,无非就是阻塞等待还是非阻塞等待,总之就只能处理这一个
但是如果同时监听多个数据源,那个数据源就绪就处理那个数据源呢,这就是 IO 多路复用
在 Linux 中文件描述符 file descriptor 可以关联一切,这里就可以是网络 socket,同时等待多个 fd,谁就绪处理谁
IO 多路复用有多种方案,如 select、poll、epoll,select、poll 只能知道 fd 集合中有 fd 就绪,不知道具体哪个
IO 多路复用 - select#
将 fd 转成二进制标识位存储,如监听 fd=1 2 5,就是最右数第 1 2 5 位标记为 1,传给内核空间
如果对应位的 fd 就绪则重记为 1,其余则记 0,遍历传到用户空间的类位图,那个位是 1 则代表就绪
好处就是非常省空间;坏处就是不能超过 1024,且 fd_set 需要来回拷贝,并且检查时还需要整个遍历
IO 多路复用 - poll#
poll 提升了 select 1024 的限制,用链表存储理论无上限,但没必要,越长遍历时越耗性能
IO 多路复用 - epoll#
epoll 采用红黑树,在内核空间中维护一个红黑树,记录所有需要监听的 fd;同时维护一个就绪列表,存放所有就绪的 fd
流程:用户空间通过 epoll_create 创建 epoll 实例,通过 epoll_ctl 往红黑树中添加或删除 fd,通过 epoll_wait 等待就绪事件,并从就绪列表中获取就绪的 fd
当某个 fd 就绪时,内核会将其添加到就绪列表中。epoll_wait 只需要从就绪列表中取出就绪的 fd,不需要遍历整个 fd 集合
好处:无 fd 上限,无需拷贝整个 fd 集合,只传递单个 fd 及操作,只返回就绪 fd,利用 ep_poll_callback 机制监听 fd 状态,无需遍历所有 fd
IO 多路复用 - 事件通知机制#
当监听的 fd 上发生事件(如数据可读、可写)时,内核会通过回调机制,如 epoll 的 ep_poll_callback 通知 epoll 实例
内核会将就绪的 fd 添加到 epoll 实例的就绪列表中。 应用程序调用 epoll_wait 时,就可以直接从就绪列表中获取已发生事件的 fd 进行处理,而无需遍历所有监听的 fd
实际使用 ET 模式,即只有新数据才会通知,对于已通知的数据,循环进行非阻塞读取,直到读取完成
IO 多路复用 - web 服务流程#
信号驱动 IO#
信号驱动 IO 是一种非阻塞 IO 模型
应用程序可以设置一个信号处理函数,当内核在 fd 就绪时发送这个信号给进程,进程收到信号,立即执行 recvfrom 读取数据
好处:非阻塞,无需主动轮询
坏处:读取数据仍然阻塞 正常,信号处理有开销,信号可能丢失,不如 IO 多路复用在高并发中
异步 IO#
异步 IO 在用户发起 IO 操作后立即返回,没有任何阻塞阶段
内核不仅负责等待数据就绪,还要负责将数据从内核空间拷贝到用户空间,整个 IO 操作完成之后,内核才会根据用户注册的回调函数或信号通知用户进程
好处:用户进程在发起 IO 请求到接收到完成通知期间,完全非阻塞
坏处:复杂
同步与异步#
异步 IO 在二阶段也就是读取数据阶段也是异步的
Redis 是单线程的吗?#
Redis 到底是单线程还是多线程?
- Redis 的核心业务部分 命令处理,是单线程
- 整个 Redis,是多线程
在 Redis 版本选代过程中,在两个重要的时间节点上引入了多线程的支持:
- Redis V4.0:引入多线程异步处理一些耗时较旧的任务,例如异步删除命令 unlink
- Redis v6.0:在核心网络模型中引入多线程,进一步提高对于多核 CPU 的利用率
对于 Redis 核心网络模型,在 Redis v6.0 之前都是单线程,利用 epoll 这样的 IO 多路复用技术在事件循环中不断处理客户端的情况
Redis 为什么选择单线程?
- Redis 在命令处理上纯内存操作,本身执行速度就很快了,性能瓶颈在于网络 IO 而非执行速度,因此专注于网络 IO 优化比多线程更有必要
- 多线程会导致过多的上下文切换,反而带来不必要的开销
- 引入多线程还会存在线程安全问题,还需引入线程锁这类安全手段,反而浪费性能
Redis 实现的网络模型#
此文由 Mix Space 同步更新至 xLog
原始链接为 https://blog.0xling.cyou/posts/redis/redis-4