您当前的位置:首页 > 电脑百科 > 数据库 > Redis

Redis事件驱动(aeEventLoop)原理分析

时间:2023-05-31 13:59:52  来源:  作者:编程技术之道
redis作为Server服务端在启动之后随时随刻监听着相关事件的发生。以linux为例,其处理过程与基于epoll的i/o多路复用伪代码框架基本相似,Redis源码中更多的是通过封装使其得到一个方便使用的库,库的底层包含了多种i/o多路复用实现方式。

关于Redis事件驱动

众所周知,Redis是高性能的、基于内存的、k-v数据库。其强大的功能背后,存在着2种不同类型的事件驱动,包括:

  1. 文件事件(File event)
  2. 时间事件(Time event)

文件事件是对相关的 fd 相关操作的封装,时间事件则是对定时任务相关操作的封装。Redis server通过文件事件来进行外部请求的处理与操作,通过时间事件来对系统内部产生的定时任务进行处理。(本文重点讲解文件事件相关的操作流程以及原理)

文中探讨的原理及源码基于Redis官方 v7.0 版本

Redis事件驱动的相关源码

在Redis源码中,涉及事件驱动相关的源码文件主要有以下几个(以ae作为文件名称前缀):

src
├── ae.c  
├── ae.h
├── ae_epoll.c
├── ae_evport.c
├── ae_kqueue.c
└── ae_select.c
  • ae.c 文件事件驱动/时间事件驱动的核心处理逻辑
  • ae.h文件事件驱动/时间事件驱动结构体、方法签名的定义
  • ae_epoll.c linux os 文件事件驱动涉及的i/o多路复用实现
  • ae_evport.c sun os 文件事件驱动涉及的i/o多路复用实现
  • ae_kqueue.c mac/BSD os 文件事件驱动涉及的os i/o多路复用实现
  • ae_select.c 其他 os 文件事件驱动涉及的i/o多路复用实现(或者说是通用型的,包括windows)

根据源码中注释(ae.c)可知 ae 的含义为 A simple event-driven。

/* A simple event-driven programming library. Originally I wrote this code
 * for the Jim's event-loop (Jim is a Tcl interpreter) but later translated
 * it in form of a library for easy reuse.
 */

一个简单的事件驱动编程库。最初我(作者:antirez)为Jim的事件循环(Jim是Tcl解释器)编写了这段代码,但后来将其转化为库形式以便于重用。

多种i/o多路复用方法的选择

在Redis源码中存在多种i/o多路复用实现方式,如何选择使用哪种i/o多路复用实现呢?源码编译时选择不同的实现方式,即:Redis源码编译成二进制文件的时候来选择对应的实现方式,在源码可以看到蛛丝马迹。

代码文件: ae.c

#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

