来聊聊Redis所实现的Reactor模型

写在文章开头

我们都知道解决C10k问题的最好方案就是通过在IO多路复用的基础上通过reactor模型实现高性能的网络并发程序,借助这个设计,redis的主线程也是基于IO多路复用reactor模型的思路实现了一个高性能的单线程内存数据,本文将带领读者从源码的角度来查看redis关于reactor模型的设计。

在这里插入图片描述

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

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

在这里插入图片描述

详解Redis中的Reactor模型

Reactor模型扫盲

在此之前我们先来了解一下Reactor模型,在高性能网络并发程序的设计中,Reactor模型通过reactor接收用户连接事件、读事件、写事件这些网络事件,得到连接事件之后通过acceptor为其分配handler,后续的这些客户端的读写事件都会交由handler完成读写事件的处理,由此实现尽可能少的线程处理尽可能多的连接。

在这里插入图片描述

详解reactor的实现

上文我们简单的对Reactor模型进行了简单的扫盲,接下来我们将从redis的源码来了解redis对于Reactor模型的实现,我们都知道Reactor模型是通过reactor接收连接、读、写三种事件的,这一点我们可以直接在main方法看到aeMain的调用,该方法内部本质就是通过epoll模型进行非阻塞获取就的网络事件:

int main(int argc, char **argv) {
	   //前置初始化步骤
	   //......
    //事件循环轮询前置操作
    aeSetBeforeSleepProc(server.el,beforeSleep);
    //执行事件驱动框架,循环处理各种触发的事件
    aeMain(server.el);
    //事件循环后置操作
    aeDeleteEventLoop(server.el);
    return 0;
}

我们步入aeMain方法,可以看到只要eventLoop没有停止就会无限循环调用aeProcessEvents获取并处理就绪的事件:

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
       //......
       //轮询并处理就绪的事件
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}

步入aeProcessEvents方法,我们就可以看到redis通过对于epoll的封装函数aeApiPoll非阻塞获取就绪的IO事件,注意笔者所强调的非阻塞获取,这也就是为什么redis仅仅用一个主线程即可实现Reactor模型的原因所在。

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
	 //......
	 //非阻塞获取就绪事件
        numevents = aeApiPoll(eventLoop, tvp);
        for (j = 0; j < numevents; j++) {
           //......
           //处理事件
            processed++;
        }
    }
    /* Check time events */
    if (flags & AE_TIME_EVENTS)
        processed += processTimeEvents(eventLoop);

    return processed; /* return the number of processed file/time events */
}

对此我们再次步入aeApiPoll实现可以看到redis对于epoll的调用epoll_wait,得到事件数retval 之后,直接基于retval遍历eventLoopevents这里面存储的就是所有收到的事件aeFiredEventredis会根据其事件类型累加对应的事件mask值,例如如果是得到的事件类型是EPOLLIN则mask值会加上AE_READABLE(1),若是标准输出事件EPOLLOUT则累加AE_WRITABLE即2:

在这里插入图片描述

对应的我们给出这段基于epoll实现reacor的实现,可以看到其reactor通过事件轮询获取对应的事件类型再将其封装为aeFileEvent存到事件数组eventLoop->fired中:

static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
    aeApiState *state = eventLoop->apidata;
    int retval, numevents = 0;

    retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
            tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
    if (retval > 0) {
        int j;

        numevents = retval;
        //遍历事件
        for (j = 0; j < numevents; j++) {
            int mask = 0;
            struct epoll_event *e = state->events+j;
			//根据事件类型累加读写的mask值
            if (e->events & EPOLLIN) mask |= AE_READABLE;
            if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
            if (e->events & EPOLLERR) mask |= AE_WRITABLE;
            if (e->events & EPOLLHUP) mask |= AE_WRITABLE;
            //将该事件存到fired数组中
            eventLoop->fired[j].fd = e->data.fd;
            eventLoop->fired[j].mask = mask;
        }
    }
    //返回事件数
    return numevents;
}

