[redis 源码走读] - 事件 - 文件事件

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

redis 服务底层采用了异步事件管理(aeEventLoop):管理时间事件和文件事件。对大量网络文件描述符(fd)事件管理,redis 建立在安装系统对应的事件驱动基础上(例如 Linux 的 epoll)。

  • 关于事件驱动,本章主要讲述 Linux 系统的 epoll 事件驱动。
  • 关于事件处理,本章主要讲述文件事件,时间事件可以参考帖子 《[redis 源码走读] 事件 - 定时器》。

1. 事件驱动

redis 根据安装系统选择对应的事件驱动。

 
    // ae.c
    /* Include the best multiplexing layer supported by this system.
     * The following should be ordered by performances, descending. */
    #ifdef HAVE_EVPORT
    #include "ae_evport.c"
    #else
        #ifdef HAVE_EPOLL
        #include "ae_epoll.c"
        #else
            #ifdef HAVE_KQUEUE
            #include "ae_kqueue.c"
            #else
            #include "ae_select.c"
            #endif
        #endif
    #endif

2. 异步事件管理

epoll 是异步事件驱动,上层逻辑操作和下层事件驱动要通过 fd 文件描述符串联起来。异步事件管理(aeEventLoop),对 epoll 做了一些封装,方便异步事件回调处理。

层次描述
ae.c关联异步业务事件和epoll接口,处理fd对应事件逻辑。
ae_epoll.c对epoll接口进行封装,方便上层操作。
epollLinux内核多路复用I/O模型,主要为了高效处理大批量文件描述符事件。

2.1. 数据结构

 
    // ae.c
    
    // 文件事件结构
    typedef struct aeFileEvent {
        int mask; // 事件类型组合(one of AE_(READABLE|WRITABLE|BARRIER))
        aeFileProc *rfileProc; // 读事件回调操作。
        aeFileProc *wfileProc; // 写事件回调操作。
        void *clientData;      // 业务传入的私有数据。方便回调使用。
    } aeFileEvent;
    
    // 就绪事件
    typedef struct aeFiredEvent {
        int fd;   // 文件描述符。
        int mask; // 事件类型组合。
    } aeFiredEvent;
    
    // 事件管理结构
    typedef struct aeEventLoop {
        int maxfd;   // 监控的最大文件描述符。
        int setsize; // 处理文件描述符个数。
        ...
        aeFileEvent *events; // 根据 fd 监听事件。
        aeFiredEvent *fired; // 从内核取出的就绪事件。
        ...
    } aeEventLoop;
结构描述
aeEventLoop文件事件和时间事件管理。
aeFileEvent文件事件结构,方便异步回调逻辑调用。aeEventLoop会创建一个aeFileEvent数组,数组下标是fd,fd对应aeFileEvent数据结构。
aeFiredEvent从内核获取的就绪事件。(例如Linux系统通过epoll_wait接口获取就绪事件,每个事件分别存储在aeFiredEvent数组中)

2.2. 创建事件管理对象

创建事件管理对象,对监控的文件数量设置了上限。

  • 文件监控上限配置。
 
    # redis.conf
    #
    # Set the max number of connected clients at the same time. By default
    # this limit is set to 10000 clients, however if the Redis server is not
    # able to configure the process file limit to allow for the specified limit
    # the max number of allowed clients is set to the current file limit
    # minus 32 (as Redis reserves a few file descriptors for internal uses).
    #
    # Once the limit is reached Redis will close all the new connections sending
    # an error 'max number of clients reached'.
    #
    # maxclients 10000
  • 创建事件管理对象。
 
    #define CONFIG_MIN_RESERVED_FDS 32
    #define CONFIG_FDSET_INCR (CONFIG_MIN_RESERVED_FDS+96)
    
    // server.c
    void initServer(void) {
        ...
        server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
        ...
    }
    
    int main(int argc, char **argv) {
        ...
        initServer();
        ...
    }

