Redis事件机制

Redis是一个基于I/O复用机制的事件驱动服务器,处理文件事件(如客户端请求)和时间事件(如定时任务)。aeEventLoop是其核心,负责事件管理。aeCreateFileEvent用于注册文件事件,而serverCron时间事件处理内部任务如持久化和过期数据清理。aeProcessEvents函数循环处理各种事件,通过aeApiPoll阻塞等待事件就绪。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、Redis事件机制概述

Redis 服务器是一个事件驱动程序,它主要处理如下两种事件:

  • 文件事件:利用 /0 复用机制,监听 Scket 等文件描述符上发生的事件。这类事件主要由客户端(或其他 Redis 服务器)发送网络请求触发。

  • 时间事件:定时触发的事件,负责完成 Redis 内部定时任务,如生成 RDB 文件、清除过期数据等。

Redis 利用I/O 复用机制实现网络通信。I/O 复用是一种高性能 I/O 模型,它可以利用单进程监听多个客户端连接,当某个连接状态发生变化(如可读、可写) 时,操作系统会发送事件(这些事件称为已就绪事件)通知进程处理该连接的数据。

Redis 事件机制的实现代码在 ae.h、ae.c 中,它实现了上层逻辑,负责控制进程,使其阻塞等待事件就绪或处理已就绪的事件,并为不同系统的I/O复用API定义了一致的 Redis API:

  • aeApiCreate:初始化 IO 复用机制的上下文环境。

  • aeApiAddEvent、aeApiDelEvent:添加或删除一个监听对象

  • aeApiPoll:阻塞进程,等待事件就绪或给定时间到期。

ae_select.c、ae_epoll.c、ae_evport.c、ae kqueue.c 是 Redis 针对不同系统 I/O 复用机制的适配代码,分别调用 select、epoll、evport、kqueue 实现了上述 Redis API,a.c 会在 Redis 服务启动时根据操作系统支持的 I/O 复用 API选择使用合适的适配代码。(将ae.h、a.c 称为AE 抽象层,将ae _select.c、ae_epoll.c 等称为IO复用层

//aeEventLoop 是 Redis 中的事件循环器,负责管理事件。

typedef struct aeEventLoop {
    intmaxfd;    //当前已注册的最大文件描述符。
    int setsize;    //该事件循环器允许监听的最大的文件描述符。
    long long timeEventNextId;    //下一个时间事件ID。
    time_t lastTime;    //上一次执行时间事件的时间,用于判断是否发生系统时钟偏移。
    aeFileEvent *events;    //已注册的文件事件表。
    aeFiredEvent *fired;    //已就绪的事件表。
    aeTimeEvent *timeEventHead;    //时间事件表的头节点指针。
    int stop;    //事件循环器是否停止。
    void *apidata;    //存放用于I/ 复用层的附加数据。
    aeBeforeSleepProc *beforesleep;    //进程阻塞前后调用的钩子函数
    aeBeforeSleepProc *aftersleep;    //进程阻塞前后调用的钩子函数
    int flags;
} aeEventLoop;

//aeFileEvent存储了一个文件描述符上已注册的文件事件:

typedef struct aeFileEvent {
    int mask;    // 监听的文件事件类型,有以下值:AB_NONE、AE_READABLE、AB_WRITABLE。
    aeFileProc *rfileProc;    //AE READABLE 事件处理函数
    aeFileProc*wfileProc;    //AEWRITABLE 事件处理函数
    void *clientData;    //附加数据
} aeFileEvent;

aeFileEvent中并没有记录文件描述符fd的属性。POSIX标准对文件描述符fd有以下约束:

  1. 值为0、1、2的文件描述符分别表示标准输入、标准输出和错误输出。

  1. 每次新打开的文件描述符,必须使用当前进程中最小可用的文件描述符。

Redis充分利用文件描述符的这些特点,定义了一个数组aeEventLoop.events来存储已注的文件事件。数组索引即文件描述符,数组元素即该文件描述符上注册的文件事件,如aeEventLoop.events[99]存放了值为99的文件描述符的文件事件aeFileEvent。当该文件描述符发生了aeFileEventmask 指定的事件时,Redis 将调用 aeFileEvent.rfileProc 或aeFileEvent.wfilePro函数处理事件。

I0复用层会将已就绪的事件转化为aeFiredEvent,存放在aeEventLoopfired 中,等待事件循环器处理。

typedef struct aeFiredEvent {
    int fd;    // 产生事件的文件描述符
    int mask;    //产生的事件类型。
) aeFiredEvent;

// aeTimeEvent中存储了一个时间事件的信息:

typedef struct aeTimeEvent {
    long long id;    //时间事件的ID。
    long when_sec;    //时间事件下一次执行的秒数(UNIX 时间戳)和剩余毫秒数
    long when_ms;    //时间事件下一次执行的秒数(UNIX 时间戳)和剩余毫秒数
    aeTimeProc*timeProc;    //时间事件处理函数。
    aeEventFinalizerProc *finalizerProc;    //时间事件终结函数。
    void *clientData;    //客户端传入的附加数据。
    struct aeTimeEvent *prev;    //指向前一个时间事件
    struct aeTimeEvent *next;    //指向后一个时间事件
    int refcount;
} aeTimeEvent;

二、Redis启动时创建的事件

Redis启动时,initServer 函数调用aeCreateEventLoop 函数创建一个事件循环器,存储在

server.el 属性中。

[1]、初始化aeEventLoop 属性。

[2]、aeApiCreate 由I/O 复用层实现,这时 Redis 已经根据运行系统选择了具体的IO复用层适配代码,该函数会调用到 ae_select.c、ae_epoll.c、ae_evport.c、ae_kqueue.c 其中的一个实现并初始化具体的IO 复用机制执行的上下文环境。

Redis 启动时,调用 aeCreateFileEvent 函数为 TCP Socket 等文件描述符注册了监听AE_WRITABLE类型的文件事件。所以,事件循环器会监听 TCP Socket,并使用指定函数处理AE_WRITABLE 事件。

/**
 * fd:需监听的文件描述符
 * mask:监听事件类型
 * proc:事件处理函数
 * clientData:附加数据
 */
int aeCreateFileEvent(aeEventLoop *eventLoop,int fd,int mask,
                        aeFileProc*proc,void *clientData)
{
    // [1]
    if (fd >= eventLoop->setsize) {
        errno = ERANGE;
        return AE_ERR;
    }
    aeFileEvent *fe = &eventLoop->events[fd];
    // [2]
    if (aeApiAddEvent(eventLoop,fd,mask)== -1)
    return AE ERR;
    // [3]
    fe->mask|= mask;
    if(mask & AE READABLE) fe->rfileProc = proc;
    if (mask & AE_WRITABLE) fe->wfileProc = proc;
    fe->clientData= clientData;
    if(fd>eventLoop->maxfd)
    eventLoop->maxfd = fd;
    return AE_OK;
}

serverCron 时间事件非常重要,负责完成 Redis 中的大部分内部任务,如定时持久化数据、清除过期数据、清除过期客户端等。另一部分内部任务则在 beforeSleep 函数中触发(事件循器每次阻塞前都调用的钩子函数)。Redis启动的最后,调用aeMain函数,启动事件循环器

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop =0;
    while (!eventLoop->stop){
        aeProcessEvents(eventLoop,AE_ALL_EVENTS|
        AE_CALL_BEFORE_SLEEP|AE_CALL_AFTER_SLEEP);
    }
}