详解事件的封装

上文我们提到一个aeFileEvent 事件的概念,该个事件结构如下图所示,它通过mask标记当前IO事件类型,在epoll轮询到事件时,它并通过rfileProc读事件处理指针和wfileProc写文件处理保存针对网络IO事件的处理函数,注意这个处理函数我们完全可以直接理解为reactor模型中的handler,最后用clientData记录客户端私有数据的指针:

typedef struct aeFileEvent {
	//记录事件读写类型,如果是读事件READABLE则mask+1,若是写事件WRITABLE则加2
    int mask; /* one of AE_(READABLE|WRITABLE) */
    //读事件处理器指针指向读事件处理函数handler
    aeFileProc *rfileProc;
    //写事件处理器指针指向读事件处理函数handler
    aeFileProc *wfileProc;
    //记录客户端私有数据指针
    void *clientData;
} aeFileEvent;

这里我们以服务端socket初始化阶段为例展示一下aeFileEvent对应处理器的初始化过程,我们在redis服务端启动的main函数可以看到initServer的调用,该方法会为当前服务端socket套接字的文件描述符绑定读事件的处理器acceptTcpHandler

在这里插入图片描述

对应的我们给出这一段事件绑定handler的逻辑的核心代码段:


int main(int argc, char **argv) {
  	//......
    //server初始化,其内部会完成数据结构、键值对数据库初始化、网络框架初始化工作
    initServer();
}
void initServer(void) {
  	//......

   
    for (j = 0; j < server.ipfd_count; j++) {
     //为每一个监听服务端socket的读事件绑定对应的TCP处理器acceptTcpHandler,并将其注册到eventLoop中
        if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
            acceptTcpHandler,NULL) == AE_ERR)
            {
                redisPanic(
                    "Unrecoverable error creating server.ipfd file event.");
            }
    }
   //......
}

轮询并分发到handler

上述步骤完成redis server的事件注册之后,main方法的aeMain函数就会通过epoll轮询eventLoop中是否有就绪的IO事件,如果redis serverfd的读事件就绪就会交给当前对应的读处理器完成redis客户端初始化工作,后续redis客户端套接字的fd也会将读写事件注册到eventLoop中,如此一来所有的服务端和客户端socket的读写事件都会注册到epoll上,让epoll作为reactor进行轮询,然后根据读写事件分配到各自的handlerrfileProc/wfileProc 指针所指向的函数上。
这里我们补充的一下rfileProc/wfileProc指针指向的函数列表:

  1. rfileProc:如果是redis服务端则该指针指向acceptTcpHandler处理新连接,如果是客户端则指向readQueryFromClient处理客户端的命令。
  2. wfileProc:该指针服务端和客户端都一样,指向sendReplyToClient用于将响应结果发送给客户端。

在这里插入图片描述

对应的我们给出上述描述的核心代码段,可以看到main方法会调用aeMain开始事件轮询:

int main(int argc, char **argv) {
	   //前置初始化步骤
	   //......
    //事件循环轮询前置操作
    aeSetBeforeSleepProc(server.el,beforeSleep);
    //执行事件驱动框架,循环处理各种触发的事件
    aeMain(server.el);
    //事件循环后置操作
    aeDeleteEventLoop(server.el);
    return 0;
}

步入aeMain即可看到无限循环传入eventLoop查看是否有就绪的事件:

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        //......
        //传入eventLoop查看是否有socket的事件就绪
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}

