分享

【作者面对面问答】包邮送《Redis 5设计与源码分析》5本

 数据和云 2020-07-01

墨墨导读:本文节选自《Redis 5设计与源码分析》,主要为读者分析Redis高性能内幕,重点从源码层次讲解了Redis事件模型,网络IO事件重在使用IO复用模型,时间事件重在限制最大执行CPU时间。最后简单介绍了Redis的进程模型(以后不要简简单单说Redis是单进程单线程了),以及使用命令时需要注意的一些耗时操作。 参与作者墨天轮作者问答(点击“阅读原文可直接参与”)将有机会获得《Redis 5设计与源码分析》包邮到家!

书籍介绍


《Redis 5设计与源码分析》

多名专家联袂推荐,资深专家联合撰写,深入理解Redis 5设计精髓

系统讲解Redis 5设计、数据结构、底层命令实现,以及持久化、主从复制、集群

作者简介


陈雷,好未来学而思网校增长研发负责人,清华与北京邮电大学硕士,曾在百度、腾讯和滴滴等公司工作,12年后端架构经验。合著有《PHP7底层设计与源码实现》。

方波,资深工程师,先后就职于360、百度、滴滴,设计并开发360消息系统Qbus、Nginx接入层、电商网站架构等分布式高并发系统。

黄桃,好未来学而思网校架构师,从事互联网服务端研发与架构工作多年,熟悉PHP、Nginx、Redis等源码实现,乐于学习与分享。合著有《PHP7底层设计与源码实现》。

李乐,好未来PHP工程师,西安电子科技大学硕士,乐于钻研技术与源码研究,对Redis和Nginx有较深理解。

施洪宝, 好未来后端研发工程师,东南大学硕士,对Redis、Nginx等开源软件有较深的理解,熟悉C/C++开发,对高并发、分布式有浓厚兴趣,曾发表EI论文2篇,。

熊浩含,百度研发工程师、PHP开发者,对Redis等开源软件有较深的研究。乐于钻研技术难点,喜欢折腾,在学习思路上有很好的方法论。

闫昌,好未来后端软件开发工程师,深耕信息安全领域多年,对Linux下服务端开发有较深见解,擅长高并发业务的实现。

张仕华,滴滴资深软件开发工程师,热衷于研究高并发场景下的架构设计及实现,熟悉Redis、Nginx和LevelDB等源码,热衷于探究技术本质。

周生政,滴滴后端高级工程师,多年LNMP技术栈开发经验,曾任北京环球购物电商后端技术负责人。热衷于Linux平台效率工具, 熟悉Bash、Docker等自动化工具。

如何得到这本书?


【作者面对面问答】你可以在文末留言写出Redis相关的一些问题,将有机会得到《Redis 5设计与源码分析》作者的留言回复,作者会选取5条比较精致的问答赠与图书,以下这些问题供参考:

  • Redis性能提升的可能方面?说说你的想法?

  • Redis高性能与数据结构的关系,以及如何在实践中应用?

  • 高性能缓存架构中,多线程模型与单进程单线程模型的优缺点?如emcache与Redis架构对比

  • 其他你困扰的问题

大家也可以去京东上自行购买:https://item.jd.com/12566383.html (复制链接至浏览器,即可查看)

活动截止:2019年9月24日12:00,我们会留言回复获奖者,敬请期待。

以下为《Redis 5设计与源码分析》节选:Redis为什么这么快之事件模型详解

Redis作为一个开源的基于内存的键值对存储系统,以出色的性能著称。官方提供的基准测试数据如下图所示, 摘自《How fast is Redis?》(https:///topics/benchmarks,复制链接至浏览器,即可查看

图中横轴为连接数,纵轴为QPS;可以看到Redis单实例甚至可以达到10万+QPS。那么,Redis作为"单进程单线程"服务端程序,性能为何如此之高?

Redis为什么这么快?


Redis的高性能主要有以下几个原因:

1)基于内存

Redis是基于内存的存储系统,绝大部分的命令处理只是纯粹的内存操作,内存的读写速度是非常快的。

2)单进程单线程

单进程单线程处理请求,避免了不必要的上下文切换,同时不存在加锁释放锁等同步操作,而且实现简单;简单加高性能,何乐而不为呢?

