1. 首页
  2. IT资讯

为什么 Redis 单线程却能支撑高并发?

“u003Cpu003E优质文章,及时送达u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRcsUnd92eoboDR” img_width=”640″ img_height=”29″ alt=”为什么 Redis 单线程却能支撑高并发?” inline=”0″u003Eu003Cpu003E作者 | Dravenessu003Cu002Fpu003Eu003Cpu003E链接 | draveness.meu002Fredis-io-multipleu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Exiu003Cu002Fiu003Engu003Cu002Fpu003Eu003Cpu003E上篇 | 35 个小细节,提升 Java 代码运行效率u003Cu002Fpu003Eu003Cpu003E最近在看 UNIX 网络编程并研究了一下 Redis 的实现,感觉 Redis 的源代码十分适合阅读和分析,其中 Iu002FO 多路复用(mutipleu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Exiu003Cu002Fiu003Eng)部分的实现非常干净和优雅,在这里想对这部分的内容进行简单的整理。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch1 toutiao-origin=”h2″u003E几种 Iu002FO 模型u003Cu002Fh1u003Eu003Cpu003E为什么 Redis 中要使用 Iu002FO 多路复用这种技术呢?u003Cu002Fpu003Eu003Cpu003E首先,Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 Iu002FO 操作在一般情况下往往不能直接返回,这会导致某一文件的 Iu002FO 阻塞导致整个进程无法对其它客户提供服务,而 Iu002FO 多路复用就是为了解决这个问题而出现的。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch1 toutiao-origin=”h2″u003EBlocking Iu002FOu003Cu002Fh1u003Eu003Cpu003E先来看一下传统的阻塞 Iu002FO 模型到底是如何工作的:当使用 read 或者 write 对某一个文件描述符(File Descriptor 以下简称 FD)进行读写时,如果当前 FD 不可读或不可写,整个 Redis 服务就不会对其它的操作作出响应,导致整个服务不可用。u003Cu002Fpu003Eu003Cpu003E这也就是传统意义上的,也就是我们在编程中使用最多的阻塞模型:u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp9.pstatp.comu002Flargeu002Fpgc-imageu002FRgjm6oq4mzanr9″ img_width=”1000″ img_height=”750″ alt=”为什么 Redis 单线程却能支撑高并发?” inline=”0″u003Eu003Cpu003E阻塞模型虽然开发中非常常见也非常易于理解,但是由于它会影响其他 FD 对应的服务,所以在需要处理多个客户端任务的时候,往往都不会使用阻塞模型。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch1 toutiao-origin=”h2″u003EIu002FO 多路复用u003Cu002Fh1u003Eu003Cpu003E虽然还有很多其它的 Iu002FO 模型,但是在这里都不会具体介绍。u003Cu002Fpu003Eu003Cpu003E阻塞式的 Iu002FO 模型并不能满足这里的需求,我们需要一种效率更高的 Iu002FO 模型来支撑 Redis 的多个客户(redis-cli),这里涉及的就是 Iu002FO 多路复用模型了:u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002FRgjm6p17FTRkVT” img_width=”1000″ img_height=”750″ alt=”为什么 Redis 单线程却能支撑高并发?” inline=”0″u003Eu003Cpu003E在 Iu002FO 多路复用模型中,最重要的函数调用就是 select,该方法的能够同时监控多个文件描述符的可读可写情况,当其中的某些文件描述符可读或者可写时,select 方法就会返回可读以及可写的文件描述符个数。u003Cu002Fpu003Eu003Cpu003E关于 select 的具体使用方法,在网络上资料很多,这里就不过多展开介绍了;u003Cu002Fpu003Eu003Cpu003E与此同时也有其它的 Iu002FO 多路复用函数 epollu002Fkqueueu002Fevport,它们相比 select 性能更优秀,同时也能支撑更多的服务。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch1 toutiao-origin=”h2″u003EReactor 设计模式u003Cu002Fh1u003Eu003Cpu003ERedis 服务采用 Reactor 的方式来实现文件事件处理器(每一个网络连接其实都对应一个文件描述符)u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRgjm6pN4MMm1Uj” img_width=”1000″ img_height=”750″ alt=”为什么 Redis 单线程却能支撑高并发?” inline=”0″u003Eu003Cpu003E文件事件处理器使用 Iu002FO 多路复用模块同时监听多个 FD,当 accept、read、write 和 close 文件事件产生时,文件事件处理器就会回调 FD 绑定的事件处理器。u003Cu002Fpu003Eu003Cpu003E虽然整个文件事件处理器是在单线程上运行的,但是通过 Iu002FO 多路复用模块的引入,实现了同时对多个 FD 读写的监控,提高了网络通信模型的性能,同时也可以保证整个 Redis 服务实现的简单。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch1 toutiao-origin=”h2″u003EIu002FO 多路复用模块u003Cu002Fh1u003Eu003Cpu003EIu002FO 多路复用模块封装了底层的 select、epoll、avport 以及 kqueue 这些 Iu002FO 多路复用函数,为上层提供了相同的接口。u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRgjm6pj4eaQBKM” img_width=”1000″ img_height=”581″ alt=”为什么 Redis 单线程却能支撑高并发?” inline=”0″u003Eu003Cpu003E在这里我们简单介绍 Redis 是如何包装 select 和 epoll 的,简要了解该模块的功能,整个 Iu002FO 多路复用模块抹平了不同平台上 Iu002FO 多路复用函数的差异性,提供了相同的接口:u003Cu002Fpu003Eu003Culu003Eu003Cliu003Eu003Cpu003Estatic int aeApiCreate(aeEventLoop *eventLoop)u003Cu002Fpu003Eu003Cu002Fliu003Eu003Cliu003Eu003Cpu003Estatic int aeApiResize(aeEventLoop *eventLoop, int setsize)u003Cu002Fpu003Eu003Cu002Fliu003Eu003Cliu003Eu003Cpu003Estatic void aeApiFree(aeEventLoop *eventLoop)u003Cu002Fpu003Eu003Cu002Fliu003Eu003Cliu003Eu003Cpu003Estatic int aeApiAu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-3″u003Eddu003Cu002Fiu003EEvent(aeEventLoop *eventLoop, int fd, int mask)u003Cu002Fpu003Eu003Cu002Fliu003Eu003Cliu003Eu003Cpu003Estatic void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask)u003Cu002Fpu003Eu003Cu002Fliu003Eu003Cliu003Eu003Cpu003Estatic int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp)u003Cu002Fpu003Eu003Cu002Fliu003Eu003Cu002Fulu003Eu003Cpu003E同时,因为各个函数所需要的参数不同,我们在每一个子模块内部通过一个 aeApiState 来存储需要的上下文信息:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Eu002Fu002F selectu003Cbru003Etypedef struct aeApiState {u003Cbru003Efd_set rfds, wfds;u003Cbru003Efd_set _rfds, _wfds;u003Cbru003E} aeApiState;u003Cbru003Eu003Cbru003Eu002Fu002F epollu003Cbru003Etypedef struct aeApiState {u003Cbru003Eint epfd;u003Cbru003Estruct epoll_event *events;u003Cbru003E} aeApiState;u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E这些上下文信息会存储在 eventLoop 的 void *state 中,不会暴露到上层,只在当前子模块中使用。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch1 toutiao-origin=”h2″u003E封装 select 函数u003Cu002Fh1u003Eu003Cpu003Eselect 可以监控 FD 的可读、可写以及出现错误的情况。u003Cu002Fpu003Eu003Cpu003E在介绍 Iu002FO 多路复用模块如何对 select 函数封装之前,先来看一下 select 函数使用的大致流程:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Eint fd = u002F* file descriptor *u002Fu003Cbru003Eu003Cbru003Efd_set rfds;u003Cbru003EFD_ZERO(&rfds);u003Cbru003EFD_SET(fd, &rfds)u003Cbru003Eu003Cbru003Efor ( ; ; ) {u003Cbru003Eselect(fd+1, &rfds, , , );u003Cbru003Eif (FD_ISSET(fd, &rfds)) {u003Cbru003Eu002F* file descriptor `fd` becomes readable *u002Fu003Cbru003E}u003Cbru003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Culu003Eu003Cliu003Eu003Cpu003E初始化一个可读的 fd_set 集合,保存需要监控可读性的 FD;u003Cu002Fpu003Eu003Cu002Fliu003Eu003Cliu003Eu003Cpu003E使用 FD_SET 将 fd 加入 rfds;u003Cu002Fpu003Eu003Cu002Fliu003Eu003Cliu003Eu003Cpu003E调用 select 方法监控 rfds 中的 FD 是否可读;u003Cu002Fpu003Eu003Cu002Fliu003Eu003Cliu003Eu003Cpu003E当 select 返回时,检查 FD 的状态并完成对应的操作。u003Cu002Fpu003Eu003Cu002Fliu003Eu003Cu002Fulu003Eu003Cpu003E而在 Redis 的 ae_select 文件中代码的组织顺序也是差不多的,首先在 aeApiCreate 函数中初始化 rfds 和 wfds:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Estatic int aeApiCreate(aeEventLoop *eventLoop) {u003Cbru003EaeApiState *state = zmalloc(sizeof(aeApiState));u003Cbru003Eif (!state) return -1;u003Cbru003EFD_ZERO(&state->rfds);u003Cbru003EFD_ZERO(&state->wfds);u003Cbru003EeventLoop->apidata = state;u003Cbru003Ereturn 0;u003Cbru003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E而 aeApiAu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-3″u003Eddu003Cu002Fiu003EEvent 和 aeApiDelEvent 会通过 FD_SET 和 FD_CLR 修改 fd_set 中对应 FD 的标志位:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Eu003Cstrong toutiao-origin=”span”u003Estaticu003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span”u003Eintu003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span”u003EaeApiAu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-3″u003Eddu003Cu002Fiu003EEventu003Cu002Fstrongu003E(aeEventLoop *eventLoop, int fd, int mask) {u003Cbru003EaeApiState *state = eventLoop->apidata;u003Cbru003Eif (mask & AE_READABLE) FD_SET(fd,&state->rfds);u003Cbru003Eif (mask & AE_WRITABLE) FD_SET(fd,&state->wfds);u003Cbru003Ereturn 0;u003Cbru003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E整个 ae_select 子模块中最重要的函数就是 aeApiPoll,它是实际调用 select 函数的部分,其作用就是在 Iu002FO 多路复用函数返回时,将对应的 FD 加入 aeEventLoop 的 fired 数组中,并返回事件的个数:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Estatic int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {u003Cbru003EaeApiState *state = eventLoop->apidata;u003Cbru003Eint retval, j, numevents = 0;u003Cbru003Eu003Cbru003Ememcpy(&state->_rfds,&state->rfds,sizeof(fd_set));u003Cbru003Ememcpy(&state->_wfds,&state->wfds,sizeof(fd_set));u003Cbru003Eu003Cbru003Eretval = select(eventLoop->maxfd+1,u003Cbru003E&state->_rfds,&state->_wfds,,tvp);u003Cbru003Eif (retval > 0) {u003Cbru003Efor (j = 0; j <= eventLoop->maxfd; j++) {u003Cbru003Eint mask = 0;u003Cbru003EaeFileEvent *fe = &eventLoop->events[j];u003Cbru003Eu003Cbru003Eif (fe->mask == AE_NONE) continue;u003Cbru003Eif (fe->mask & AE_READABLE && FD_ISSET(j,&state->_rfds))u003Cbru003Emask |= AE_READABLE;u003Cbru003Eif (fe->mask & AE_WRITABLE && FD_ISSET(j,&state->_wfds))u003Cbru003Emask |= AE_WRITABLE;u003Cbru003EeventLoop->fired[numevents].fd = j;u003Cbru003EeventLoop->fired[numevents].mask = mask;u003Cbru003Enumevents++;u003Cbru003E}u003Cbru003E}u003Cbru003Ereturn numevents;u003Cbru003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch1 toutiao-origin=”h2″u003E封装 epoll 函数u003Cu002Fh1u003Eu003Cpu003ERedis 对 epoll 的封装其实也是类似的,使用 epoll_create 创建 epoll 中使用的 epfd:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Estatic int aeApiCreate(aeEventLoop *eventLoop) {u003Cbru003EaeApiState *state = zmalloc(sizeof(aeApiState));u003Cbru003Eu003Cbru003Eif (!state) return -1;u003Cbru003Estate->events = zmalloc(sizeof(struct epoll_event)*eventLoop->setsize);u003Cbru003Eif (!state->events) {u003Cbru003Ezfree(state);u003Cbru003Ereturn -1;u003Cbru003E}u003Cbru003Estate->epfd = epoll_create(1024); u002F* 1024 is just a hint for the kernel *u002Fu003Cbru003Eif (state->epfd == -1) {u003Cbru003Ezfree(state->events);u003Cbru003Ezfree(state);u003Cbru003Ereturn -1;u003Cbru003E}u003Cbru003EeventLoop->apidata = state;u003Cbru003Ereturn 0;u003Cbru003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E在 aeApiAu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-3″u003Eddu003Cu002Fiu003EEvent 中使用 epoll_ctl 向 epfd 中添加需要监控的 FD 以及监听的事件:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Eu003Cstrong toutiao-origin=”span”u003Estaticu003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span”u003Eintu003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span”u003EaeApiAu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-3″u003Eddu003Cu002Fiu003EEventu003Cu002Fstrongu003E(aeEventLoop *eventLoop, int fd, int mask) {u003Cbru003EaeApiState *state = eventLoop->apidata;u003Cbru003Estruct epoll_event ee = {0}; u002F* avoid valgrind warning *u002Fu003Cbru003Eu002F* If the fd was already monitored for some event, we need a MODu003Cbru003E* operation. Otherwise we need an ADD operation. *u002Fu003Cbru003Eint op = eventLoop->events[fd].mask == AE_NONE ?u003Cbru003EEPOLL_CTL_ADD : EPOLL_CTL_MOD;u003Cbru003Eu003Cbru003Eee.events = 0;u003Cbru003Emask |= eventLoop->events[fd].mask; u002F* Merge old events *u002Fu003Cbru003Eif (mask & AE_READABLE) ee.events |= EPOLLIN;u003Cbru003Eif (mask & AE_WRITABLE) ee.events |= EPOLLOUT;u003Cbru003Eee.data.fd = fd;u003Cbru003Eif (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1;u003Cbru003Ereturn 0;u003Cbru003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E由于 epoll 相比 select 机制略有不同,在 epoll_wait 函数返回时并不需要遍历所有的 FD 查看读写情况;在 epoll_wait 函数返回时会提供一个 epoll_event 数组:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Etypedef union epoll_data {u003Cbru003Evoid *ptr;u003Cbru003Eint fd; u002F* 文件描述符 *u002Fu003Cbru003Euint32_t u32;u003Cbru003Euintu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-3″u003E64u003Cu002Fiu003E_t uu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-3″u003E64u003Cu002Fiu003E;u003Cbru003E} epoll_data_t;u003Cbru003Eu003Cbru003Estruct epoll_event {u003Cbru003Euint32_t events; u002F* Epoll 事件 *u002Fu003Cbru003Eepoll_data_t data;u003Cbru003E};u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E其中保存了发生的 epoll 事件(EPOLLIN、EPOLLOUT、EPOLLERR 和 EPOLLHUP)以及发生该事件的 FD。u003Cu002Fpu003Eu003Cpu003EaeApiPoll 函数只需要将 epoll_event 数组中存储的信息加入 eventLoop 的 fired 数组中,将信息传递给上层模块:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Estatic int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {u003Cbru003EaeApiState *state = eventLoop->apidata;u003Cbru003Eint retval, numevents = 0;u003Cbru003Eu003Cbru003Eretval = epoll_wait(state->epfd,state->events,eventLoop->setsize,u003Cbru003Etvp ? (tvp->tv_sec*1000 + tvp->tv_usecu002F1000) : -1);u003Cbru003Eif (retval > 0) {u003Cbru003Eint j;u003Cbru003Eu003Cbru003Enumevents = retval;u003Cbru003Efor (j = 0; j < numevents; j++) {u003Cbru003Eint mask = 0;u003Cbru003Estruct epoll_event *e = state->events+j;u003Cbru003Eu003Cbru003Eif (e->events & EPOLLIN) mask |= AE_READABLE;u003Cbru003Eif (e->events & EPOLLOUT) mask |= AE_WRITABLE;u003Cbru003Eif (e->events & EPOLLERR) mask |= AE_WRITABLE;u003Cbru003Eif (e->events & EPOLLHUP) mask |= AE_WRITABLE;u003Cbru003EeventLoop->fired[j].fd = e->data.fd;u003Cbru003EeventLoop->fired[j].mask = mask;u003Cbru003E}u003Cbru003E}u003Cbru003Ereturn numevents;u003Cbru003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch1 toutiao-origin=”h2″u003E子模块的选择u003Cu002Fh1u003Eu003Cpu003E因为 Redis 需要在多个平台上运行,同时为了最大化执行的效率与性能,所以会根据编译平台的不同选择不同的 Iu002FO 多路复用函数作为子模块,提供给上层统一的接口;在 Redis 中,我们通过宏定义的使用,合理的选择不同的子模块:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Eu003Cstrong toutiao-origin=”span”u003E#ifdef HAVE_EVPORTu003Cu002Fstrongu003Eu003Cbru003Eu003Cstrong toutiao-origin=”span”u003E#include “ae_evport.c”u003Cu002Fstrongu003Eu003Cbru003Eu003Cstrong toutiao-origin=”span”u003E#elseu003Cu002Fstrongu003Eu003Cbru003Eu003Cstrong toutiao-origin=”span”u003E#ifdef HAVE_EPOLLu003Cu002Fstrongu003Eu003Cbru003Eu003Cstrong toutiao-origin=”span”u003E#include “ae_epoll.c”u003Cu002Fstrongu003Eu003Cbru003Eu003Cstrong toutiao-origin=”span”u003E#elseu003Cu002Fstrongu003Eu003Cbru003Eu003Cstrong toutiao-origin=”span”u003E#ifdef HAVE_KQUEUEu003Cu002Fstrongu003Eu003Cbru003Eu003Cstrong toutiao-origin=”span”u003E#include “ae_kqueue.c”u003Cu002Fstrongu003Eu003Cbru003Eu003Cstrong toutiao-origin=”span”u003E#elseu003Cu002Fstrongu003Eu003Cbru003Eu003Cstrong toutiao-origin=”span”u003E#include “ae_select.c”u003Cu002Fstrongu003Eu003Cbru003Eu003Cstrong toutiao-origin=”span”u003E#endifu003Cu002Fstrongu003Eu003Cbru003Eu003Cstrong toutiao-origin=”span”u003E#endifu003Cu002Fstrongu003Eu003Cbru003Eu003Cstrong toutiao-origin=”span”u003E#endifu003Cu002Fstrongu003Eu003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E因为 select 函数是作为 POSIX 标准中的系统调用,在不同版本的操作系统上都会实现,所以将其作为保底方案:u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRgjm6q15WKCkVQ” img_width=”1000″ img_height=”1095″ alt=”为什么 Redis 单线程却能支撑高并发?” inline=”0″u003Eu003Cpu003ERedis 会优先选择时间复杂度为 u003Ccodeu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E$O(1)$u003Cu002Fstrongu003Eu003Cu002Fcodeu003E的 Iu002FO 多路复用函数作为底层实现,包括 Solaries 10 中的 evport、Linux 中的 epoll 和 macOSu002FFreeBSD 中的 kqueue,上述的这些函数都使用了内核内部的结构,并且能够服务几u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E十万u003Cu002Fiu003E的文件描述符。u003Cu002Fpu003Eu003Cpu003E但是如果当前编译环境没有上述函数,就会选择 select 作为备选方案,由于其在使用时会扫描全部监听的描述符,所以其时间复杂度较差 u003Ccodeu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E$O(n)$u003Cu002Fstrongu003Eu003Cu002Fcodeu003E,并且只能同时服务 1024 个文件描述符,所以一般并不会以 select 作为第一方案使用。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch1 toutiao-origin=”h2″u003E总结u003Cu002Fh1u003Eu003Cpu003ERedis 对于 Iu002FO 多路复用模块的设计非常简洁,通过宏保证了 Iu002FO 多路复用模块在不同平台上都有着优异的性能,将不同的 Iu002FO 多路复用函数封装成相同的 API 提供给上层使用。u003Cu002Fpu003Eu003Cpu003E整个模块使 Redis 能以单进程运行的同时服务成千上万个文件描述符,避免了由于多进程应用的引入导致代码实现复杂度的提升,减少了出错的可能性。u003Cu002Fpu003Eu003Cpu003Eu003Cu002Fpu003Eu003Ch1 toutiao-origin=”h2″u003E参考u003Cu002Fh1u003Eu003Cpu003Eu003Cemu003E[1] http:u002Fu002Fman7.orgu002Flinuxu002Fman-pagesu002Fman2u002Fselect.2.htmlu003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cemu003E[2] https:u002Fu002Fen.wikipedia.orgu002Fwikiu002FReactor_patternu003Cu002Femu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cemu003E[3] https:u002Fu002Fpeople.eecs.berkeley.eduu002F~sangjinu002F2012u002F12u002F21u002Fepoll-vs-kqueue.htmlu003Cu002Femu003Eu003Cu002Fpu003Eu003Cp class=”pgc-end-source”u003E-END-u003Cu002Fpu003Eu003Cp class=”pgc-end-source”u003E如果看到这里,说明你喜欢这篇文章,请转发、点赞。u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E微信u003Cu002Fiu003E搜索「web_resource」,u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E关注u003Cu002Fiu003E后回复「进群」或者扫描下方二维码即可进入无广告交流群。u003Cu002Fpu003E”

原文始发于:为什么 Redis 单线程却能支撑高并发?

主题测试文章,只做测试使用。发布者:逗乐男神i,转转请注明出处:http://www.cxybcw.com/17818.html

联系我们

13687733322

在线咨询:点击这里给我发消息

邮件:1877088071@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code