从上面代码可知,在编译源码的预处理阶段,根据不同的编译条件(#ifdef/#else/#endif)来判断对应的宏是否定义(#define定义的常量)来加载实现逻辑。以epoll为例,若定义了 HAVE_EPOLL 宏,则加载 "ae_epoll.c" 文件。宏 "HAVE_EVPORT/HAVE_EPOLL/HAVE_KQUEUE" 分别对应不同的系统(或者说是对应的编译器)。

代码文件: config.h

#ifdef __sun
#include <sys/feature_tests.h>
#ifdef _DTRACE_VERSION
#define HAVE_EVPORT 1
#define HAVE_PSINFO 1
#endif
#endif

#ifdef __linux__
#define HAVE_EPOLL 1
#endif

#if (defined(__AppLE__) && defined(MAC_OS_X_VERSION_10_6)) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined (_.NETBSD__)
#define HAVE_KQUEUE 1
#endif

假设,当前是linux系统,那么 宏__linux__ 又是从哪里来的呢?Linux环境下主要用gcc编译,借助 gcc -dM -E - < /dev/null 命令从获得相应的变量中可以看到其定义。

root@ivansli ~# gcc -dM -E - < /dev/null | grep __linux
#define __linux 1
#define __linux__ 1

即:Redis源码会根据编译器来判断应该把源码编译成对应平台(或者是通用平台,性能会有所下降)运行的二进制可执行程序。

核心结构体 aeEventLoop

aeEventLoop 结构体如下所示:

/* State of an event based program 事件驱动程序的状态 */
typedefstruct aeEventLoop {
    int maxfd;   /* highest file descriptor currently registered. 当前已注册的最高文件描述符 */
    int setsize; /* max number of file descriptors tracked. [events/fired数组的大小] */
    longlong timeEventNextId; /* 时间事件的下一个ID */

    /* events/fired 都是数组 */
    /* events 数组,下标含义:为某个fd。fd=>aeFileEvent,即 文件描述符=>文件事件 */
    /* fired 为 io多路复用返回的数组,每一个值为就绪的fd */
    /* 通过 fired 中的 fd 去 events 查找对应的事件信息(事件信息包含conn) */
    aeFileEvent *events; /* Registered events 已注册事件,数组 */
    aeFiredEvent *fired; /* Fired events 触发的事件,数组 */

    aeTimeEvent *timeEventHead; /* 时间事件,链表 */
    int stop; /* 停止事件循环 */
    void *apidata; /* This is used for polling API specific data. 这用于获取特定的API数据,aeApiState *state 包含io多路复用fd等字段 */

    aeBeforeSleepProc *beforesleep;
    aeBeforeSleepProc *aftersleep;

    int flags;
} aeEventLoop;

aeEventLoop 结构体核心字段以及相关交互如下图所示:

  • setsize 文件事件数组大小,等于 server.maxclients+CONFIG_FDSET_INCR
  • events 文件事件数组,大小等于setsize
  • fired 文件事件就绪的fd数组,大小等于setsize
  • timeEventHead 时间事件数组,双向链表
  • apidata 这用于获取特定的API数据,指向 aeApiState结构体,不同的i/o多路复用实现包含不同的字段。
// ae_epoll.c
typedefstruct aeApiState {/* 在 aeApiCreate 中初始化,linux则在 ae_linux.c 文件 */
    int epfd; /* io多路复用fd */
    struct epoll_event *events;/* 就绪的事件数组  */
} aeApiState;

// ae_kqueue.c
typedefstruct aeApiState {
    int kqfd;
    struct kevent *events;

    /* Events mask for merge read and write event.
     * To reduce memory consumption, we use 2 bits to store the mask
     * of an event, so that 1 byte will store the mask of 4 events. */
    char *eventsMask; 
} aeApiState;

// ae_evport.c
typedefstruct aeApiState {
    int     portfd;                             /* event port */
    uint_t  npending;                           /* # of pending fds */
    int     pending_fds[MAX_EVENT_BATCHSZ];     /* pending fds */
    int     pending_masks[MAX_EVENT_BATCHSZ];   /* pending fds' masks */
} aeApiState;

// ae_select.c
typedefstruct aeApiState {
    fd_set rfds, wfds;
    /* We need to have a copy of the fd sets as it's not safe to reuse
     * FD sets after select(). */
    fd_set _rfds, _wfds;
} aeApiState;

 

图片

 

aeEventLoop 相关操作方法签名如下所示(文件ae.h):

aeEventLoop *aeCreateEventLoop(int setsize);
void aeDeleteEventLoop(aeEventLoop *eventLoop);
void aeStop(aeEventLoop *eventLoop);

int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, aeFileProc *proc, void *clientData);
void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask);
int aeGetFileEvents(aeEventLoop *eventLoop, int fd);
void *aeGetFileClientData(aeEventLoop *eventLoop, int fd);

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

int aeProcessEvents(aeEventLoop *eventLoop, int flags);
int aeWAIt(int fd, int mask, long long milliseconds);

void aeMain(aeEventLoop *eventLoop);

char *aeGetApiName(void);

void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep);
void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep);

int aeGetSetSize(aeEventLoop *eventLoop);
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize);
void aeSetDontWait(aeEventLoop *eventLoop, int noWait);

aeEventLoop事件处理核心方法

用途

调用i/o多路复用方法

epoll为例,调用方法

aeCreateEventLoop

创建并初始化事件循环

aeApiCreate

epoll_create()

默认水平触发

aeDeleteEventLoop

删除事件循环

aeApiFree

-

aeCreateFileEvent

创建文件事件

aeApiAddEvent

epoll_ctl()

EPOLL_CTL_ADD

EPOLL_CTL_MOD

aeDeleteFileEvent

删除文件事件

aeApiDelEvent

epoll_ctl()

EPOLL_CTL_MOD

EPOLL_CTL_DEL

aeProcessEvents

处理文件事件

aeApiPoll

epoll_wait()

aeGetApiName

获取i/o多路复用的实现名称

aeApiName

-

基于epoll的i/o多路复用

客户端与服务端的连接建立过程,如下图所示:

图片

TCP三次握手时,Linux内核会维护两个队列:

  1. 半连接队列,被称为SYN队列
  2. 全连接队列,被称为 accept队列

epoll相关处理方法与逻辑如下图所示:

 

图片

 

基于epoll的i/o多路复用伪代码框架:

int main(){
    lfd = socket(AF_INET,SOCK_STREAM,0); // 创建socket
    bind(lfd, ...); // 绑定IP地址与端口
    listen(lfd, ...); // 监听
 
    // 创建epoll对象
    efd = epoll_create(...);
    // 把 listen socket 的事件管理起来
    epoll_ctl(efd, EPOLL_CTL_ADD, lfd, ...);
 
    //事件循环
    for (;;) {
        size_t nready = epoll_wait(efd, ep, ...);
  
        for (int i = 0; i < nready; ++i){
            if(ep[i].data.fd == lfd){
                fd = accept(listenfd, ...); //lfd上发生事件表示都连接到达,accept接收它
                epoll_ctl(efd, EPOLL_CTL_ADD, fd, ...);
            }else{
                //其它socket发生的事件都是读写请求、或者关闭连接
                ...
            }
        }
    }
}
  •  

图片

 

从上可知,Redis作为Server服务端在启动之后随时随刻监听着相关事件的发生。以linux为例,其处理过程与基于epoll的i/o多路复用伪代码框架基本相似,Redis源码中更多的是通过封装使其得到一个方便使用的库,库的底层包含了多种i/o多路复用实现方式。

aeEventLoop 的执行过程

以epoll为例,简化版的Redis事件驱动交互过程。

 

图片

 

图中仅列出了核心方法,如有错误欢迎指正

Red括: 针对不同的 fd 注册 AE_READABLE/AE_WRITABLE 类型的回调方法,同时把 fd 添加到 epoll 中。当 fd 关心的事件触发之后,执行对应回调方法(主要针对 可读/可写/时间事件 3种类型的事件进行处理)。Redis 中 epoll 使用的触发方式为 LT 水平触发,意味着数据一次性没有处理完,下次 epoll_wait() 方法还会返回对应fd,直到处理完毕,对于客户端一次性发起批量处理多条命令的操作非常有益,减少对其他指令的阻塞时间。