不过,单进程单线程的方式无法发挥多核CPU性能,只能采取部署多个Redis实例来利用多核CPU,但这样会带来一定的运维复杂度。另外,单进程单线程处理某个命令请求而阻塞时,整个Redis服务无法处理其他请求。

注:实际上一个正在运行的Redis Server肯定不止一个进程/线程,详情见附录Redis进程模型小节

3)数据结构

Redis中的数据结构是专门设计的,大部分命令处理的时间复杂度都是O(1),少数命令才是O(logN)或者O(N);比如zset数据类型,为了兼顾范围查找与键查找效率,底层采用跳跃表与字典两种数据结构实现。

4)事件模型

Redis作为服务端程序,必然伴随着大量的客户端链接以及网络IO,网络IO通常是影响服务端程序性能的主要因素。

Redis高效的网络IO源于两点:非阻塞,且采用了成熟的IO多路复用模型select/epoll/kqueue/evport。IO多路复用通常有几个关键点:

  1. 多条连接注册到同一个模型对象,进程只需要在一个模型对象阻塞等待事件发生,而无需再轮询所有连接;

  2. 当某条连接有新的数据处理时,操作系统会唤醒阻塞进程,进程随之可处理事件请求;

  3. Linux系统我们通常会使用epoll,其底层基于红黑树+双向链表;红黑树实现进程高效注册连接fd,双向链表实现进程高效获取连接事件;

Redis除了需要处理网络IO事件(文件事件)之外,还需要处理定时任务事件(时间事件);而定时任务事件的要求只有一点:不能影响命令处理主流程,即不能占用太多CPU时间;


Redis事件模型即为文件事件与时间事件的管理模型;本文将会重点从源码角度为读者介绍Redis事件模型。

事件模型

Redis服务器是典型的事件驱动程序,事件驱动程序通常存在while/for循环,循环等待事件发生并处理,Redis也不例外,其事件循环如下:

while (!eventLoop->stop) {    if (eventLoop->beforesleep != NULL)        eventLoop->beforesleep(eventLoop);    aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);}


函数aeProcessEvents为事件处理主函数,eventLoop表示服务端事件循环,类型为struct aeEventLoop,无论是文件事件还是时间事件都封装在该结构体,定义如下:

typedef struct aeEventLoop {    int stop;        aeFileEvent *events;     aeFiredEvent *fired;     aeTimeEvent *timeEventHead;        void *apidata    aeBeforeSleepProc *beforesleep;    aeBeforeSleepProc *aftersleep;} aeEventLoop;


stop标识事件循环是否结束;events为文件事件数组,存储已经注册的文件事件;fired存储被触发的文件事件;Redis有多个定时任务,因此理论上应该有多个时间事件,多个时间事件形成链表,timeEventHead即为时间事件链表头结点;Redis服务器需要阻塞等待文件事件的发生,进程阻塞之前会调用beforesleep函数,进程因为某种原因被唤醒之后会调用aftersleep函数。Redis底层可以使用四种IO多路复用模型(kqueue、epoll等),apidata是对这四种模型的进一步封装,所以其类型为void*。

文件事件

Redis客户端通过TCP Socket与服务端交互,文件事件指的就是socket的可读可写事件。 socket读写操作有阻塞与非阻塞之分,采用阻塞模式时,一个进程只能处理一条网络连接的读写事件,为了同时处理条网络连接,通常会采用多线程或者多进程,效率低下;非阻塞模式下,可以使用目前比较成熟的IO多路复用模型select/epoll/kqueue/evport等,视不同操作系统而定。

这里只对epoll作简要介绍。epoll是linux内核为处理大量并发网络连接而提出的解决方案,能显著提升系统CPU利用率。epoll使用非常简单,总共只有三个API,epoll_create函数创建一个epoll专用的文件描述符,用于后续epoll相关API调用;epoll_ctl函数向epoll注册、修改或删除需要监控的事件;epoll_wait函数会阻塞进程,直到监控的若干网络连接有事件发生。

int epoll_create(int size)


输入参数size通知内核程序期望注册的网络连接数目,内核以此判断初始分配空间大小;注意在linux2.6.8版本以后,内核动态分配空间,此参数会被忽略。返回参数为epoll专用的文件描述符,不再使用时应该及时关闭此文件描述符。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)