只要不是 stop 状态,while 循环就一直执行下去,调用 aeProcessEvents 函数处理事件。Redis是一个事件驱动程序,正是该事件循环器驱动 Redis 运行并提供服务。

三、事件循环器的运行

Redis 运行期间,aeProcessEvents 函数被不断循环调用,处理 Redis 中的事件。

/**
 * flags: 指定aeProcessEvents 函数处理的事件类型和事件处理策略
 * AE_ALL_EVENTS:处理所有事件。
 * AE_FILE_EVENTS:处理文件事件。
 * AE_TIME_EVENTS:处理时间事件
 * AE_DONT_WAIT:是否阻塞进程
 * AE_CALL_AFTER_SLEEP:阻塞后是否调用eventLoopaftersleep 函数。
 * AE_CALL_BEFORE_SLEEP:阻塞前是否调用eventLoop.beforesleep 函数
 */
int aeProcessEvents(aeEventLoop *eventLoop,int flags)
{
    int processed = 0,numevents;
    if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE EVENTS)) return 0;
    // [1]
    if (eventLoop->maxfd != -1||
        ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))){
    int j;
    // [2]
    aeTimeEvent*shortest = NULL;
    struct timeval tv,*tvp;
    if (flags & AE_TIME_EVENTS && !(flags & AE DONT_WAIT))
        shortest= aeSearchNearestTimer(eventLoop);
    ...
    //[3]
    if (eventLoop->beforesleep != NULL && flags & AE_CALL_BEFORE_SLEEP)
        eventLoop->beforesleep(eventLoop);
    //[4]
    numevents = aeApiPoll(eventLoop,tvp);
    //[5]
    if(eventLoop->aftersleep != NULL && flags & AE CALL AFTER SLEEP)
        eventLoop->aftersleep(eventLoop);
    // more
    }
}

