来聊聊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则加2int mask; /* one of AE_(READABLE|WRITABLE) *///读事件处理器指针指向读事件处理函数handleraeFileProc *rfileProc;//写事件处理器指针指向读事件处理函数handleraeFileProc *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,也可能是客户端的命令处理器readQueryFromClientif (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
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

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

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

相关文章

一种改进最大相关峭度解卷积的滚动轴承故障诊断方法(MATLAB)

近年来&#xff0c;最大相关峭度解卷积受到了研究人员越来越多的关注&#xff0c;陆续有多篇研究论文将该方法应用于滚动轴轴承故障诊断。MCKD是由McDonald提出的一种解卷积方法&#xff0c;其算法中设计了一个新的目标函数——相关峭度&#xff0c;并以此为优化目标设计一系列…

HTML(24)——过渡

过渡 作用&#xff1a;可以为一个元素在不同的状态之间切换的时候添加过渡效果 属性名&#xff1a;transition(复合属性) 属性值&#xff1a;过渡的属性 花费时间(s) 提示&#xff1a; 过渡的属性可以是具体的CSS属性也可以为all&#xff08;两个状态属性值不同的所有属性…

HTML基础入门知识

HTML基础使用 文章目录 HTML基础使用1、什么是HTML2、web标准4、HTML语法规则5、常用的标签标题标签段落标签换行标签文本格式化标签div和span标签图片标签路径链接标签注释 1、什么是HTML 什么是网页 网站是指在因特网上根据一定的规则&#xff0c;使用 HTML 等制作的用于展示…

完美世界否认大规模裁员,存在项目和人员的正常调整

原标题&#xff1a;完美世界回应裁员传闻&#xff1a;确实存在人员调整 项目继续正常研发 易采游戏网6月25日消息&#xff1a;网络上热传完美世界进行史上最大规模裁员&#xff0c;甚至有消息称其两栋办公楼已近乎搬空&#xff0c;同时备受瞩目的游戏项目《完美新世界》和《一拳…

日立EX-PROII+系列全新升级,智慧随心控畅享新生活

随着科技的进步&#xff0c;各种智能家电也开始走入人们的生活&#xff0c;而在这个领域&#xff0c;日立用技术创新生活&#xff0c;不断为新时代注入活力&#xff0c;推出日立 EX-PROII系列家用净化中央空调&#xff0c;贯彻“小身材&#xff0c;大能量”核心设计理念&#x…

Docker编译nanopc-t4源码流程介绍

官方文档 Android系统编译 vnc加环境变量配置 https://github.com/friendlyarm/docker-cross-compiler-novnc 下载 git clone https://github.com/friendlyarm/docker-ubuntu-lxde-novnc cd docker-ubuntu-lxde-novnc docker build --no-cache -t docker-ubuntu-lxde-novnc …

【期末复习】计算机组成原理

海明码 最通俗的海明码计算方法&#xff0c;不需记公式&#xff0c;套步骤即可&#xff08;可能都不需要理解&#xff09; https://www.bilibili.com/video/BV1tL4y1h7Fd/ 接上一海明码视频&#xff08;海明码的纠错&#xff09; https://www.bilibili.com/video/BV1tf4y1A7NX/…

使用官方新工具手动升级 Quest 操作系统

Meta 近期推出了一款用于手动升级 Meta Quest 系统的工具&#xff0c;为用户提供了更多选择。本文将详细介绍如何使用这一工具进行系统升级。 优势与劣势 优势&#xff1a; 安装迅速&#xff1a;升级速度相比在线自动升级快&#xff0c;且可实时查看进度 即时升级&#xff1…

全国计算机等级考试WPS如何报名

全国计算机等级考试WPS如何报名&#xff1f; 注册并登录 全国计算机等级考试官网选择 考试服务-在线报名选择报考省份-开始报名

【Splitpanes】Vue.js 靠谱、简单并支持触摸的窗格分割器/调整器。

【Splitpanes】Vue.js 靠谱、简单并支持触摸的窗格分割器/调整器。 介绍安装使用示例与文档 介绍 Vue.js 靠谱、简单并支持触摸的窗格分割器/调整器。用于实现可调节窗口&#xff0c;支持Vue2、Vue3。 安装 Vue3 npm install splitpanesVue2 npm install splitpaneslegac…

东昂科技从创业板改道北交所:大客户依赖症明显,巨额分红又募投补流

《港湾商业观察》施子夫 黄懿 2024年6月24日&#xff0c;厦门东昂科技股份有限公司&#xff08;以下简称&#xff0c;东昂科技&#xff09;在北交所网站披露第二轮审核问询函的回复。自2024年1月IPO申请获北交所受理以来&#xff0c;东昂科技已经收到北交所下发的两轮审核问询…

项目实训-接口测试(十八)

项目实训-后端接口测试&#xff08;十八&#xff09; 文章目录 项目实训-后端接口测试&#xff08;十八&#xff09;1.概述2.测试对象3.测试一4.测试二 1.概述 本篇博客将记录我在后端接口测试中的工作。 2.测试对象 3.测试一 这段代码是一个单元测试方法&#xff0c;用于验证…

echarts+vue2实战(二)

目录 一、WebSocket【双向通信】的使用 1.1、前端 1.2、后端 二、前端组件的合并与优化 三、全屏切换 3.1、单页面切换 3.2、同页面多端联动 四、主题切换 4.1、单页面切换 4.2、同页面多端联动 一、WebSocket【双向通信】的使用 1.1、前端 在utils文件夹里创建soc…

ArkUI开发学习随机——得物卡片,京东登录界面

案例一&#xff1a;得物卡片 代码&#xff1a; Column(){Column(){Image($r("app.media.mihoyo")).width(200).height(200)Row(){Text("今晚玩这个 | 每日游戏打卡").fontWeight(700).fontSize(16).padding(4)}.width(200)Text("No.12").fontWe…

盲盒小程序开发:解锁未知,探索无限惊喜

一、开启新篇章 在追求独特与新颖的时代&#xff0c;盲盒以其神秘感与未知性&#xff0c;成为了年轻人热衷的购物新方式。为了满足这一市场需求&#xff0c;我们精心打造了一款全新的盲盒小程序&#xff0c;带您步入一个充满未知与惊喜的购物新领域。 二、产品亮点 精选商品&…

【机器学习】K-Means算法详解:从原理到实践

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 K-Means算法详解&#xff1a;从原理到实践引言1. 基本原理1.1 簇与距离度量1.2 …

JLPT历年真题刷题小程序:Navi日语社全新升级,更新至2024年真题!

Navi日语社小程序专为日语能力考试设计&#xff0c;提供全网最全的JLPT备考真题资源&#xff0c;包括日语N1-N5等级考试的历年真题&#xff0c;2024年真题将在7月底更新。无论你是日语新手准备参加N3考试练练手&#xff0c;还是准备冲刺N1最高等级&#xff0c;都能在这个小程序…

基于Java微信小程序火锅店点餐系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f;感兴趣的可以先收藏起来&#xff0c;还…

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

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

惯性级惯导的定位漂移估算

一般来说&#xff0c;惯性级陀螺仪指的是0.01度/小时的零偏稳定性&#xff08;是否可以作为等效常值漂移呢&#xff1f;&#xff09;&#xff0c;其定位误差大约为1海里每小时&#xff0c;其具体估算方法可见秦永元老师的《惯性导航》一书中静基座下系统误差传播特性分析一节内…