函数执行成功时返回0,否则返回-1,错误码设置在变量errno;输入参数含义如下:

  • epfd:函数epoll_create返回的epoll文件描述符;

  • op:需要进行的操作,EPOLL_CTL_ADD表示注册事件,EPOLL_CTL_MOD表示修改网络连接事件,EPOLL_CTL_DEL表示删除事件;

  • fd:网络连接的socket文件描述符; event:需要监控的事件,结构体epoll_event定义如下

struct epoll_event {__uint32_t events;      epoll_data_t data;     };typedef union epoll_data {void *ptr;int fd;__uint32_t u32;__uint64_t u64;} epoll_data_t;


其中events表示需要监控的事件类型,比较常用的是EPOLLIN文件描述符可读事件,EPOLLOUT文件描述符可写事件;data保存与文件描述符关联的数据。

int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)


函数执行成功时返回0,否则返回-1,错误码设置在变量errno;输入参数含义如下:

  • epfd:函数epoll_create返回的epoll文件描述符;

  • epoll_event:作为输出参数使用,用于回传已触发的事件数组;

  • maxevents:每次能处理的最大事件数目;

  • timeout:epoll_wait函数阻塞超时时间,如果超过timeout时间还没有事件发生,函数不再阻塞直接返回;当timeout等于0时函数立即返回,timeout等于-1时函数会一直阻塞直到有事件发生。

需要注意的是Redis并没有直接使用epoll提供的的API,而是同时支持四种IO多路复用模型,并将这些模型的API进一步统一封装,由文件ae_evport.c、ae_epoll.c、ae_kqueue.c和ae_select.c实现。

static int aeApiCreate(aeEventLoop *eventLoop);static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask);static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask)static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp);


四个函数的输入参数含义如下:

  • eventLoop:事件循环;

  • fd:操作的socket文件描述符;

  • mask或delmask:添加或者删除的事件类型,AE_NONE表示没有任何事件;AE_READABLE表示可读事件;AE_WRITABLE表示可写事件;

  •  tvp:阻塞等待文件事件的超时时间。

而Redis在编译阶段,会检查操作系统支持的IO多路复用模型,并按照一定规则决定使用哪种模型。

以epoll为例,aeApiCreate函数是对epoll_create的封装;aeApiAddEvent函数用于添加事件,是对epoll_ctl的封装;aeApiDelEvent函数用于删除事件,是对epoll_ctl的封装;aeApiPoll是对epoll_wait的封装。此时eventLoop->apidata指向的结构体为:

typedef struct aeApiState {    int epfd;    struct epoll_event *events;} aeApiState;


其中epfd函数epoll_create返回的epoll文件描述符,events存储epoll_wait函数返回时已触发的事件数组。

这里只对等待事件函数aeApiPoll实现作简要介绍:

static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {    aeApiState *state = eventLoop->apidata;    //阻塞等待事件的发生    retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,            tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);    if (retval > 0) {        int j;        numevents = retval;        for (j = 0; j < numevents; j++) {            int mask = 0;            struct epoll_event *e = state->events+j;            //转换事件类型为Redis定义的            if (e->events & EPOLLIN) mask |= AE_READABLE;            if (e->events & EPOLLOUT) mask |= AE_WRITABLE;           //记录已发生事件到fired数组            eventLoop->fired[j].fd = e->data.fd;            eventLoop->fired[j].mask = mask;        }    }    return numevents;}


函数首先需要通过eventLoop->apidata字段获取到epoll模型对应的aeApiState结构体对象,才能调用epoll_wait函数等待事件的发生;而epoll_wait函数将已触发的事件存储到aeApiState对象的events字段,Redis再次遍历所有已触发事件,将其封装在eventLoop->fired数组,数组元素类型为结构体aeFiredEvent,只有两个字段,fd表示发生事件的socket文件描述符,mask表示发生的事件类型,如AE_READABLE可读事件和AE_WRITABLE可写事件。

上面简单介绍了epoll的使用,以及Redis对epoll等IO多路复用模型的封装,下面我们回到本小节的主题,文件事件。结构体aeEventLoop有一个关键字段events,类型为aeFileEvent数组,存储所有需要监控的文件事件。文件事件结构体定义如下:

typedef struct aeFileEvent {    int mask;     aeFileProc *rfileProc;    aeFileProc *wfileProc;    void *clientData;} aeFileEvent;


其中mask存储监控的文件事件类型,如AE_READABLE可读事件和AE_WRITABLE可写事件;rfileProc为函数指针,指向读事件处理函数;wfileProc同样为函数指针,指向写事件处理函数;clientData指向对应的客户端对象。

调用aeApiAddEvent函数添加事件之前,首先需要调用aeCreateFileEvent函数创建对应的文件事件,并存储在aeEventLoop结构体的events字段,aeCreateFileEvent函数简单实现如下:

int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,        aeFileProc *proc, void *clientData){        aeFileEvent *fe = &eventLoop->events[fd];    if (aeApiAddEvent(eventLoop, fd, mask) == -1)        return AE_ERR;    fe->mask |= mask;    if (mask & AE_READABLE) fe->rfileProc = proc;    if (mask & AE_WRITABLE) fe->wfileProc = proc;    fe->clientData = clientData;        return AE_OK;}


