来聊聊redis文件事件驱动的设计

news2024/11/26 23:19:21

写在文章开头

近期团队安排变得比较紧急,关于redis系列的更新相对放缓一些,而我们今天要讨论的就是redis中关于事件模型的设计,我们都知道redis通过单线程实现高效的网络IO处理,本文会从源码的角度来讲解一下redis中文件事件驱动这一块的设计。

在这里插入图片描述

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

详解文件事件的设计与实现

单线程reactor模式的设计

Linux系统的思想是一切皆文件,所以在对于网络通信中服务端socket也是为其分配一个文件描述符(fd)并将其以文件的形式进行管理,并将其读写交给epoll进行轮询处理,由此构成一个单线程的reactor模型。

如下图所示,redis服务端的主线程会在每一次循环时通过epoll并将服务端socketfd传入非阻塞获取该文件就绪的事件,然后根据事件的类型有序的分发给对应的处理器进行处理。

在这里插入图片描述

对应的我们也给出redis文件事件循环框架的核心代码,可以看到其入参为server.maxclients+REDIS_EVENTLOOP_FDSET_INCR也就是10000+32+96,这就是事件循环框架每次可容纳的socket的文件描述符的大小:

	//创建事件循环框架
    server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR);

对此我们步入aeCreateEventLoop即可看到事件循环框架的核心实现,其本质就是完成事件循环框架的初始化,通过上一步传入的大小创建创建socket的数组空间,记录每一个接入的客户端socket的事件。

aeEventLoop *aeCreateEventLoop(int setsize) {
    aeEventLoop *eventLoop;
    int i;
	//空间分配
    if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err;
    eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);
    eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);
    if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;
    //记录事件数组的大小
    eventLoop->setsize = setsize;
    //......
	//创建事件循环框架
	if (aeApiCreate(eventLoop) == -1) goto err;
    //基于传入的setsize初始化每一个文件描述符的events空间为AE_NONE,表示当前socket没有就绪的事件
    for (i = 0; i < setsize; i++)
        eventLoop->events[i].mask = AE_NONE;
    return eventLoop;

//......

}

后续的事件与轮询处理都会在aeMain中进行不断的轮询并转交分发器进行处理,这里我们也给出对应的核心代码,可以看到该循环本质就是传入轮询框架eventLoop,轮询所的事件AE_ALL_EVENTS

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    //循环
    while (!eventLoop->stop) {
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}

我们步入其内部就可以看到对于服务端套接字(socket)的事件非阻塞轮询查看和事件分发处理逻辑:

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

	   	//......
		//非阻塞获取服务端socket的就绪事件	
        numevents = aeApiPoll(eventLoop, tvp);
        //基于返回值处理事件
        for (j = 0; j < numevents; j++) {
            aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
            //获取事件的mask值
            int mask = eventLoop->fired[j].mask;
            //获取该事件是那个客户端套接字
            int fd = eventLoop->fired[j].fd;
            int rfired = 0;
			//如果是读事件则调用rfileProc指针的函数进行命令处理
            if (fe->mask & mask & AE_READABLE) {
                rfired = 1;
                fe->rfileProc(eventLoop,fd,fe->clientData,mask);
            }
            //如果是写则将该事件交给写处理器wfileProc将数据写回客户端
            if (fe->mask & mask & AE_WRITABLE) {
                if (!rfired || fe->wfileProc != fe->rfileProc)
                    fe->wfileProc(eventLoop,fd,fe->clientData,mask);
            }
            processed++;
        }
    }
   //......
}

处理redis客户端连接请求

了解整体的模型设计之后,我们就来看看各个事件处理器的具体实现,当epoll轮询的有客户端进行连接时,就会将该事件分发给连接处理器,而连接处理器的核心逻辑为:

  1. 记录套接字信息。
  2. 为该客户端初始化一个redisClient结构体记录其信息。
  3. 将其套接字(socket)的事件注册到epoll中后续进行轮询处理。

在这里插入图片描述

这里我们给出redis的main方法关于redis服务端acceptTcpHandler处理的初始化逻辑,可以看到它会将acceptTcpHandler和服务端套接字的AE_READABLE进行绑定:

 for (j = 0; j < server.ipfd_count; j++) {
 		//为服务端套接字绑定acceptTcpHandler连接处理器
        if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
            acceptTcpHandler,NULL) == AE_ERR)
            {
              //......
            }
    }