aeProcessEvents 函数执行的步骤流程

[1]、判断是否需要阻塞进程。

[2]、按以下规则计算进程最大阻塞时间。

  1. 查找最先执行的时间事件,如果能找到,则将该事件执行时间减去当前时间作为进程的最大阻塞时间。

  1. 找不到时间事件,检查 flags 参数中是否 AE_DONT_WAT 标志,若不存在,则进程将一直阻塞,直到有文件事件就绪:若存在,则进程不阻塞,将不断询问系统是否有已就绪文件事件。另外,如果 evenLoop.ags 中存在 AE_DONT_WAIT 标志,那么进程也不会阻塞。

[3]、进程阻塞前,执行钩子函数 beforeSleep。

[4]、aeApiPoll 函数由 /0 复用层实现,负责阻塞当前进程,直到有文件事件就绪或者给定时间到期。该函数返回已就绪文件事件的数量,并将这些事件存储在 aeEventLoop.fired 中。

[5]、进程阻塞后,执行钩子函数 aftersleep

由于 Redis 只有一个处理函数为serverCron 的时间事件,这里进程的最大阻塞时间为 serverCron时间事件的下次执行时间。

int aeProcessEvents(aeEventLoop *eventLoop,int flags)
{
    ...
        // [6]
        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;
            // [7]
            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];
            }
            // [8]
            if (fe->mask & mask & AE_WRITABLE) {
                if (!fired|| fe->wfileProc != fe->rfileProc){
                    fe->wf1leProc(eventLoop,fd,fe->clientData,mask);
                    fired++;
                }
            }

            // [9]
            if (invert){
                fe= &eventLoop->events[fd];
                if((fe->mask & mask & AE READABLE) &&
                    (!fired|| fe->wfileProc != fe->rfileProc))
                {
                    fe->rfileProc(eventLoop,fd,fe->clientData,mask);
                    fired++;
                }
            }
            processed++;
        }
    }
    // [10]
    if (flags & AETIME_EVENTS)
    processed += processTimeEvents(eventLoop);
    return processed;
}

[6]、aeApiPoll 函数返回已就绪的文件事件数量,这里处理所有已就绪的文件事件。

[7]、如果就绪的是AE_READABLE 事件,则调用rfileProc 函数处理。通常 Redis 先处理AE_READABLE 事件,再处理AE_WRITABLE 事件,这有助于服务器尽快处理请求并回复结果给客户端。如果aeFileEventmask 中设置了AE_BARRIER标志,则优先处理 AE_WRITABLE事件。AEWRITABLE是 Redis 中预留的功能,Redis 中并没有使用该标志

[8]、如果就绪的是AE_WRITABLE 事件,则调用wileProc 函数处理

[9]、如果aeFileEventmask 中设置了AE_BARRIER标志,则在这里处理AE_READABLE事件。

[10]、processTimeEvents 函数处理时间事件。

  1. 上一次执行事件的时间比当前时间还大,说明系统时间混乱了 (由于系统时钟偏移等原因)。这里将所有时间事件 when sec 设置为 0,这样会导致时间事件提前执行,由于提前执行事件的危害比延后执行的小,所以 Redis 执行了该操作。

  1. 遍历时间事件。

  1. aeTimeEventid 等于AE_DELETED_EVENT_ID,代表该时间事件已删除,将其从链

  1. 如果时间事件已到达执行时间,则执行aeTimeEvent.timeProc 函数。该函数执行时间表中移除。事件的逻辑并返回事件下次执行的间隔时间。事件下次执行间隔时间等于AE_NOMORE,代表该事件需删除,将aeTimeEventid 置为AE_DELETED_EVENT_ID,以便processTimeEvents函数下次执行时将其删除。由于 Redis 中只有 serverCron 时间事件,所以这里直接遍历所有时间事件也不会有性能

  1. 处理下一个时间事件。

由于 Redis 中只有 serverCron 时间事件,所以这里直接遍历所有时间事件也不会有性能问题。

另外,Redis 提供了 hz 配置项,代表 serverCron 时间事件的每秒执行次数,默认为 10,即

每隔100毫秒执行一次 serverCron 时间事件。

四、总结:

  • Redis 采用事件驱动机制,即通过一个死循环,不断处理服务中发生的事件。

  • Redis 事件机制可以处理文件事件时间事件。文件事件由系统 IO 复用机制产生,通常由客户端请求触发。时间事件定时触发,负责定时执行 Redis 内部任务。

Redis事件机制执行流程如图所示:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值