Redis服务器启动时需要创建socket并监听,等待客户端连接;客户端与服务器建立socket连接之后,服务器会等待客户端的命令请求;服务器处理完成客户端的命令请求之后,命令回复会暂时缓存在client结构体的buf缓冲区,待客户端文件描述符的可写事件发生时,才会真正往客户端发送命令回复。这些都需要创建对应的文件事件:

aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE, acceptTcpHandler,NULL);aeCreateFileEvent(server.el,fd,AE_READABLE, readQueryFromClient, c);aeCreateFileEvent(server.el, c->fd, ae_flags, sendReplyToClient, c);


可以发现接收客户端连接的处理函数为acceptTcpHandler,此时还没有创建对应的客户端对象,因此函数aeCreateFileEvent第四个参数为NULL;接收客户端命令请求的处理函数为readQueryFromClient;向发送命令回复的处理函数为sendReplyToClient。

最后思考一个问题, aeApiPoll函数的第二个参数是时间结构体timeval,存储调用epoll_wait时传入的超时时间,那么这个时间怎么计算出来的呢?我们之前提过,Redis除了要处理各种文件事件外,还需要处理很多定时任务(时间事件),那么当Redis由于执行epoll_wait而阻塞时,恰巧定时任务到期而需要处理怎么办?要回答这个问题需要分析Redis事件循环的执行函数aeProcessEvents,函数在调用aeApiPoll之前会遍历Redis的时间事件链表,查找最早会发生的时间事件,以此作为aeApiPoll需要传入的超时时间。

int aeProcessEvents(aeEventLoop *eventLoop, int flags){shortest = aeSearchNearestTimer(eventLoop);long long ms =           shortest->when_sec - now_sec)*1000 +           shortest->when_ms - now_ms;       …………//阻塞等待文件事件发生       numevents = aeApiPoll(eventLoop, tvp);for (j = 0; j < numevents; j++) {    aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];    //处理文件事件,即根据类型执行rfileProc或wfileProc}      //处理时间事件processed += processTimeEvents(eventLoop);}


时间事件

上一节介绍了Redis文件事件,已经知道事件循环执行函数aeProcessEvents的主要逻辑:1)查找最早会发生的时间事件,计算超时时间;2)阻塞等待文件事件的产生;3)处理文件事件;4)处理时间事件。时间事件的执行函数为processTimeEvents。

Redis服务器内部有很多定时任务需要执行,比如说定时清除超时客户端连接,定时删除过期键等,定时任务被封装为时间事件aeTimeEvent对象,多个时间事件形成链表,存储在aeEventLoop结构体的timeEventHead字段,其指向链表首节点。时间事件aeTimeEvent定义如下:

typedef struct aeTimeEvent {    long long id;     long when_sec;     long when_ms;     aeTimeProc *timeProc;    aeEventFinalizerProc *finalizerProc;    void *clientData;    struct aeTimeEvent *next;} aeTimeEvent;


