前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Redis源码分析2:Redis的事件处理模型

Redis源码分析2:Redis的事件处理模型

作者头像
黑光技术
修改于 2020-05-15 03:36:36
修改于 2020-05-15 03:36:36
1.4K00
代码可运行
举报
文章被收录于专栏:黑光技术黑光技术
运行总次数:0
代码可运行

前言

上一篇分析了一下redis的大致框架和启动过程,这篇我想分析一下redis的事件处理模型,当然也包含了网络事件的处理模型。redis除了其高效的nosql存储非常有名以外,另外一个比较被称赞的就是其服务效率。像这类服务的是怎么设计的,为什么这么高效。所以我想这里来分析分析。

事件驱动框架

redis的代码中有一个ae框架,是整个redis事件框架的基础,所以这里先来看看整个东东。按照ae.c文件中的注释说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. 翻译一下就是:这是一个简单的事件驱动编程库,原本是为Jim的一个事件循环程序,但是后来就变成了要开发一个简单可重用的库。

来看看ae中的核心数据结构,分析代码,我认为首先要把数据结构理清楚,说的简单点程序就是数据结构和算法逻辑结合。数据结构是骨,算法和逻辑是血肉经脉。所以一般分析代码都是要先看其核型数据结构。

核心数据结构

在ae这个事件驱动框架的核心数据结构就是下面这个,在上篇分析的代码中创建中就有分析到redis的main函数最后就是启动这个数据结构。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// ae.h
/* State of an event based program */
typedef struct aeEventLoop {
    int maxfd;   /* highest file descriptor currently registered
                注册的最高并发文件描述符*/
    int setsize; /* max number of file descriptors tracked
                最大可跟踪的文件描述符*/
    long long timeEventNextId;
    time_t lastTime;     /* Used to detect system clock skew
                        系统时间存储*/
    aeFileEvent *events; /* Registered events
    注册的事件 这是一个数组,看初始化代码是建立了10128大小的数组*/
    aeFiredEvent *fired; /* Fired events */
    aeTimeEvent *timeEventHead;
    int stop;
    void *apidata; /* This is used for polling API specific data*/
    aeBeforeSleepProc *beforesleep;
    aeBeforeSleepProc *aftersleep;
} aeEventLoop;

这个结构体中最重要的就是events这个结构了,里面是注册的所有io相关的事件,有可能是本地接口,有可能是外部请求的接口。

初始化过程

在redis的server.c文件的main函数最后有这样一句函数aeMain(server.el),其中server.el就是调用ae.c中的函数进行创建的,下面这段代码就在server.c的initServer(void)函数中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
    // 这里的参数就制定了上面events这个数组的大小10128.
    if (server.el == NULL) {       
        serverLog(LL_WARNING,      
            "Failed creating the event loop. Error message: '%s'",        
            strerror(errno));      
        exit(1);      
    }  
}

aeCreateEventLoop函数中就创建了aeEventLoop结构体,这里面申请了events了空间,并且做了初始化。在写c程序中初始化这个习惯一定要有,因为一般申请的内存单中的值是不确定的,为了避免使用上的意外,都要进行一次初始化操作。

事件循环调度

数据结构创建好,接下来就看看如何使用这个结构体,就是要看aeMain这个函数的实现过程。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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);
        // 这里进行循环处理事件
    }    
}     

aeProcessEvents这才是整个处理的核心,这里处理定时事件,文件读写事件,这个函数创的最后一个参数决定它可以处理那些类型的事件,也是机制和策略分离的一种设计思路。具体的处理类型在这个函数定义的头部注释中写的非常明白。

再来看看这个函数的处理过程:

  1. 首先根据flags参数判断,如果没有时间任务和文件输入输入类型的事件处理,则直接返回
  2. aeEventLooptimeEventHead中获取最近要处理的事件,处理函数是aeSearchNearestTimer。这里timeEventHead是一个链表,是使用顺序比较的方式获取最近要处理的事情,算法复杂度O(n),其实这里可以使用堆或者有序链表来优化的。
  3. 根据最近要处理的事件的时间计算当前时间事件中已经过期的事件的时间差(也有可能没有过期的事件,则后面就不需要处理了)
  4. 获取当前要处理的事件,这里就是根据上面的时间差来获取的numevents = aeApiPoll(eventLoop, tvp);
  5. 执行eventLoop->aftersleep(eventLoop);,这个函数是设置了才执行,和beforesleep是一对,是在server.c的main函数最后设置的,实际上就是进行了一个加锁解锁操作,为了避免在一次操作中对命令执行的线程安全。
  6. 接下来就是根据时间获取到的事件进行文件io的处理,主要处理读写
  7. 最后处理时间事件。

