来聊聊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,一经查实,立即删除!

相关文章

为什么CDN加速后网站访问速度较慢

小提示&#xff0c;这种场景出现的概率比较低。 背景说明 使用天翼云CDN加速后&#xff0c;正常情况下网站各个维度的性能指标均会得到明显提升&#xff0c;具体的性能指标以及相关参数信息&#xff0c;详情请见&#xff1a;性能指标。如果使用CDN加速后没有得到预期的性能提…

一种改进最大相关峭度解卷积的滚动轴承故障诊断方法(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…

想法是否靠谱5步判断(学习笔记)

产品价值 帮助用户解决了什么样的问题&#xff1f; 在没有解决用户问题之前&#xff0c;用户是怎么解决的 与之前的方法相比有没有更高效&#xff0c;更便捷的满足用户的需求 目标市场 刚需才是永远的王牌&#xff0c;年轻消费者的一些观察 市场规模 存量市场&#xff1a…

spring boot的yml文件参数最后是一个点

在YAML文件中&#xff0c;点&#xff08;.&#xff09;是用来表示文档分隔符的&#xff0c;而不是用来表示参数的一部分。如果你在YAML文件中看到参数后面跟着一个点&#xff08;.&#xff09;&#xff0c;这可能是一个语法错误。 例如&#xff0c;下面的YAML文件是无效的&…

就业率低于5%,2024高考志愿还能选择计算机吗?

大家好&#xff0c;我是瑶琴呀&#xff0c;拥有一头黑长直秀发的女程序员。 这里的 5% 是指某些高校计算机专业毕业后找到对口工作的数据&#xff0c;不代表全部啊。 高考刚刚结束&#xff0c;接下来填志愿是家长和学子最关心的事。那么&#xff0c;2024 年专业选择要不要选计…

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

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

自定义弹窗禁止底部页面内容滑动(禁止穿透)

一、禁止弹窗下方页面内容滚动 在弹窗的外部盒子上加上 touchmove.stop.prevent“moveHandle” <view class"model" touchmove.stop.prevent"moveHandle"></view>定义方法 moveHandle // 禁止弹窗滚动function moveHandle() {return false}…

【Savitzky-Golay 滤波器】scipy.signal.savgol_filter 的使用

scipy.signal.savgol_filter 是 SciPy 库中用于数据平滑的函数。Savitzky-Golay 滤波器通过多项式拟合来平滑数据&#xff0c;并保留信号的高频特性&#xff0c;比其他平滑方法&#xff08;如移动平均&#xff09;更能保留信号的特征。以下是对 savgol_filter 的详细解释&#…

helm chart里优雅的定义k8s容器的启动命令行

在 Helm Chart 中定义容器的启动命令行时&#xff0c;确实可以直接写整个命令行&#xff0c;但需遵循 Helm 的模板语法。Helm 使用 Go 的文本模板语言来动态生成 Kubernetes 配置文件&#xff0c;这意味着你需要将完整的命令行字符串放在双大括号 {{ }} 内&#xff0c;并且可能…

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

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

[Android]修改XML中定义的约束比例

修改约束比例&#xff1a; /// 约束比例修改 private fun adapterCellRatio(holder: BaseViewHolder) {// 确保视图完全加载后进行操作val consLayoutBaseBG holder.getView<ConstraintLayout>(R.id.cl_cell_bg) // 获取当前约束比例val currentDimensionRatio getCur…