各字段含义如下:

  • id:时间事件唯一ID,通过字段eventLoop->timeEventNextId实现;

  • when_sec与when_ms:时间事件触发的秒数与毫秒数;

  • timeProc:函数指针,指向时间事件处理函数;

  • finalizerProc:函数指针,删除时间事件节点之前会调用此函数;

  • clientData:指向对应的客户端对象;

  • next:指向下一个时间事件节点。

时间事件执行函数processTimeEvents的处理逻辑比较简单,只是遍历时间事件链表,判断当前时间事件是否已经到期,如果到期则执行时间事件处理函数timeProc:

static int processTimeEvents(aeEventLoop *eventLoop) {te = eventLoop->timeEventHead;while(te) {    aeGetTime(&now_sec, &now_ms);    if (now_sec > te->when_sec || (now_sec == te->when_sec && now_ms >= te->when_ms)) {    //处理时间事件        retval = te->timeProc(eventLoop, id, te->clientData);            //重新设置时间事件到期时间        if (retval != AE_NOMORE) {                  aeAddMillisecondsToNow(retval, &te->when_sec,&te->when_ms);           }    }    te = te->next;}}


注意时间事件处理函数timeProc返回值retval,其表示此时间事件下次应该被触发的时间,单位毫秒,且是一个相对时间,即从当前时间算起,retval毫秒后此时间事件会被触发。

其实Redis只有一个时间事件,看到这里读者可能会有疑惑,服务器内部不是有很多定时任务吗,为什么只有一个时间事件呢?回答此问题之前我们需要先分析这个唯一的时间事件。Redis创建时间事件节点的函数为aeCreateTimeEvent,内部实现非常简单,只是创建时间事件并添加到时间事件链表。aeCreateTimeEvent函数定义如下:

long long aeCreateTimeEvent(aeEventLoop *eventLoop,                                  long long milliseconds,                                 aeTimeProc *proc, void *clientData,                                 aeEventFinalizerProc *finalizerProc);


其中输入参数eventLoop指向事件循环结构体;milliseconds表示此时间事件触发时间,单位毫秒,注意这是一个相对时间,即从当前时间算起,milliseconds毫秒后此时间事件会被触发;proc指向时间事件的处理函数;clientData指向对应的结构体对象;finalizerProc同样是函数指针,删除时间事件节点之前会调用此函数。

可以在代码目录全局搜索aeCreateTimeEvent,会发现确实只创建了一个时间事件

aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL);


该时间事件在1毫秒后会被触发,处理函数为serverCron,参数clientData与finalizerProc都为NULL。而函数serverCron实现了Redis服务器所有定时任务的周期执行。

int serverCron(struct aeEventLoop *eventLoop, long long id, void                    *clientData) {run_with_period(100) {    //100毫秒周期执行}run_with_period(5000) {    //5000毫秒周期执行}       //清除超时客户端链接clientsCron();       //处理数据库databasesCron();server.cronloops++;return 1000/server.hz;}


变量server.cronloops用于记录serverCron函数的执行次数,变量server.hz表示serverCron函数的执行频率,用户可配置,最小为1最大为500,默认为10。假设server.hz取默认值10,函数返回1000/server.hz会更新当前时间事件的触发时间为100毫秒后,即serverCron的执行周期为100毫秒。run_with_period宏定义实现了定时任务按照指定时间周期(ms)执行,其会被替换为一个if条件判断,条件为真才会执行定时任务,定义如下:

#define run_with_period(_ms_) if ((_ms_ <= 1000/server.hz)                             || !(server.cronloops%((_ms_)/(1000/server.hz))))


另外我们可以看到serverCron函数会无条件执行某些定时任务,比如清除超时客户端连接,以及处理数据库(清除数据库过期键等)。需要特别注意一点,serverCron函数的执行时间不能过长,否则会导致服务器不能及时响应客户端的命令请求。以过期键删除为例,分析下Redis是如何保证serverCron函数的执行时间。过期键删除由函数activeExpireCycle实现,由函数databasesCron调用,其函数是实现如下:

#define ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 25void activeExpireCycle(int type) {timelimit =                 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;timelimit_exit = 0;for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {    do {        //查找过期键并删除              if ((iteration & 0xf) == 0) {                   elapsed = ustime()-start;                if (elapsed > timelimit) {                       timelimit_exit = 1;                       break;                }              }    }while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4)      }}


函数activeExpireCycle最多遍历dbs_per_call个数据库,并记录每个数据库删除的过期键数目;当删除过期键数目大于门限时,认为此数据库过期键较多,需要再次处理。考虑到极端情况,当数据库键数目非常多且基本都过期时,do-while循环会一直执行下去。因此我们添加timelimit时间限制,每执行16次do-while循环,检测函数activeExpireCycle执行时间是否超过timelimit,如果超过则强制结束循环。通过变量timelimit限制了activeExpireCycle函数执行时间不会过长。

初看timelimit的计算方式可能会比较疑惑,其计算结果使得函数activeExpireCycle的总执行时间最多占CPU时间的25%,即每秒钟函数activeExpireCycle的总执行时间为1000000 * 25 / 100。仍然假设server.hz取默认值10,即每秒钟函数activeExpireCycle执行10次,那么每次函数activeExpireCycle的执行时间最多为1000000 * 25 / 100 / 10,单位微妙。

附录


Redis进程模型

第一节我们说过,Redis是单进程单线程的服务,实际上一个正在运行的Redis Server肯定不止一个进程/线程,只是单进程单线程处理命令请求而已。下面我们简要介绍下Redis"真正"的进程模型。

如图:

  1)主进程主线程用于处理网络请求,主要流程有:阻塞等待网络IO事件,解析并处理命令请求,处理定时任务;

  2)主进程子线程用于处理一些耗时任务,比如删除大集合时,如果配置了懒删除策略,主进程主线程会断开该集合到数据库连接(软删除),同时将该任务添加到任务链表中,主进程子线程从该任务链表获取任务,删除并释放每一个集合元素。注意,添加以及消费任务时,需要加锁。有兴趣的读者可以研究bio.c源码文件;

  3)Redis持久化方式有AOF和RDB两种;BGSAVE命令持久化RDB文件,BGREWRITEAOF进行AOF文件重写时,Redis都会fork子进程处理。

Redis耗时操作举例

前面说过,Redis大部分命令处理的时间复杂度都是O(1),少数命令才是O(logN)或者O(N),而有些命令更是非常耗时的。而,单进程单线程有一个缺点:处理某个命令请求而阻塞时,整个Redis服务无法处理其他请求。因此,在使用Redis时,我们必须很清楚每个命令请求的时间复杂度,对于耗时操作需要做相应处理才行,否则很可能会拖垮线上Redis服务。

  

1)keys,smembers等命令