Tags:   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
整治“暗箱操作” 义务教育阳光招生专项行动启动
中新网4月11日电 据教育部微信公众号消息,近日,教育部印发通知,对开展义务教育阳光招生专项行动作出部署。《通知》明确了专项行动的工作目标。通过专项行动,全面排查人民群众反...【详细内容】
2024-04-11  Search:   点击:(1)  评论:(0)  加入收藏
网易再牵暴雪的手,实际想搂微软的腰
是复婚,也是新婚不论是网易和暴雪复婚的声明,还是见证重归于好的合影中,都有一个第三方:微软。在微软的斡旋和推动下,网易在这次复合中拿到了更多的运营权和后续改编暴雪IP的空间...【详细内容】
2024-04-11  Search:   点击:(2)  评论:(0)  加入收藏
注意!密码、验证码都没说,钱是怎么被骗走的?
转自:科普中国“我密码和验证码都没有给他,为什么钱还是被骗子骗走了?”骗子是怎么做到的呢?真实案例今年1月,武汉市民张某在家接到一陌生电话,电话自称是某平台的客服,告诉张某购...【详细内容】
2024-04-11  Search:   点击:(2)  评论:(0)  加入收藏
将他人商标设为搜索关键词构成侵权吗?看看这起案件
案情介绍近日,深圳市智搜信息技术有限公司(下称智搜信息公司)起诉广州玛塔网络有限公司(下称玛塔网络公司)、王某某商标侵权及不正当竞争纠纷案一审判决正式生效,广东省广州市海珠...【详细内容】
2024-04-11  Search:   点击:(2)  评论:(0)  加入收藏
打破刚兑:投资者还能相信保险吗
4月初,西安市民陈柏如去银行与投资经理谈了两小时,掰开聊透了自己的理财预期和投资规划后,她选择购买了一份年金险分红型产品。相比曾经过高的期望值,如今她务实了很多:“我从基...【详细内容】
2024-04-11  Search:   点击:(2)  评论:(0)  加入收藏
拜登坐不住了?罕见对美联储货币政策表态
周三公布的连续3个月高于预期的通胀数据对于拜登来说是灾难性的,因为高通胀和高失业率往往会影响总统的连任机会。拜登罕见公开“打赌”预测美联储年底前将降息。美国3月CPI...【详细内容】
2024-04-11  Search:   点击:(3)  评论:(0)  加入收藏
黄金狂飙如何影响人民币
传导效应下,金融市场的波动受到密切关注。北京商报记者注意到,在历经3月中下旬的大幅走贬后,4月以来人民币汇率呈现企稳回升态势。4月9日,人民币中间价小幅回落,单日调降9基点,日...【详细内容】
2024-04-11  Search:   点击:(2)  评论:(0)  加入收藏
重新审视2008年全球金融危机应对,我们需要记住什么?
我们要时常回望,审视金融危机所造成的伤疤,即使是在金融市场正常运行时期,也要绷紧神经,避免金融市场在错误的道路上越走越远,陷入危机境地。重新审视2008年全球金融危机应对,我们...【详细内容】
2024-04-11  Search:   点击:(2)  评论:(0)  加入收藏
京东掷10亿现金扶持达人,能捧出下一个“董宇辉”吗?
界面新闻记者 | 查沁君京东(NASDAQ:JD)终于打响了内容生态的第一枪。4月10日,京东宣布投入10亿现金和10亿流量加码内容生态。这是自年初京东零售定下2024年“三大必赢之战”(内容...【详细内容】
2024-04-11  Search:   点击:(1)  评论:(0)  加入收藏
直播电商激战正酣:2023年市场规模或超4.9万亿,京东增速惊人!
直播电商领域正成为众多电商平台竞争的新焦点。根据一份研究报告,2023年中国直播电商成交额达到4.9万亿,增速达到35%,显示出该领域的巨大潜力。传统电商平台增速放缓,而直播电商...【详细内容】
2024-04-11  Search:   点击:(2)  评论:(0)  加入收藏
▌简易百科推荐
Redis 不再 “开源”,未来采用 SSPLv1 和 RSALv2 许可证
Redis 官方于21日宣布修改开源协议 &mdash;&mdash; 未来所有版本都将使用 “源代码可用” 的许可证 (source-available licenses)。具体来说,Redis 将不再遵循 BSD 3-Clause...【详细内容】
2024-03-27  dbaplus社群    Tags:Redis   点击:(12)  评论:(0)  加入收藏
Redis“叛逃”开源,得罪了几乎所有人
内存数据库供应商Redis近日在开源界砸下了一块“巨石”。Redis即将转向双许可模式,并实施更为严格的许可条款。官方对此次变更的公告直截了当:从Redis 7.4版本开始,Redis将在Re...【详细内容】
2024-03-25    51CTO  Tags:Redis   点击:(10)  评论:(0)  加入收藏
如何使用 Redis 实现消息队列
Redis不仅是一个强大的内存数据存储系统,它还可以用作一个高效的消息队列。消息队列是应用程序间或应用程序内部进行异步通信的一种方式,它允许数据生产者将消息放入队列中,然...【详细内容】
2024-03-22  后端Q  微信公众号  Tags:Redis   点击:(18)  评论:(0)  加入收藏
Redis不再 “开源”
Redis 官方今日宣布修改开源协议 &mdash;&mdash; 未来所有版本都将使用 “源代码可用” 的许可证 (source-available licenses)。具体来说,Redis 将不再遵循 BSD 3-Clause 开...【详细内容】
2024-03-21  OSC开源社区    Tags:Redis   点击:(9)  评论:(0)  加入收藏
在Redis中如何实现分布式锁的防死锁机制?
在Redis中实现分布式锁是一个常见的需求,可以通过使用Redlock算法来防止死锁。Redlock算法是一种基于多个独立Redis实例的分布式锁实现方案,它通过协调多个Redis实例之间的锁...【详细内容】
2024-02-20  编程技术汇    Tags:Redis   点击:(49)  评论:(0)  加入收藏
手动撸一个 Redis 分布式锁
大家好呀,我是楼仔。今天第一天开工,收拾心情,又要开始好好学习,好好工作了。对于使用 Java 的小伙伴,其实我们完全不用手动撸一个分布式锁,直接使用 Redisson 就行。但是因为这些...【详细内容】
2024-02-19  楼仔  微信公众号  Tags:Redis   点击:(40)  评论:(0)  加入收藏
工作中Redis有哪些好用的运维工具
工作中使用 Redis 时,如果大家公司没有专业运维,可能开发人员就会面临这些运维的工作,包括 Redis 的运行状态监控,数据迁移,主从集群、切片集群的部署和运维等等。本文我就从这三...【详细内容】
2024-02-06  waynaqua    Tags:Redis   点击:(56)  评论:(0)  加入收藏
批量执行Redis命令的四种方式!
前言在我们的印象中Redis命令好像都是一个个单条进行执行的,如果有人问你如何批量执行Redis命令,你能回答的上吗,或者说能答出几种方式呢?最容易想到的是Redis的一些批量命令,例...【详细内容】
2024-01-17  小许code  微信公众号  Tags:Redis命令   点击:(60)  评论:(0)  加入收藏
Redis 实现多规则限流的思考与实践
市面上很多介绍redis如何实现限流的,但是大部分都有一个缺点,就是只能实现单一的限流,比如1分钟访问1次或者60分钟访问10次这种,但是如果想一个接口两种规则都需要满足呢,我们的...【详细内容】
2024-01-03  架构精进之路  微信公众号  Tags:Redis   点击:(109)  评论:(0)  加入收藏
一站式Redis解决方案
Redis是一个高效的内存数据库,它支持包括String、List、Set、SortedSet和Hash等数据类型的存储,在Redis中通常根据数据的key查询其value值,Redis没有模糊条件查询,在面对一些需...【详细内容】
2024-01-01  大雷家吃饭    Tags:Redis   点击:(66)  评论:(0)  加入收藏
相关文章
    无相关信息
站内最新
站内热门
站内头条