redis的客户端链接处理

这里提出2个问题:

  1. 客户端的连接请求那里处理
  2. 怎么和现在的ae处理框架联系起来

下面跟随这两个问题进行进一步的分析。 这里我们要回到server.cmain函数中的事件注册中来了,如下的注册过程中把acceptTcpHandler注册到事件中,在所有监听端口fd上都绑定了这个处理事件。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    /* Create an event handler for accepting new connections in TCP and Unix           
     * domain sockets. */          
    for (j = 0; j < server.ipfd_count; j++) {   
        if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,     
            acceptTcpHandler,NULL) == AE_ERR)   
            {         
   serverPanic(       
       "Unrecoverable error creating server.ipfd file event.");           
            }         
    }   

acceptTcpHandler设置断点我们再用客户端连接看看,方法还是和上次的方法一样的。可以看到就是从aeProcessEvents中去调用acceptTcpHandler的。实际上就是根据监听端口fd的事件来处理的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x000000010001bdc0 redis-server`acceptTcpHandler(el=0x000000010042f240, fd=7, privdata=0x0000000000000000, mask=1) at networking.c:728
    frame #1: 0x00000001000046bc redis-server`aeProcessEvents(eventLoop=0x000000010042f240, flags=11) at ae.c:443
    frame #2: 0x00000001000049db redis-server`aeMain(eventLoop=0x000000010042f240) at ae.c:501
    frame #3: 0x00000001000105be redis-server`main(argc=1, argv=0x00007ffeefbffb88) at server.c:4197
    frame #4: 0x00007fff5d3e108d libdyld.dylib`start + 1
    frame #5: 0x00007fff5d3e108d libdyld.dylib`start + 1

