Redis学习之事件驱动模型

相关阅读

简介

Redis的原子性是通过Redis的单进程单线程的网络模型保证的;
那么单线程的Redis是如何实现高性能呢?

  1. 基于内存操作;
  2. 事件驱动模型;

server.c中的main函数是服务的开始,其中和事件驱动相关的核心代码如下:

int main(int argc, char **argv) {
    ...
    aeMain(server.el);
    ...
}

aeMain在ae.c中实现,代码如下:

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    // 死循环执行aeProcessEvents处理事件
    while (!eventLoop->stop) {
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
    }
}

aeProcessEvents在ae.c中实现,核心代码如下:

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
    int processed = 0, numevents;

    // 处理AE_TIME_EVENTS/AE_FILE_EVENTS
    if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
    ...
    
        // I/O多路复用,当有事件发生或者超时才返回
        // 调用操作系统函数实现,在Linux上为epoll
        numevents = aeApiPoll(eventLoop, tvp);

        /* After sleep callback. */
        if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)
            eventLoop->aftersleep(eventLoop);

        // 遍历发生的事件
        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;
            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++;
                }
            }

            processed++;
        }
    }
    // 处理时间事件
    if (flags & AE_TIME_EVENTS)
        processed += processTimeEvents(eventLoop);

    // 返回处理的文件、时间事件数量
    return processed;
}

aeProcessEvents是Redis统一处理所有事件的地方(事件分发器),从其处理逻辑看出,Redis没有fork任何线程处理事件,所以说Redis是一个基于事件驱动高的单线程应用;
Redis的事件驱动模型主要涉及:事件类型、I/O多路复用模块、事件分发器、事件处理器;而aeProcessEvents就是实现了事件分发器;

事件类型

Redis是一个事件驱动的内存数据库,服务器需要处理两种类型的事件,定义如下:

// 文件事件
#define AE_FILE_EVENTS 1
// 时间事件
#define AE_TIME_EVENTS 2

文件事件

Redis服务器通过socket实现和客户端(或其它Redis服务器)的交互,文件事件就是服务器对socket操作的抽象,Redis服务器通过监听并处理这些socket产生的文件事件,实现对客户端调用的响应;
其数据结构如下:

typedef struct aeFileEvent {
    // 监听事件类型掩码,可表示为事件类型
    // 值可以是 AE_READABLE 或 AE_WRITABLE ,
    // 或者 AE_READABLE | AE_WRITABLE
    int mask; /* one of AE_(READABLE|WRITABLE) */

    // 读事件处理器
    aeFileProc *rfileProc;

    // 写事件处理器
    aeFileProc *wfileProc;

    // 多路复用库的私有数据
    void *clientData;
} aeFileEvent;

// 文件事件处理器
typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);

时间事件

Redis有很多操作需要在给定的时间点进行处理,时间事件就是对这类定时任务的抽象;
其数据结构定义如下:

typedef struct aeTimeEvent {
    // 时间事件的唯一标识符
    long long id; /* time event identifier. */

    // 事件的到达时间
    long when_sec; /* seconds */
    long when_ms; /* milliseconds */

    // 事件处理函数
    aeTimeProc *timeProc;

    // 事件终结函数
    aeEventFinalizerProc *finalizerProc;

    // 多路复用库的私有数据
    void *clientData;

    // 指向上个时间事件结构,形成链表
    struct aeTimeEvent *prev;

    // 指向下个时间事件结构,形成链表
    struct aeTimeEvent *next;
} aeTimeEvent;

// 时间事件处理器
typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData);
// 时间事件终结处理器
typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData);

I/O多路复用模块

Redis基于Reactor模式开发了自己的事件处理器;
I/O多路复用模块会监听多个FD,当这些FD产生:accpetreadwriteclose的文件事件时,就会向事件分发器传送事件;
事件分发器收到事件后,会根据事件的类型(读/写)将事件分发给对应的事件处理器;

Redis的I/O多路复用模块,其实就是封装了底层不同操作系统提供的select(ae_select.c)、epoll(ae_epoll.c)、avport(ae_avport.c)、kqueue(ae.kqueue.c)等基础函数,向上层提供了一个统一的接口(ae.c),屏蔽了底层操作系统实现的差异性;
Redis封装的接口定义如下:

typedef struct aeApiState {
    // epoll实例描述符
    int epfd;
    // 绑定的事件
    struct epoll_event *events;
} aeApiState;

/**
 * 创建epoll 
 */
static int aeApiCreate(aeEventLoop *eventLoop)

/**
 * 调整绑定事件的大小
 */
static int aeApiResize(aeEventLoop *eventLoop, int setsize)

/**
 * 释放epoll
 */
static void aeApiFree(aeEventLoop *eventLoop)

/**
 * 绑定新事件
 */
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask)

/**
 * 删除事件
 */
static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask)

/**
 * 获取可执行事件
 */
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp)

事件分发器

ae.c中aeProcessEvents实现了事件分发器的功能,文件事件分发的核心代码如下:

// I/O多路复用,当有事件发生或者超时才返回
// 调用操作系统函数实现,在Linux上为epoll
numevents = aeApiPoll(eventLoop, tvp);

/* After sleep callback. */
if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)
    eventLoop->aftersleep(eventLoop);

// 遍历发生的事件
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;
    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++;
        }
    }

    processed++;
}

时间事件处理的核心代码如下:

static int processTimeEvents(aeEventLoop *eventLoop) {
    int processed = 0;
    aeTimeEvent *te;
    long long maxId;
    time_t now = time(NULL);

    // 处理系统时间被修改的情况
    if (now < eventLoop->lastTime) {
        te = eventLoop->timeEventHead;
        while(te) {
            te->when_sec = 0;
            te = te->next;
        }
    }
    // 更新上一次执行的时间
    eventLoop->lastTime = now;

    te = eventLoop->timeEventHead;
    maxId = eventLoop->timeEventNextId-1;
    // 遍历时间事件链表
    while(te) {
        long now_sec, now_ms;
        long long id;

        // 删除标记为删除状态的时间事件
        if (te->id == AE_DELETED_EVENT_ID) {
            aeTimeEvent *next = te->next;
            if (te->prev)
                te->prev->next = te->next;
            else
                eventLoop->timeEventHead = te->next;
            if (te->next)
                te->next->prev = te->prev;
            if (te->finalizerProc)
                te->finalizerProc(eventLoop, te->clientData);
            zfree(te);
            te = next;
            continue;
        }

        // 如果时间事件ID大于maxId,说明该时间事件是本次遍历过程中产生的,本次遍历不处理
        if (te->id > maxId) {
            te = te->next;
            continue;
        }
        // 获取当前时间
        aeGetTime(&now_sec, &now_ms);
        if (now_sec > te->when_sec ||
            (now_sec == te->when_sec && now_ms >= te->when_ms))
        {
            // 找到需要处理的时间事件
            int retval;

            id = te->id;
            // 执行对应的时间事件处理函数
            retval = te->timeProc(eventLoop, id, te->clientData);
            processed++;
            if (retval != AE_NOMORE) {
                // 刷新该时间事件下一次执行时间
                aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
            } else {
                // 不再需要,则标记为删除状态
                te->id = AE_DELETED_EVENT_ID;
            }
        }
        // 继续下一个时间事件,直到链表结束
        te = te->next;
    }
    return processed;
}

事件处理器

Redis有大量的事件处理器,通过aeCreateFileEvent绑定事件处理器到文件事件上,代码如下:

int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
        aeFileProc *proc, void *clientData)
{
    // 校验fd
    if (fd >= eventLoop->setsize) {
        errno = ERANGE;
        return AE_ERR;
    }

    // 准备填充该FD对应的处理
    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;
    if (fd > eventLoop->maxfd)
        eventLoop->maxfd = fd;
    return AE_OK;
}

一个简单命令的处理流程涉及如下处理器:

  • acceptTcpHandler:连接应答处理器,负责处理连接相关的事件,当有client连接到Redis时就会产生AE_READABLE事件,由其处理;绑定操作为:aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE, acceptTcpHandler, NULL)
  • readQueryFromClient:命令请求处理器,负责读取通过socket发送过来的命令;绑定操作为:aeCreateFileEvent(server.el,fd,AE_READABLE, readQueryFromClient, c)
  • sendReplyToClient:命令回复处理器,当Redis处理完命令,就会产生AE_WRITEABLE事件,将数据回复给client;绑定操作为:aeCreateFileEvent(server.el, c->fd, ae_flags, sendReplyToClient, c)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值