redis ae事件

事件

Redis服务器是一个事件驱动程序,服务器需要处理以下两类事件:
文件事件(file event)
        Redis服务器通过套接字与客户端进行连接,而文件事件就是服务器对套接字操作的抽象。
服务器与和客户端的通信会产生相应的文件事件,而服务器则通过监听并处理这些事件来完成一系列的网络通信操作。

时间事件(time event)
        Redis服务器中的一些操作(比如serverCron函数)需要在给定的时间点执行,而时间事件就是服务器对这类定时操作的抽象。

文件事件

        redis基于Reactor模式开发了自己的网络事件处理器;这个处理器被称为文件事件处理器(file event handler)。

        文件事件处理器使用IO多路复用程序来同时监听多个套接字,并且根据套接字目前执行的任务来为套接字关联不同的事件处理器。当被监听的套接字准备好执行连接应答、读取、写入、关闭等操作时,与操作相对应的文件事件就会产生,处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

        虽然文件事件处理器以单线程方式运行,但通过IO多路复用程序来监听多个套接字,实现了高性能的网络通信模型,保持了单线程设计的简单性。

/* File event structure */
typedef struct aeFileEvent {
    int         mask;           /* one of AE_(READABLE|WRITABLE|BARRIER) 文件事件类型 */
    aeFileProc  *rfileProc;     /* 可读事件回调函数 */
    aeFileProc  *wfileProc;     /* 可写事件回调函数 */
    void        *clientData;    /* 客户端传入的数据 */
} aeFileEvent;

/* A fired event */
typedef struct aeFiredEvent {
    int fd;     /* 就绪文件句柄 */
    int mask;   /* 就绪文件事件类型 */
} aeFiredEvent;

时间事件

/* Time event structure 双向链表*/
typedef struct aeTimeEvent {
    long long id;                           /* time event identifier. 时间事件id */
    long when_sec;                          /* seconds 执行节点秒 */
    long when_ms;                           /* milliseconds 执行节点毫秒 */   
    aeTimeProc *timeProc;                   /* 时间事件处理函数 */
    aeEventFinalizerProc *finalizerProc;    /* 时间事件终结函数 */
    void *clientData;                       /* 事件数据 */
    struct aeTimeEvent *prev;               /* 上一个时间事件 */
    struct aeTimeEvent *next;               /* 下一个时间事件 */
} aeTimeEvent;

事件循环

typedef struct aeEventLoop {
    int maxfd;                      /* 当前最大的已注册事件文件描述符  */
    int setsize;                    /* 文件描述符监听集合的大小 */
    long long timeEventNextId;      /* 下一个时间事件id */
    time_t lastTime;                /* 上一次执行事件的时间 */
    aeFileEvent *events;            /* 已经注册的文件事件,以fd为索引 */
    aeFiredEvent *fired;            /* 已经就绪的文件时间,以fd为索引 */
    aeTimeEvent *timeEventHead;     /* 时间事件的表头 */
    int stop;                       /* 事件处理开关 */
    void *apidata;                  /* used for polling API specific data 事件状态数据,根据不同的多路事件库来选择 */
    aeBeforeSleepProc *beforesleep; /* epoll_wait之前调用 */
    aeBeforeSleepProc *aftersleep;  /* epoll_wait之后调用 */
} aeEventLoop;

epoll 库,*apidata 存储的数据结构如下
typedef struct aeApiState {
    int epfd;                       /* epoll事件描述符 */
    struct epoll_event *events;     /* epoll事件表 */
} aeApiState;

aeMain 循环执行aeProcessEvents,aeProcessEvents返回处理事件数量

1. 查找最近的一个超时事件 aeSearchNearestTimer 
        遍历eventLoop->timeEventHead
        如果没有计时器,则返回 NULL
        查找O(N),因为时间事件未排序。可能的优化(目前 Redis 不需要):
        1)按顺序插入事件,使最近的只是头部。更好,但仍然插入或删除计时器是 O(N)。
        2)  使用跳跃列表skiplist将此操作作为 O(1) 并将插入作为 O(log(N))

        如果有定时器:获得当前时间,计算还需要多久触发事件tvp
        如果无定时器:tv = NULL
        如果AE_DONT_WAIT: tv = 0