这里我们也给出acceptTcpHandler处理器的实现逻辑,可以看到其内部的处理逻辑本质获取套接字的文件描述符,并基于这个文件描述符fd为其进行初始化生成redisClient并将事件注册到epollepoll进行轮询处理:

void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
	  //......

    while(max--) {
    	//获取套接字的文件描述符
        cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
        //......
        //为该套接字进行初始化,并为其生产客户端对象进行管理,并将客户端读写事件交给epoll
        acceptCommonHandler(cfd,0);
    }
}

步入acceptCommonHandler的逻辑我们即可看到创建redisClient的方法createClient,从核心逻辑可以看出如果redis客户端对象创建失败,它会直接关闭套接字。同理如果创建的redis客户端达到我们上文maxclients的上限也会将其释放并调用write方法提交写事件告知客户端已达上限:

static void acceptCommonHandler(int fd, int flags) {
    redisClient *c;
    //为客户端套接字创建redisClient对象并注册事件到epoll中
    if ((c = createClient(fd)) == NULL) {
        redisLog(REDIS_WARNING,
            "Error registering fd event for the new client: %s (fd=%d)",
            strerror(errno),fd);
        close(fd); /* May be already closed, just ignore errors */
        return;
    }
    //.......
    //客户端已达上线输出错误并释放客户端
    if (listLength(server.clients) > server.maxclients) {
        char *err = "-ERR max number of clients reached\r\n";

       //输出错误
        if (write(c->fd,err,strlen(err)) == -1) {
            /* Nothing to do, Just to avoid the warning... */
        }
        //自增失败连接数
        server.stat_rejected_conn++;
        //释放客户端对象
        freeClient(c);
        return;
    }
    server.stat_numconnections++;
    c->flags |= flags;
}

最后再来说说创建客户端的处理逻辑,如我们上文所说就是将客户端读事件注册到epoll中,让epoll进行轮询并分发处理,完成该操作后再初始化客户端的各种基础信息:

redisClient *createClient(int fd) {
    redisClient *c = zmalloc(sizeof(redisClient));

  		//......
  		//将当前客户端套接字读请求即AE_READABLE事件注册到epoll中,并绑定命令处理器readQueryFromClient
        if (aeCreateFileEvent(server.el,fd,AE_READABLE,
            readQueryFromClient, c) == AE_ERR)
        {
            close(fd);
            zfree(c);
            return NULL;
        }
    }

	//初始化客户端对象各种参数
    selectDb(c,0);
    c->id = server.next_client_id++;
    c->fd = fd;
   	//.......
    return c;
}

客户端命令读取与回复

经过上述的处理后,redis客户但就和服务端建立连接,每当客户端发起各种指令操作时,redis的epoll就会轮询到这个客户端套接字的读事件,并将其交给命令处理器处理,完成后将处理结果交给命令回复处理器提交写事件,下次epoll轮询到这个事件就会将结果交给redis客户端

在这里插入图片描述

我们再次给出事件轮询的代码片段,该片段位于ae.c文件下,它会调用aeApiPoll轮询所有套接字对应的fd是否有事件,以客户端命令请求为例,该方法就会返回redis客户端套接字的事件对象,然后计算得到是AE_READABLE事件,就将其交给readQueryFromClient处理器处理,完成后会将处理结果即命令回复交给写处理器sendReplyToClient中,下次aeApiPoll就会轮询到该事件并将其回复给redis客户端:

	//轮询所有套接字查看是否有就绪事件
	numevents = aeApiPoll(eventLoop, tvp);
        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 rfired = 0;

	 		//如果是AE_READABLE则说明客户端发起命令,调用rfileProc走到readQueryFromClient方法,完成后生成写事件下次循环时就会走到下方sendReplyToClient的处理逻辑
            if (fe->mask & mask & AE_READABLE) {
                rfired = 1;
                fe->rfileProc(eventLoop,fd,fe->clientData,mask);
            }
            //读请求的处理结果就会走到AE_WRITABLE的处理器sendReplyToClient将结果写回客户端
            if (fe->mask & mask & AE_WRITABLE) {
                if (!rfired || fe->wfileProc != fe->rfileProc)
                    fe->wfileProc(eventLoop,fd,fe->clientData,mask);
            }
            processed++;
        }