继续步入aeProcessEvents即看到轮询就绪事件、acceptor调用acceptTcpHandler分发到读写的处理器handler上、后续客户端都会基于读写handler完成事件处理这样一套核心的reactor模型设计:

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
    //......
		//调用epoll获取所有就绪的socket的读写事件
        numevents = aeApiPoll(eventLoop, tvp);
        for (j = 0; j < numevents; j++) {
        	//获取当前事件的读写类型为mask赋值
            aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
            int mask = eventLoop->fired[j].mask;
            int fd = eventLoop->fired[j].fd;
            int rfired = 0;

		  //如果是读事件则交给rfileProc指向的函数,可以是服务端socket的连接处理器acceptTcpHandler,也可能是客户端的命令处理器readQueryFromClient
            if (fe->mask & mask & AE_READABLE) {
                rfired = 1;
                fe->rfileProc(eventLoop,fd,fe->clientData,mask);
            }
            //如果是写事件则调用wfileProc指向的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
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用JAVA代码实现发送订阅消息以及模板消息

今天写了一个商品到货提醒的job任务&#xff0c;具体效果如下 这里用到了微信的发送订阅消息&#xff0c;主要代码是这一块的&#xff0c;最后我把发送了消息的订单存到表里&#xff0c;因为是定时任务&#xff0c;大家可不存 发送订阅消息 | 微信开放文档 /*** 微信平台-商品…

vue+canvas画布实现网页签名效果

1、签名自定义组件代码示例&#xff1a; qianMing.vue <template><!-- 容器&#xff0c;包含画布和清除按钮 --><div class"signature-pad-container"><!-- 画布元素&#xff0c;用于用户签名 --><canvasref"canvas" <!--…

领克杀入纯电赛道:年轻人想要一台什么样的大电轿?

‍作者 |老缅 编辑 |德新 6月12日&#xff0c;领克旗下首款纯电动车型在瑞典进行了全球首秀&#xff0c;该车正式定名为Z10。 Z10的字母「Z」&#xff0c;源自ZERO。 Zeal-激情&#xff0c;Enjoy-享受&#xff0c;Responsibility-责任&#xff0c;Original-原创&#xff0c;…

动态规划数字三角形模型——AcWing 275. 传纸条

动态规划数字三角形模型 定义 动态规划数字三角形模型是在一个三角形的数阵中&#xff0c;通过一定规则找到从顶部到底部的最优路径或最优值。 运用情况 通常用于解决具有递推关系、需要在不同路径中做出选择以达到最优结果的问题。比如计算最短路径、最大和等。 计算其他…

中国高分辨率土壤侵蚀因子K

土壤可蚀性因子&#xff08;K&#xff09;数据&#xff0c;基于多种土壤属性数据计算&#xff0c;所用数据包括土壤黏粒含量&#xff08;%&#xff09;、粉粒含量&#xff08;%&#xff09;、砂粒含量&#xff08;%&#xff09;、土壤有机碳含量&#xff08;g/kg&#xff09;、…

【新版本来袭】ONLYOFFICE桌面编辑器8.1 —— 重塑办公效率与体验

文章目录 一、功能完善的PDF编辑器&#xff1a;重塑文档处理体验编辑文本插入和修改各种对象&#xff0c;如表格、形状、文本框、图像、艺术字、超链接、方程式等添加、旋转和删除页面添加文本注释和标注 二、幻灯片版式设计&#xff1a;创意展示的无限舞台三、改进从右至左显示…

规则引擎-Aviator 表达式校验是否成立

目录 介绍特性使用更多文献支持 介绍 Aviator是一个轻量级、高性能的Java表达式执行引擎&#xff0c;它动态地将表达式编译成字节码并运行。 特性 支持绝大多数运算操作符&#xff0c;包括算术操作符、关系运算符、逻辑操作符、位运算符、正则匹配操作符(~)、三元表达式(?:…

接口防篡改+防重放攻击

接口防止重放攻击&#xff1a;重放攻击是指攻击者截获了一次有效请求(如交易请求),并在之后的时间里多次发送相同的请求&#xff0c;从而达到欺骗系统的目的。为了防止重放攻击&#xff0c;通常需要在系统中引入一种机制&#xff0c;使得每个请求都有一个唯一的标识符(如时间戳…

Go 如何使用指针灵活操作内存

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

华为的开发语言有2中,分别是ArkTS和仓颉,他们的区别是什么?

华为的开发语言有2中&#xff0c;分别是ArkTS和仓颉&#xff0c;他们的区别在哪呢&#xff1f; ArkTS和仓颉&#xff08;cangjie&#xff09;他们的区别是什么&#xff1f; 华为的仓颉和 ArkTS 是两种不同的编程语言&#xff0c;它们有以下区别&#xff1a; 设计目的&#xff1…

emoji控必备:制作一个emoji面板插件

说在前面 &#x1f4bb;在数字时代&#xff0c;emoji表情符号已成为很多人沟通的重要工具&#xff0c;但是输入法中的emoji表情包可能不太够用&#xff0c;所以很多时候我会到在线的网站去复制emoji&#xff0c;然后再回来粘贴&#xff0c;这样操作感觉有点繁琐&#xff0c;所以…

在线朋友圈系统(Java Web)

本项目是一个基于Java Web技术栈开发的在线朋友圈系统&#xff0c;提供用户注册、登录、动态发布与评论、好友发现与管理等功能。通过Spring Boot、MySQL、MyBatis、Sa-token以及LayUI等技术实现&#xff0c;确保系统具有良好的性能和扩展性。 技术栈 后端技术 Spring Boot: …

问题-python-爬虫无法爬取外网资源问题(python爬虫)

方法一&#xff1a; 这个报错通过关掉梯子就能解决&#xff0c;目前不清楚具体原理。 后续了解具体原理了&#xff0c;我会在这篇文章上更新具体分析—— 方法二&#xff1a; 也可以把这个东西打开&#xff0c;但是用完建议关掉。

红酒品鉴新手速成:一键解锁味觉密码,让你秒变品鉴达人

红酒&#xff0c;这被誉为“液体宝石”的美酒&#xff0c;承载着丰富的口感和深邃的文化。对于许多人来说&#xff0c;品鉴红酒既是一种享受&#xff0c;也是一门艺术。然而&#xff0c;对于初学者来说&#xff0c;如何开始这场美妙的味觉之旅呢&#xff1f;今天&#xff0c;就…

vite项目自定义端口号

server.port​ 类型&#xff1a; number默认值&#xff1a; 5173 指定开发服务器端口。 注意&#xff1a;如果端口已经被使用&#xff0c;Vite 会自动尝试下一个可用的端口&#xff08;5174&#xff09;&#xff0c;所以这可能不是开发服务器最终监听的实际端口。 在vite.con…

【金】02Y90-60 大数据-HivetoMysQL

1、安装 Java 程序&#xff08;jdk&#xff09; 2、添加以下JAR包 3、确认配置成自己的数据库 ....

jenkins api部署时,一直提示pending-Finished waiting

问题&#xff1a; 调用jenkins api部署时&#xff0c;一直提示pending-Finished waiting 解决方案&#xff1a; 这个问题困扰了很久&#xff0c;一直没有思路&#xff0c;后面看到调用jenkinsAPI本身会出现一段提示&#xff0c;pending in the quiet period&#xff0c;通过搜…

NAS安全存储怎样实现更精细的数据权限管控?

NAS存储&#xff0c;即网络附属存储&#xff08;Network Attached Storage&#xff09;&#xff0c;是一种专用数据存储服务器&#xff0c;其核心特点在于将数据存储设备与网络相连&#xff0c;实现集中管理数据的功能。 NAS存储具有以下明显优势&#xff0c;而被全球范围内的企…

PostgreSQL 17 Beta 1 发布!

PostgreSQL 全球开发小组宣布&#xff0c;PostgreSQL 17 的第一个测试版本现已可供下载。此版本包含 PostgreSQL 17 正式发布时将提供的所有功能的预览&#xff0c;但测试期间版本的某些细节可能会发生变化。 #PG培训#PG考试#postgresql培训#postgresql考试#postgresql认证 您…