2.3. 事件处理流程

  • 循环处理事件
 
    // server.c
    int main(int argc, char **argv) {
        ...
        aeMain(server.el);
        ...
    }
    
    // ae.c
    // 循环处理事件
    void aeMain(aeEventLoop *eventLoop) {
        eventLoop->stop = 0;
        while (!eventLoop->stop) {
            if (eventLoop->beforesleep != NULL)
                eventLoop->beforesleep(eventLoop);
            aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
        }
    }
  • 添加事件,关联 fd 事件与异步回调相关信息。
 
    int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
            aeFileProc *proc, void *clientData) {
        if (fd >= eventLoop->setsize) {
            errno = ERANGE;
            return AE_ERR;
        }
        aeFileEvent *fe = &eventLoop->events[fd];
    
        // 调用底层 epoll_ctl 注册事件。
        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;
        if (fd > eventLoop->maxfd)
            eventLoop->maxfd = fd;
        return AE_OK;
    }
  • 删除事件,删除对应 fd 的事件。
 
    void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask) {
        if (fd >= eventLoop->setsize) return;
        aeFileEvent *fe = &eventLoop->events[fd];
        if (fe->mask == AE_NONE) return;
    
        // 如果删除的是写事件,要把写事件优先处理的事件也去掉,恢复优先处理读事件,再处理写事件逻辑。
        if (mask & AE_WRITABLE) mask |= AE_BARRIER;
    
        // 调用底层 epoll_ctl 修改删除事件。
        aeApiDelEvent(eventLoop, fd, mask);
        fe->mask = fe->mask & (~mask);
        if (fd == eventLoop->maxfd && fe->mask == AE_NONE) {
            /* Update the max fd */
            int j;
    
            for (j = eventLoop->maxfd-1; j >= 0; j--)
                if (eventLoop->events[j].mask != AE_NONE) break;
            eventLoop->maxfd = j;
        }
    }

2.4. 事件处理逻辑

文件事件处理逻辑,从内核取出就绪事件,根据事件的读写类型,分别进行回调处理相关业务逻辑。

 
    // ae.c
    int aeProcessEvents(aeEventLoop *eventLoop, int flags) {
        ...
        // 多路复用接口,从内核取出就绪事件。
        numevents = aeApiPoll(eventLoop, tvp);
        ...
        for (j = 0; j < numevents; j++) {
            // 根据就绪事件 fd,取出对应的异步文件事件进行逻辑处理。
            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. */
    
            /* AE_BARRIER 表示优先可写事件。正常情况,一般先读后写。
             * AE_BARRIER 使用场景,有兴趣的朋友,可以查找源码关键字:CONN_FLAG_WRITE_BARRIER
             * 理解这部分的逻辑。 */
            int invert = fe->mask & AE_BARRIER;
    
            if (!invert && fe->mask & mask & AE_READABLE) {
                fe->rfileProc(eventLoop,fd,fe->clientData,mask);
                fired++;
            }
    
            if (fe->mask & mask & AE_WRITABLE) {
                if (!fired || fe->wfileProc != fe->rfileProc) {
                    fe->wfileProc(eventLoop,fd,fe->clientData,mask);
                    fired++;
                }
            }
    
            if (invert && fe->mask & mask & AE_READABLE) {
                if (!fired || fe->wfileProc != fe->rfileProc) {
                    fe->rfileProc(eventLoop,fd,fe->clientData,mask);
                    fired++;
                }
            }
            ...
        }
        ...
    }

2.5. 获取待处理事件

通过 epoll_wait 从系统内核取出就绪文件事件进行处理。

 
    // ae_epoll.c
    static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
        aeApiState *state = eventLoop->apidata;
        int retval, numevents = 0;
    
        // 从内核取出就绪文件事件进行处理。
        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;
    
                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;
    
                // 就绪事件和fd保存到 fired。
                eventLoop->fired[j].fd = e->data.fd;
                eventLoop->fired[j].mask = mask;
            }
        }
        return numevents;
    }

3. 总结

  • redis 没有使用第三方库,实现跨平台的异步事件驱动。对文件事件驱动封装也比较简洁高效。
  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值