2. 调用多路复用 API aeApiPoll,等待文件事件触发、或超时时返回
        aeApiPoll有4种实现:select、kqueue、epoll、evport
        aeApiState *state = eventLoop->apidata;

        epoll为例:

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

    timeout参数单位是毫秒,如果指定了大于0的超时时间,则在这段时间内即使如果没有文件事件触发,epoll_wait到了指定时间也会返回。 如果超时时间指定为-1,则epoll_wait将会一直阻塞等待,直到文件事件触发

epoll_wait(state->epfd, state->events, eventLoop->setsize, tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);

        epoll_wait获得就绪事件描述符,保存到eventLoop->fired就绪事件(fd、mask:AE_READABLE 、AE_WRITABLE),返回就绪事件个数

        select为例:

select(eventLoop->maxfd + 1, &state->_rfds, &state->_wfds, NULL, tvp);

        遍历小于eventLoop->maxfd的所有fd,判断是否在读事件集合、是否在写事件集合 FD_ISSET,保存到eventLoop->fired就绪事件(fd、mask:AE_READABLE 、AE_WRITABLE)
返回就绪事件个数

3. 处理就绪事件
        一般情况下,Redis会先处理读事件(AE_READABLE),再处理写事件(AE_WRITABLE)。 
先读后写可以让一个请求的处理和回复都是在同一次循环里面,使得请求可以尽快地回复
如果某个fd的mask包含了AE_BARRIER,那它的处理顺序会是先写后读

        遍历eventLoop->fired就绪事件(fd、mask)
        调用eventLoop->events[eventLoop->fired.fd]获得文件事件aeFileEvent
        根据就绪事件mask调用文件事件aeFileEvent读回调rfileProc或写回调wfileProc

4. 处理时间事件,processTimeEvents
        如果当前时间小于上次执行的时间eventLoop->lastTime,需要重置上次事件处理的时间
        设置上次时间事件处理的时间eventLoop->lastTime为当前时间 
        遍历时间事件链表eventLoop->timeEventHead
        如果时间事件已经被删除,要从链表中移除
        获取当前时间,找到到期时间事件(当前事件 > 时间事件到期时间),回调时间事件函数

创建文件事件

int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, aeFileProc *proc, void *clientData)
aeFileEvent *fe = &eventLoop->events[fd]

        将某个fd的某些事件及事件回调注册到eventloop中
        AE_READABLE 可读事件、AE_WRITABLE 可写事件、AE_BARRIER
        epoll为例:调用epoll_ctl   EPOLL_CTL_ADD、EPOLL_CTL_MOD

创建时间事件(定时器)

long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds, aeTimeProc *proc, void *clientData, aeEventFinalizerProc *finalizerProc)
aeTimeEvent *te
te->timeProc = proc;
te->finalizerProc = finalizerProc;

        超时调用回调timeProc,定时器被删除时调用回调finalizerProc
        Redis的定时器是一个普通的双向链表,链表也并不是有序的。每次最新的超时事件,直接插入链表的最头部。 当AE要遍历当前时刻的超时事件时,也是直接暴力的从头到尾遍历链表,看有没有超时的事件。

注册事件
        aeApiAddEvent,有4种实现:select、kqueue、epoll、evport
        向Redis事件表aeEventLoop的event注册一个事件
        epoll为例:调用了epoll_ctl

创建事件循环

server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR)

        1. maxclients代表用户配置的最大连接数,可在启动时由--maxclients指定,默认为10000。
        2. CONFIG_FDSET_INCR 大小为128。给Redis预留一些安全空间。

aeEventLoop *aeCreateEventLoop(int setsize)
eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize)

        setsize参数表示了eventloop可以监听的文件事件的个数,如果当前监听的fd个数超过了setsize,eventloop将不能继续注册。对于eventLoop->events数组来说,fd就是这个数组的下标

aeApiCreate
        有4种实现:select、kqueue(支持FreeBSD系统(如macOS) )、epoll(支持Linux系统)、evport(支持Solaris)
        epoll为例:epoll_create

        对于每种IO复用方式,只要实现以下8个接口就可以正常对接Redis了:
        int aeApiCreate(aeEventLoop *eventLoop);
        void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask);
        void aeApiResize(aeEventLoop *eventLoop, int setsize);
        void aeApiFree(aeEventLoop *eventLoop);
        int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask);
        void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask);
        int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp);
        char *aeApiName(void);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值