这里我顺便打印了一下acceptTcpHandler(el=0x000000010042f240...中的el这个结构体,看看目前的最大连接fd是7,setsize是10128等。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(lldb) p *el
(aeEventLoop) $1 = {
  maxfd = 7
  setsize = 10128
  timeEventNextId = 1
  lastTime = 1553153284
  events = 0x000000010025c000
  fired = 0x000000010100d600
  timeEventHead = 0x0000000100305cb0
  stop = 0
  apidata = 0x000000010042f290
  beforesleep = 0x0000000100009fc0 (redis-server`beforeSleep at server.c:1358)
  aftersleep = 0x000000010000a100 (redis-server`afterSleep at server.c:1415)
}
(lldb) 

好了回来再看acceptTcpHandler内部的实现,这个函数中主要有2个函数调用非常重要:

  1. anetTcpAccept,它是接受tcp的连接请求
  2. acceptCommonHandler,它是把请求生成客户端对象并放到aeEventLoop对象中。在这个函数中有个一createClient函数,在这里面创建了客户端,并且把连接fd和客户端处理事件放到aeEventLoop对象中,如下面的代码,
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    client *createClient(int fd) {     
    client *c = zmalloc(sizeof(client));        
         
    /* passing -1 as fd it is possible to create a non connected client.  
     * This is useful since all the commands needs to be executed         
     * in the context of a client. When commands are executed in other    
     * contexts (for instance a Lua script) we need a non connected client. */         
    if (fd != -1) {   
        anetNonBlock(NULL,fd);     
        anetEnableTcpNoDelay(NULL,fd);          
        if (server.tcpkeepalive)   
            anetKeepAlive(NULL,fd,server.tcpkeepalive);      
        if (aeCreateFileEvent(server.el,fd,AE_READABLE,      
            readQueryFromClient, c) == AE_ERR)
            // 这里首先是把readQueryFromClient绑定到事件处理对象`aeEventLoop`中
            // readQueryFromClient这里读取客户端请求,进行处理。
        {
            close(fd);
            zfree(c); 
            return NULL;           
        }
    }  
    ......       

因为所有的事件都是放到事件处理对象aeEventLoop中,而这个对象的调度执行都是aeProcessEvents函数,所以在我们进行堆栈查看的时候都只会看到这样的调用路径:main->aeMain->aeProcessEvents->xxx。如下面,我把readQueryFromClient打断点之后是这样的调用结果。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100019e91 redis-server`readQueryFromClient(el=0x000000010042ee00, fd=8, privdata=0x0000000100801000, mask=1) at networking.c:1508
    frame #1: 0x00000001000046bc redis-server`aeProcessEvents(eventLoop=0x000000010042ee00, flags=11) at ae.c:443
    frame #2: 0x00000001000049db redis-server`aeMain(eventLoop=0x000000010042ee00) at ae.c:501
    frame #3: 0x00000001000105be redis-server`main(argc=1, argv=0x00007ffeefbffb88) at server.c:4197
    frame #4: 0x00007fff5d3e108d libdyld.dylib`start + 1
    frame #5: 0x00007fff5d3e108d libdyld.dylib`start + 1

这里对client这个结构体没有展开分析,这个其实也是非常重要的,要理解redis的客户端和服务端的交互,就要多理解服务端中这个结构体的构成。但是这里不是今天分析的重点。

这里我们就看清楚了,所有的事件处理请求都会放到aeEventLoop这个核心对象中,而且所有的事件处理都是围绕这个数据结构的。从客户端的连接建立,到客户端请求事件的处理都是由这个结构体调度触发的。

总结

通过上面的分析,我们应该可以理解了redis的基本事件处理模型。aeEventLoop是整个redis的事件处理核心,aeProcessEvents不断的循环处理这个结构,所有的事件都是根据处理方式异步加入到aeEventLoop这个结构中,再由aeProcessEvents调度之心。主要的执行事件就是时间定时事件,IO读写事件。 最后附上一个分析脑图:

看完本文有收获?请分享给更多人

关注「黑光技术」加星标,关注大数据+微服务

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-03-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 黑光技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Redis 这破玩意为什么那么快?
shell 程序把我的程序加载到了内存,开始执行我的 main 方法,一切就从这里开始了。
macrozheng
2021/07/02
4170
Redis 这破玩意为什么那么快?
【Redis源码】Redis 启动过程分析
由于本人目前是华为FusionInsight HD 中Redis组件的Owner,所以要对Redis进行深入的了解,这对于C语言水平不咋地的我来讲还是有点难度的,于是我决定先从Redis的启动开始看,了解其基本原理。
zeekling
2022/08/26
6350
Redis 服务端程序实现原理
上篇我们简单介绍了 redis 客户端的一些基本概念,包括其 client 数据结构中对应的相关字段的含义,本篇我们结合这些,来分析分析 redis 服务端程序是如何运行的。一条命令请求的完成,客户端服务端都经历了什么?服务端程序中定时函数 serverCron 都有哪些逻辑?
Single
2020/03/21
1.2K0
Redis 源码简洁剖析 10 - aeEventLoop 及事件
Redis 事件驱动框架对应的数据结构,在 ae.h 中定义,记录了运行过程信息,有 2 个记录事件的变量:
Yano_nankai
2022/03/24
3900
Redis 源码简洁剖析 10 - aeEventLoop 及事件
Redis 6 中的多线程是如何实现的!?
Redis 是一个高性能服务端的典范。它通过多路复用 epoll 来管理海量的用户连接,只使用一个线程来通过事件循环来处理所有用户请求,就可以达到每秒数万 QPS 的处理能力。下图是单线程版本 Redis 工作的核心原理图(详情参见:单线程 Redis 如何做到每秒数万 QPS 的超高处理能力!)。
开发内功修炼
2022/08/31
1.8K0
Redis 6 中的多线程是如何实现的!?
Redis源码分析1:Redis启动分析
近期决定把redis的源码阅读分析一下,在官网下载了最新稳定版本5.0.3。整个代码包还是比较小的,下载之后整个包才9M,解压之后看src文件夹也才3.7M,也就是说redis的源码就这么点,其它占空间的主要是几个依赖组件:hiredis(redis的C客户端), lua, jemalloc(内存池), linenoise(配置文件解析),这些代码占了大概6.3M.
黑光技术
2019/03/10
2.9K0
Redis源码阅读(三)初始化与事件循环
Redis实现了一个简单的事件驱动程序库,即 ae.c 的代码,它屏蔽了系统底层在事件处理上的差异,并实现了事件循环机制。
星沉
2022/01/28
8720
深度解析单线程的 Redis 如何做到每秒数万 QPS 的超高处理能力!
今天开篇先给大家讲个飞哥自己的小故事。我在学校和刚毕业头一年主要从事的客户端开发,那时候对服务器端编程还不擅长。
开发内功修炼
2022/08/31
7710
深度解析单线程的 Redis 如何做到每秒数万 QPS 的超高处理能力!
Redis 源码简洁剖析 09 - Reactor 模型
是在 Redis 初始化时调用的,详见 Redis 源码简洁剖析 07 - main 函数启动。
Yano_nankai
2022/03/24
6240
Redis 源码简洁剖析 09 - Reactor 模型
Redis 事件机制详解
Redis 采用事件驱动机制来处理大量的网络IO。它并没有使用 libevent 或者 libev 这样的成熟开源方案,而是自己实现一个非常简洁的事件驱动库 ae_event。
程序员历小冰
2019/08/08
2K0
Redis 事件机制详解
【redis源码学习】事件机制
1、redis使用 IO 复用 实现网络通信。 2、在Linux环境下选用epoll模式。
看、未来
2021/12/29
3420
Redis原理篇之网络模型
此时,用户应用程序也同样需要占用这些资源,如果不加以限制,那么会和操作系统争抢资源,导致冲突。
大忽悠爱学习
2022/05/30
1.3K0
Redis原理篇之网络模型
Redis事件驱动
它要解决什么问题呢?传统的 thread per connection 用法中,线程在真正处理请求之前首先需要从 socket 中读取网络请求,而在读取完成之前,线程本身被阻塞,不能做任何事,这就导致线程资源被占用,而线程资源本身是很珍贵的,尤其是在处理高并发请求时。
tunsuy
2022/10/27
6160
通过Redis学习事件驱动设计
主要原因就是『简洁』。如果你用源码编译过Redis,你会发现十分轻快,一步到位。其他语言的开发者可能不会了解这种痛,作为C/C++程序员,如果你源码编译安装过Nginx/Grpc/Thrift/Boost等开源产品,你会发现有很多依赖,而依赖也有自己的依赖,十分苦恼。通常半天一天就耗进去了。由衷地羡慕 npm/maven/pip/composer/...这些包管理器。而Redis则给人惊喜,一行make了此残生。
果冻虾仁
2021/12/08
3350
通过Redis学习事件驱动设计
Redis为什么这么快?
| 作者 吴显坚,腾讯云数据库高级工程师,参与过360开源项目Pika的研发工作,现从事redis数据库研发工作。 ---- Redis服务器是一个事件驱动程序, 事件是Redis服务器的核心, 它处理两项重要的任务, 一个是IO事件(文件事件), 另外一个是时间事件. Redis服务器通过套接字与客户端进行连接, 而文件事件可以理解为服务器对套接字操作的抽象. 服务器与客户端的通信会产生相应的文件事件, 而服务器则通过监听并处理这些事件来完成一系列网络通信操作. 另外Redis内部有一些操作(从Redi
腾讯云数据库 TencentDB
2020/12/01
6750
【Redis源码】Redis事件监听
Redis服务器是典型的事件驱动程序,而事件又分为文件事件(socket的可读可写事件)与时间事件(定时任务)两大类。无论是文件事件还是时间事件都封装在结构体aeEventLoop中:
zeekling
2022/08/26
6380
Redis源码解析:一条Redis命令是如何执行的?
学习 Redis 源代码之前,我们需要对 Redis 代码的整体架构有一个了解,基于redis1.0源码,我们列出了主流程相关的如下源码文件。
腾讯技术工程官方号
2024/06/12
8441
Redis源码解析:一条Redis命令是如何执行的?
Redis启动分析
这一步表示Redis服务器基本数据结构和各种参数的初始化。在Redis源码中,Redis服务器是用一个叫做redisServer的struct来表达的,里面定义了Redis服务器赖以运行的各种参数,比如监听的端口号和文件描述符、当前连接的各个client端、Redis命令表(command table)配置、持久化相关的各种参数,以及事件循环结构。
tunsuy
2022/10/27
1.6K0
Redis(一):服务启动及基础请求处理流程源码解析
redis是用c语言的写的缓存服务器,有高性能和多种数据类型支持的特性,广受互联网公司喜爱。
烂猪皮
2021/01/13
1.2K0
Redis(一):服务启动及基础请求处理流程源码解析
从redis原理的角度认知Set命令的执行过程
本篇文章主要讲解 ,从redis原理的角度了解一个 set 命令从redis client发出到 redis server端接收到客户端请求的时候,到底经历了哪些过程?
付威
2024/01/02
2390
从redis原理的角度认知Set命令的执行过程
相关推荐
Redis 这破玩意为什么那么快?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档