keys命令用于查找所有符合指定模式(pattern)的key,smembers命令用于获取集合全集;这两个命令的时间复杂度都是O(N);显然,当数据库中元素或者集合中元素非常多时,命令将非常耗时。通常我们有两种方法可以处理:

"拆":慢请求阻塞了快请求,那么我们可以将慢请求与快请求分开处理;通常Redis都会配置主从模式,简单快请求可以由主服务器处理,复杂慢请求由从服务器处理;

还是"拆":请求复杂耗时,那么我们可以将请求拆分为多个子请求,复杂度转移到业务服务器;比如keys可以由scan分组完成,smembers可以由sscan分组完成。

2)save命令

save命令用于持久化RDB文件;当数据量大的时候,会造成主进程主线程长时间阻塞。线上Redis服务应该禁止该命令。

3)fork影响

在执行RDB持久化BGSAVE,或者AOF文件重写时,或者master首次向slave同步数据时,Redis会fork子进程处理,fork也是一个比较耗时的操作,此时会阻塞Redis处理其他请求。

4)持久化影响

在子进程执行持久化BGSAVE操作时,会存在大量写磁盘操作;而,AOF持久化可以配置always或者everysec,执行命令请求后同样需要写磁盘,该过程可能会导致主进程主线程的短暂阻塞。

总结


本文主要为读者分析Redis高性能内幕,重点从源码层次讲解了Redis事件模型,网络IO事件重在使用IO复用模型,时间事件重在限制最大执行CPU时间。最后简单介绍了Redis的进程模型(以后不要简简单单说Redis是单进程单线程了),以及使用命令时需要注意的一些耗时操作。

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多