「黑马 Redis 原理」二、网络模型

用户空间、内核空间

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