首页 > 编程知识 正文

redis单进程单线程,redis内部的3个线程

时间:2023-05-03 14:55:42 阅读:130346 作者:4983

Redis介绍官网的Redis介绍:

Redis是一个基于内存的存储系统,可以用作数据库、缓存和消息中间件。 Redis提供了strings、hashes、lists、sets、sorted sets、bitmaps、hyperloglogs、geo等许多对象和数据结构。

Redis内置了复制、lua脚本、LRU驱动、事务和不同级别的磁盘持久化功能,通过哨兵Sentinel和Redis群集确保了Redis的高可用性。

Redis线程模型Redis服务的实现类似于Reactor,其中每个网络请求都有一个文件描述符FD。 I/O复用模块同时接收多个FD,发生accept、read、write、close等文件事件时,I/O复用程序首先激活支持FD的事件,进入事件队列,进入文件事件程序

使用I/O复用同时接收多个套接字,并且基于套接字当前正在执行的事件选择对应于该套接字的事件处理器; 当被监听的套接字准备执行accept、read、write、close等操作时,I/O复用器将对生成事件的所有套接字进行排队,并按顺序将套接字发送到文件事件调度程序。 文件事件处理器基于通过I/O复用传递的套接字事件类型调用相应的处理器来处理事件。 文件处理器包括指令请求处理器、指令返回处理器、响应处理器的连接等,处理完成后返回结果。 因而,使用Redis的单线程模型主要意味着网络I/o事件处理和密钥值的读写在一个线程上进行,其他持久化、异步删除等操作在附加线程上进行。

Redis为什么用单线程模型这么快? 1、为什么要采用单线程模式

多线程!=快。 如果系统中存在多线程访问资源,则需要类似的锁定机制来保证共享资源的正确性,这将产生开销。 多线程的频繁上下文切换和冲突增加了资源消耗。 Redis基于内存操作,CPU不是主要问题,主要是网络I/o需要时间。 所以单线程处理百万条指令就足够了。 2、为什么单线程机型这么快

纯内存操作; Redis基于内存操作,响应时间在ns内,QPS达到10W的高效数据结构:提供哈希表、跳表等高效操作的数据结构单线程具有多线程资源争夺和线程上下文IO复用机制:使网络IO操作中能够同时处理大量客户端请求,实现高吞吐量; 请注意,Redis 6多线程模型Redis 6提供多线程操作,但Redis仍然是单线程模型。

Redis的多线程模式可以主要配置为多线程执行接收、分析和输出网络请求命令的结果。 Redis官方认为这些是需要时间的主要点。 但是,每个命令的执行仍然是单线程执行的。

(图像源: 《和杠精 聊Redis多线程》 )

Linux复用Linux复用技术是指在建立套接字连接后,对套接字生成的多个文件描述符FD进行监视,并在能够对某个文件描述符进行读写时,将相应的数据复制到用户空间,进行读写的技术。 在处理网络请求时,调用select函数的进程被阻止。

Linux提供了select、poll、epoll等实现方式,本质上都是同步IO方式,读写事件准备完毕后需要自己负责读写,这一读写过程被屏蔽。

select调用过程如下:

首先,将需要接收的fd_set从用户空间复制到内核,注册回调函数遍历所有fd,调用socket_poll等poll方法,如果有可读写的mask掩码,则返回

select在一个进程中可以打开的最大连接数有限。 32位计算机的大小为3232。 但是,epoll 1G内存机可以打开10万左右的连接。 由于select在每次调用时线性扫描所有连接,因此软盘集合较大时扫描速度较慢。 另一方面,epoll在软盘上注册了回调函数。 这个机制带来效率的提高。 也就是说,由于只有可用于活动的软盘才调用回调函数,因此即使软盘数量增加,效率也不会降低。 (有关选择、epoll的区别,请阅读本文。 我深刻理解select、poll和epoll的区别)