小结

自此我们将redis单线程的reactor模型以及对应的文件驱动设计都分析完毕,希望对你有所帮助。

我是 sharkchiliCSDN Java 领域博客专家开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

参考

《redis设计与实现》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1845404.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

国内怎样使用GPT4 turbo

GPT是当前最为熟知的大模型&#xff0c;它优越的性能一直遥遥领先于其它一众厂商&#xff0c;然而如此优秀的AI在中国境内却是无法正常使用的。本文将告诉你4种使用gpt4的方法&#xff0c;让你突破限制顺利使用。 官方售价是20美元/月&#xff0c;40次提问/3小时&#xff0c;需…

CSDN自定义模块全攻略,DIY系统原有样式打造专属个性化主页!

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 &#x1f4af;如何通过HTMLCSS自定义模板diy出自己的个性化csdn主页&#x…

Developer Day北京站倒计时三天|请查收您的参会指南!

MongoDB Developer Day 6/22北京站 倒计时3天&#xff0c;期待您的出席&#xff01; 请收藏您的行前温馨贴士⬇️ MongoDB Developer Day 专为开发者打造的动手实操工作坊和模型设计优化专场&#xff01; 本活动针对开发者和产品负责人设计&#xff0c;旨在学习NoSQL数据建…

同三维T80004EH-N HDMI高清NDI编码器

1路HDMI 1路3.5音频输入,支持NDI 产品简介&#xff1a; 同三维T80004EH-N 高清HDMI编码器是专业的NDI高清音视频编码产品&#xff0c;该产品支持1路高清HDMI音视频采集功能&#xff0c;1路3.5MM独立音频接口采集功能。编码输出双码流H.265/H.264格式&#xff0c;音频MP3/AAC格…

初阶 《数组》 3. 数组越界

3. 数组越界 数组的下标是有范围限制的。 数组的下规定是从0开始的&#xff0c;如果数组有n个元素&#xff0c;最后一个元素的下标就是n-1。 所以数组的下标如果小于0&#xff0c;或者大于n-1&#xff0c;就是数组越界访问了&#xff0c;超出了数组合法空间的访问。 C语言本身…

VUE面试题汇总(九)

之间联系&#xff08;Model 和 ViewModel 的双向数据绑定&#xff09; 解析&#xff1a; MVVM 是 Model-View-ViewModel 的缩写。MVVM 是一种设计思想。Model 层代表数据模型&#xff0c;也可以在 Model 中定义数据修改和操作的业务逻辑&#xff1b;View 代表 UI 组件&#xf…

种子流媒体服务器TorrServer

什么是 TorrServer &#xff1f; TorrServer 是一个允许用户在线查看种子而无需预先下载文件的程序。 TorrServer 的核心功能包括缓存种子以及通过 HTTP 协议进行后续数据传输&#xff0c;允许根据系统参数和用户的互联网连接速度调整缓存大小。 软件特点 缓存流媒体本地和远程…

金蝶云星空与MES系统深度集成对接案例全公开

项目背景 深圳市某自动化设备有限公司&#xff0c;自2006年成立以来&#xff0c;一直专注于高端精密自动化设备的研发、生产与销售。作为一家高科技企业&#xff0c;公司依托深圳这一经济特区的地理优势&#xff0c;构建了覆盖全国的服务网络&#xff0c;并拥有两个先进的生产…

爬虫超详细介绍

爬虫&#xff08;Spider&#xff09;是一种自动化程序&#xff0c;用于在互联网上获取信息。 其工作原理主要可以分为以下几个步骤&#xff1a; 发起请求&#xff1a; 爬虫首先需要向目标网站发起HTTP请求&#xff0c;以获取网页的内容。这个请求可以包含一些额外的信息&…

【C++】类和对象(四)拷贝构造、赋值运算符重载

文章目录 四、拷贝构造函数干嘛的&#xff1f;写拷贝构造函数的注意事项正确写法 不显示定义拷贝构造函数的情况浅拷贝:one:示例&#xff1a;内置类型:two:示例&#xff1a;自定义类型一个提问 深拷贝 五、赋值运算符重载运算符重载函数原型注意调用时的两种书写方式完整实现代…

