为什么 Redis 单线程能支撑高并发?
最近在看 UNIX 网络编程并研究了一下 Redis 的实现 , 感觉 Redis 的源代码十分适合阅读和分析 , 其中 I/O 多路复用(mutiplexing)部分的实现非常干净和优雅 , 在这里想对这部分的内容进行简单的整理 。
几种 I/O 模型为什么 Redis 中要使用 I/O 多路复用这种技术呢?
首先 , Redis 是跑在单线程中的 , 所有的操作都是按照顺序线性执行的 , 但是由于读写操作等待用户输入或输出都是阻塞的 , 所以 I/O 操作在一般情况下往往不能直接返回 , 这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务 , 而 I/O 多路复用就是为了解决这个问题而出现的 。
Blocking I/O先来看一下传统的阻塞 I/O 模型到底是如何工作的:当使用 read 或者 write 对某一个**文件描述符(File Descriptor 以下简称 FD)**进行读写时 , 如果当前 FD 不可读或不可写 , 整个 Redis 服务就不会对其它的操作作出响应 , 导致整个服务不可用 。
这也就是传统意义上的 , 也就是我们在编程中使用最多的阻塞模型:
blocking-io
阻塞模型虽然开发中非常常见也非常易于理解 , 但是由于它会影响其他 FD 对应的服务 , 所以在需要处理多个客户端任务的时候 , 往往都不会使用阻塞模型 。
I/O 多路复用虽然还有很多其它的 I/O 模型 , 但是在这里都不会具体介绍 。
阻塞式的 I/O 模型并不能满足这里的需求 , 我们需要一种效率更高的 I/O 模型来支撑 Redis 的多个客户(redis-cli) , 这里涉及的就是 I/O 多路复用模型了:
I:O-Multiplexing-Mode
在 I/O 多路复用模型中 , 最重要的函数调用就是 select , 该方法的能够同时监控多个文件描述符的可读可写情况 , 当其中的某些文件描述符可读或者可写时 , select 方法就会返回可读以及可写的文件描述符个数 。
关于 select 的具体使用方法 , 在网络上资料很多 , 这里就不过多展开介绍了;
与此同时也有其它的 I/O 多路复用函数 epoll/kqueue/evport , 它们相比 select 性能更优秀 , 同时也能支撑更多的服务 。
Reactor 设计模式Redis 服务采用 Reactor 的方式来实现文件事件处理器(每一个网络连接其实都对应一个文件描述符)
【为什么 Redis 单线程能支撑高并发?】redis-reactor-pattern
文件事件处理器使用 I/O 多路复用模块同时监听多个 FD , 当 accept、read、write 和 close 文件事件产生时 , 文件事件处理器就会回调 FD 绑定的事件处理器 。
虽然整个文件事件处理器是在单线程上运行的 , 但是通过 I/O 多路复用模块的引入 , 实现了同时对多个 FD 读写的监控 , 提高了网络通信模型的性能 , 同时也可以保证整个 Redis 服务实现的简单 。
I/O 多路复用模块I/O 多路复用模块封装了底层的 select、epoll、avport 以及 kqueue 这些 I/O 多路复用函数 , 为上层提供了相同的接口 。
ae-module
在这里我们简单介绍 Redis 是如何包装 select 和 epoll 的 , 简要了解该模块的功能 , 整个 I/O 多路复用模块抹平了不同平台上 I/O 多路复用函数的差异性 , 提供了相同的接口:
- static int aeApiCreate(aeEventLoop *eventLoop)
- static int aeApiResize(aeEventLoop *eventLoop, int setsize)
- static void aeApiFree(aeEventLoop *eventLoop)
- static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask)
- static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask)
- static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp)
// selecttypedef struct aeApiState {fd_set rfds, wfds;fd_set _rfds, _wfds;} aeApiState;// epolltypedef struct aeApiState {int epfd;struct epoll_event *events;} aeApiState;
这些上下文信息会存储在 eventLoop 的 void *state 中 , 不会暴露到上层 , 只在当前子模块中使用 。封装 select 函数select 可以监控 FD 的可读、可写以及出现错误的情况 。
在介绍 I/O 多路复用模块如何对 select 函数封装之前 , 先来看一下 select 函数使用的大致流程:
int fd = /* file descriptor */fd_set rfds;FD_ZERO(FD_SET(fd,; ) {select(fd+1,if (FD_ISSET(fd,--tt-darkmode-color: #A3A3A3;">初始化一个可读的 fd_set 集合 , 保存需要监控可读性的 FD;使用 FD_SET 将 fd 加入 rfds; 调用 select 方法监控 rfds 中的 FD 是否可读; 当 select 返回时 , 检查 FD 的状态并完成对应的操作 。 而在 Redis 的 ae_select 文件中代码的组织顺序也是差不多的 , 首先在 aeApiCreate 函数中初始化 rfds 和 wfds:
- 看不上|为什么还有用户看不上华为Mate40系列来看看内行人怎么说
- 制药领域|为什么AI制药这么火,为什么是现在?
- 手机壳里头|为什么要在手机壳里面夹钱?10个有9个不懂,我才知道大有讲究
- 短视频|全球最火APP?抖音爆火背后离不开这几剂“猛药”为什么抖音能够这么火?
- 电商快递|包邮不香吗,为什么还有人加49元让小哥穿西装专车送快递?
- 团队|为什么项目管理非常重要?
- 猫腻|为什么拼多多上商品价格那么便宜还包邮?有什么猫腻?看完明白了
- 刷机|前几年满大街的“刷机”服务去哪里了,为什么大家都不爱刷机了?
- 手机|便宜没好货!为什么二手iPhone很便宜,这些手机都来自哪儿?
- 中国|相对论Vol.48丨一个“歪果仁”,为什么要在海外电商平台直播带中国货