Redis I/O复用源代码分析

Redis IO复用模型(epoll ) )。

Redis采用事件驱动机制,提供了evport、epoll、kqueue、select4四种事件驱动模型。

接下来,我们来看看epoll模型的源代码实现。 在ae_epoll.c文件中)。

1、创建epoll实例:

staticintaeapicreate (aeeventloop * event loop ) /省略部分代码state-epfd=epoll_create(1024 );/* 1024 isjustahintforthekernel *//事件loop-apidata=state; 返回0; } 2、添加监视事件,将文件描述符和对应的事件添加到对应的IO复用中

static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { aeApiState *state = eventLoop->apidata; // 省略部分代码 mask |= eventLoop->events[fd].mask; /* Merge old events */ // 添加AE读写事件 if (mask & AE_READABLE) ee.events |= EPOLLIN; if (mask & AE_WRITABLE) ee.events |= EPOLLOUT; ee.data.fd = fd; if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1; return 0;}

3、等待事件触发,接收到事件之后将事件保存

static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { aeApiState *state = eventLoop->apidata; // 省略部分代码 for (j = 0; j < numevents; j++) { int mask = 0; struct epoll_event *e = state->events+j;// 将epoll事件转换成AE事件 if (e->events & EPOLLIN) mask |= AE_READABLE; if (e->events & EPOLLOUT) mask |= AE_WRITABLE; if (e->events & EPOLLERR) mask |= AE_WRITABLE|AE_READABLE; if (e->events & EPOLLHUP) mask |= AE_WRITABLE|AE_READABLE; // 将事件保存在fired数组中,后续处理会用到该数组 eventLoop->fired[j].fd = e->data.fd; eventLoop->fired[j].mask = mask; } return numevents;}

4、事件处理

主函数中通过调用aeMain() 函数进行监听:

void aeMain(aeEventLoop *eventLoop) { eventLoop->stop = 0; // 循环监听 while (!eventLoop->stop) { // 调用IO多路复用,如果返回事件,就激活事件处理器进行处理 aeProcessEvents(eventLoop, AE_ALL_EVENTS| AE_CALL_BEFORE_SLEEP| AE_CALL_AFTER_SLEEP); }}

aeProcessEvents函数中调用IO多路复用API进行监听;当IO多路复用返回事件后,aeProcessEvents执行每个激活事件的回调函数进行处理。

int aeProcessEvents(aeEventLoop *eventLoop, int flags){ // 省略部分代码 // 调用IO多路复用 API,获取激活事件。事件保存在eventLoop->fired[]数组中 numevents = aeApiPoll(eventLoop, tvp); for (j = 0; j < numevents; j++) { aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd]; int mask = eventLoop->fired[j].mask; int fd = eventLoop->fired[j].fd; int fired = 0; /* Number of events fired for current fd. */ int invert = fe->mask & AE_BARRIER; // 先调用回调函数执行可读事件 if (!invert && fe->mask & mask & AE_READABLE) { fe->rfileProc(eventLoop,fd,fe->clientData,mask); fired++; fe = &eventLoop->events[fd]; /* Refresh in case of resize. */ } // 再调用回调函数执行可写事件 if (fe->mask & mask & AE_WRITABLE) { if (!fired || fe->wfileProc != fe->rfileProc) { fe->wfileProc(eventLoop,fd,fe->clientData,mask); fired++; } } /* If we have to invert the call, fire the readable event now * after the writable one. */ if (invert) { fe = &eventLoop->events[fd]; /* Refresh in case of resize. */ if ((fe->mask & mask & AE_READABLE) && (!fired || fe->wfileProc != fe->rfileProc)) { fe->rfileProc(eventLoop,fd,fe->clientData,mask); fired++; } } processed++; } } // 处理超时事件 if (flags & AE_TIME_EVENTS) processed += processTimeEvents(eventLoop); return processed; /* return the number of processed file/time events */}

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。