skywalking segment索引占用elasticsearch大量磁盘空间

现象&#xff1a; skywalking segment索引占用elasticsearch大量磁盘空间 原因 recordDataTTL 是SkyWalking的一个配置项&#xff0c;用于设置记录数据的存活时间&#xff08;TTL, Time To Live&#xff09;。SkyWalking是一个开源的应用性能监控系统&#xff0c;用于监控分…

tplink安防监控raw文件转码合成mp4的方法

Tplink(深圳普联)专业的网络设备生产商&#xff0c;属于安防监控市场的后来者。Tplink的安防产品恢复了很多&#xff0c;其嵌入式文件系统也一直迭代更新。今天要说的案例比较特殊&#xff0c;其不仅仅要求恢复&#xff0c;还要求能解析出音频并且要求画面和声音实现“同步”。…

入门Rabbitmq

1、什么是消息队列 消息队列&#xff1a;应用之间传递消息的方式&#xff0c;允许应用程序异步发送和接收消息&#xff0c;不需要连接对方 消息&#xff1a;文本字符串&#xff0c;对象.... 队列&#xff1a;存储数据。先进先出 2、应用场景 ①库存系统挂掉之后 MQ会等待&…

AI PPT生成器,一键在线智能生成PPT工具

PPT作为商业沟通和教育培训中的重要工具&#xff0c;PPT制作对于我们来说并不陌生。但是传统的PPT制作不仅耗时&#xff0c;而且想要做出精美的PPT&#xff0c;需要具备一定的设计技能。下面小编就来和大家分享几款AI PPT工具&#xff0c;只要输入主题&#xff0c;内容就可以在…

本地快速部署大语言模型开发平台Dify并实现远程访问保姆级教程

文章目录 前言1. Docker部署Dify2. 本地访问Dify3. Ubuntu安装Cpolar4. 配置公网地址5. 远程访问6. 固定Cpolar公网地址7. 固定地址访问 前言 本文主要介绍如何在Linux Ubuntu系统使用Docker快速部署大语言模型应用开发平台Dify,并结合cpolar内网穿透工具实现公网环境远程访问…

C# WPF入门学习主线篇(二十八)—— 使用集合(ObservableCollection)

C# WPF入门学习主线篇&#xff08;二十八&#xff09;—— 使用集合&#xff08;ObservableCollection&#xff09; 在WPF中&#xff0c;数据绑定是构建动态和响应式用户界面的关键。ObservableCollection是一个特别有用的集合类型&#xff0c;它不仅支持数据绑定&#xff0c;还…

在Tomcat中部署war包

1、准备war包 确保已经有一个有效的war包&#xff0c;该war包包含了web应用程序的所有内容&#xff1b; 2、停止tomcat服务器 在部署之前&#xff0c;确保tomcat服务器已经停止&#xff0c;进入tomcat的配置目录执行命令&#xff1a;[路径]/tomcat/conf&#xff1b; 在Linux…

windows系统实现应用程序开机即运行(不登录系统也行)

由于近期需要设置一个Java程序开机自启动&#xff0c;因此试了一下方法&#xff0c;总结了两点&#xff0c;一个是需要用户登录系统之后再启动&#xff0c;一种是不需要登录&#xff0c;只要开机就会启动。 先看准备工作&#xff0c;写一个启动脚本&#xff1a; echo on E: cd…

[STM32]万年历

[STM32]万年历 需要资料的请在文章末尾获取~ ​​ 01描述 使用原件&#xff1a;stm32f103c8t6最小系统板x1&#xff0c;0.96寸OLED显示屏四角x1&#xff0c;4x4矩阵按键x1; 键位对应图&#xff1a; 1&#xff0c; 2&#xff0c; 3&#xff0c; 4------------- 切换页面 设置…

Dynamics 365 on-premise 隐藏高级查找导出按钮

提示 着急可以直接看结果代码部分 背景 Dynamics 365 on-premise中有个高级查找的功能,查询的结果支持导出,如下图 业务反馈这个有数据安全风险,要修改显示规则。 一开始想着能用RibbonWorkbench改,就很爽快得答应了业务。结果用RibbonWorkbench改不了。 反复尝试 既…