redis源码分析之IO多路复用

文章目录

    • 1、简述
    • 2、多路复用的三个函数
    • 3、创建epoll实例
    • 4、绑定端口、监听端口
    • 5、向epoll实例注册连接事件
    • 6、从epoll实例中获取就绪的事件

1、简述

众所周知,redis是一款抗高并发的利器,据官方压测,单机可达10万qps。但背后实际处理命令的线程只有一条,这听上去其实挺匪夷所思的,因为在我们的日常开发中,说到高并发,多线程是一个非常常用的解决方案。那redis凭什么靠一条线程,就能支持高并发呢?最主要的原因,就是标题所说的IO多路复用,IO多路复用是怎么做的呢?这是老八股了,IO多路复用,背后依赖的是多路复用的函数,有select、poll、epoll,linux默认使用的是epoll函数,redis把客户端连接通过epoll函数给到内核,内核监听到连接有可读写的事件,就将该事件返回redis进行处理。那具体的实现细节呢?redis怎么给的内核,内核又怎么返回的呢?今天,聊赖这里面的细节

2、多路复用的三个函数

epoll函数由3个函数组合来完成多路复用这件事。分别是:
epoll_create、epoll_ctl、epoll_wait
1)、epoll_create:创建epoll实例
2)、epoll_ctl:将连接对应的socket描述符注册到epoll实例中
3)、epoll_wait:获取epoll实例中可读写的描述符
画一个简单的流程图串一下这三个函数的作用
请添加图片描述
从图中可以看出,redis在启动的时候,先是通过epoll_create函数创建epoll实例,然后绑定端口、监听端口,然后通过epoll_ctl函数注册连接事件,最后会搞一个死循环,通过epoll_wait函数获取可读写的事件(每一个事件对应的都是一个可读写的客户端连接)
铺垫完上面的流程,我们看一下源码。redis的启动源码在server.c文件的main方法中,main方法是redis启动的入口,其中有很多流程,但是我们不要全部都看,就看图中流程涉及到的逻辑

3、创建epoll实例

首先是通过内核提供的epoll_create函数创建epoll实例,这个流程入口在initServer方法中.

void initServer(void) {
    ......

    //创建epoll实例
    server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
    if (server.el == NULL) {
        serverLog(LL_WARNING,
            "Failed creating the event loop. Error message: '%s'",
            strerror(errno));
        exit(1);
    }
    ......
}

aeCreateEventLoop是创建epoll实例的入口,我们进入这个方法。

aeEventLoop *aeCreateEventLoop(int setsize) {
    ......
    
    if (aeApiCreate(eventLoop) == -1) goto err;
    
    ......
}

其中又调用了一个aeApiCreate方法,这个方法是对epoll_create函数做了一层封装,我们继续进入aeApiCreate方法。

static int aeApiCreate(aeEventLoop *eventLoop) {
    ......
    
    //创建epoll实例
    //这里的1024并不是说epoll函数只能监听1024个描述符.因为在2.6.8内核之后,内核维护的是一个动态的队列,理论上我们可以一直添加描述符
    
    state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */
    
    ......
}

这里就看到了我们想找的epoll_create函数。(这里顺便说一下,这种只看主流程的源码阅读方法,很容易能得到一些结论,也很容易坚持下去。)

4、绑定端口、监听端口

创建完epoll实例后,接下来就是绑定端口、监听端口。
这部分的代码也是在initServer方法中,就在创建epoll实例的下方

void initServer(void) {
    
    ......
    //创建epoll实例
    server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
    if (server.el == NULL) {
        serverLog(LL_WARNING,
            "Failed creating the event loop. Error message: '%s'",
            strerror(errno));
        exit(1);
    }
    server.db = zmalloc(sizeof(redisDb)*server.dbnum);

    ......
    
    //绑定、监听端口
    if (server.port != 0 &&
        listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
        exit(1);
    ......
}

绑定、监听端口的逻辑在listenToPort方法中,该方法的入参有3个值。
第一个值:要绑定、监听的端口,默认是6379。
第二个值:描述符。
第三个值:描述符的数量。
后面这两个参数还没值,需要到listenToPort方法中赋值。

int listenToPort(int port, int *fds, int *count) {
    ......
    //绑定IPV6
    fds[*count] = anetTcp6Server(server.neterr,port,NULL,server.tcp_backlog);

    ......
    //绑定IPV4
    fds[*count] = anetTcpServer(server.neterr,port,NULL,server.tcp_backlog);

    ......
    (*count)++;
}

用gdb debug一下,可以看到,最终fds数组一共赋2个值,count赋值2

5、向epoll实例注册连接事件

这个逻辑还是在initServer方法中。server.ipfd_count的值就是上面的那个count值,是2。所以这个循环会执行2次,注册2个连接事件,一个IPV4、一个IPV6
aeCreateFileEvent,是一个非常重要的方法,是用来创建事件的。该方法有5个入参。
第一个:redis对应epoll实例的结构体。
第二个:需要注册的描述符。
第三个:需要注册的事件类型。
第四个:事件触发后的回调函数。
第五个:客户端数据。我们是注册连接事件,所以不会有客户端数据,因为还没有客户端连接redis

void initServer(void) {
    ......
    //注册连接事件
    
    for (j = 0; j < server.ipfd_count; j++) {
    
    ......

    if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,acceptTcpHandler,NULL) == AE_ERR)
    
    ......
    }

    ......
}

我们进入aeCreateFileEvent方法,

int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
        aeFileProc *proc, void *clientData)
{
    ......
    
    //aeApiAddEvent函数内部调用epoll_ctl函数
    if (aeApiAddEvent(eventLoop, fd, mask) == -1)
        return AE_ERR;
    ......
    //将acceptTcpHandler回调函数挂到当前连接事件上
    if (mask & AE_READABLE) fe->rfileProc = proc;
    if (mask & AE_WRITABLE) fe->wfileProc = proc;
    
    ......
}

aeCreateFileEvent主要就是做两件事,注册连接事件、给事件注册回调函数,回调函数proc就是acceptTcpHandler。aeApiAddEvent是对epoll_ctl函数的封装。我们进入aeApiAddEvent方法看一下

static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
    ......
    //调用epoll的epoll_ctl函数注册事件,一共4个参数。
    //1、epoll实例
    //2、要执行的操作类型,添加事件还是修改修改事件。第一次肯定是添加事件
    //3、要监听的文件描述符
    //4、epoll_event类型变量
    if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1;
    ......
}

这里,我们就看到了epoll_ctl函数。

6、从epoll实例中获取就绪的事件

这个获取就绪事件的动作,是在main方法的aeMain函数中。

int main(int argc, char **argv) {
    ......
    //执行aeMain函数开启事件循环处理框架。就是用epoll_wait从内核获取就绪的事件
    aeMain(server.el);
    ......
}

我们进入aeMain函数。

void aeMain(aeEventLoop *eventLoop) {
    //只要redis实例没有停止,while循环就会一直执行
    eventLoop->stop = 0;
    //stop是redis服务是否停止的标志,如果stop值变为1,说明redis服务停止了
    while (!eventLoop->stop) {
        ......
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
    }
}

我们看到获取就绪的事件函数是aeProcessEvents,我们进入其中看一下

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
        ......
        //调用多路复用API
        numevents = aeApiPoll(eventLoop, tvp);
        ......
}

可以看到一个aeApiPoll函数,该函数是对epoll_wait函数的封装,我们继续进入aeApiPoll函数。

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;

            //EPOLLIN代表epoll模型的读事件,这一行代码的意思是将epoll的读事件映射到redis事件驱动框架的读事件
            if (e->events & EPOLLIN) mask |= AE_READABLE;

            //EPOLLOUT代表epoll模型的写事件,这一行代码的意思是将epoll的写事件映射到redis事件驱动框架的写事件
            if (e->events & EPOLLOUT) mask |= AE_WRITABLE;

            //EPOLLERR:错误事件,表示文件描述符对应套接字出错
            if (e->events & EPOLLERR) mask |= AE_WRITABLE;
            if (e->events & EPOLLHUP) mask |= AE_WRITABLE;

            //将epoll模型中已就绪的描述符映射到redis事件循环框架的就绪事件数组中
            eventLoop->fired[j].fd = e->data.fd;

            //给已就绪的事件设置事件类型
            eventLoop->fired[j].mask = mask;
        }
    }
    return numevents;
}

在其中,我们看到了epoll_wait函数,epoll_wait一共四个入参。
第一个:要监听的epoll实例描述符。
第二个:内核会将就绪的事件放入该集合。
第三个:要监听的描述符数量。
第四个:等待结果返回的超时时间。
返回了结果后,后面就是处理就绪的事件,epoll_wait方法是redis IO多路复用的关键所在,redis能不断的接收客户端请求,依赖的就是epoll_wait函数,我给每一行代码都加了注释,可以细看一下。

最后说一下,文章参考了极客时间的redis源码课程《redis源码剖析与实战》,文章写的很好,有兴趣的小伙伴可以去看看。

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

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

相关文章

07、vue : 无法加载文件 C:\Users\JH\AppData\Roaming\npm\vue.ps1,因为在此系统上禁止运行脚本。

目录 问题解决&#xff1a; 问题 vue : 无法加载文件 C:\Users\JH\AppData\Roaming\npm\vue.ps1&#xff0c;因为在此系统上禁止运行脚本。 在使用 VSCode 时&#xff0c;创建 Vue 项目报的错 创建不了 Vue 项目 解决&#xff1a; 因为在此系统上禁止运行该脚本&#xff0…

什么是DITA?从百度的回答说起

▲ 搜索“大龙谈智能内容”关注GongZongHao▲ 什么是DITA? 把这个问题输入百度&#xff0c;获得以下回答&#xff1a; DITA 是“Darwin Information Typing Architecture”&#xff08;达尔文信息类型化体系结构&#xff09;的缩写&#xff0c;它是IBM 公司为OASIS 所支持…

交叉编译程序:以 freetype 为例

1 程序运行的一些基础知识 1.1 编译程序时去哪找头文件&#xff1f; 系统目录&#xff1a;就是交叉编译工具链里的某个 include 目录&#xff1b;也可以自己指定&#xff1a;编译时用 “ -I dir ” 选项指定。 1.2 链接时去哪找库文件&#xff1f; 系统目录&#…

面试算法48:序列化和反序列化二叉树

题目 请设计一个算法将二叉树序列化成一个字符串&#xff0c;并能将该字符串反序列化出原来二叉树的算法。 分析 先考虑如何将二叉树序列化为一个字符串。需要逐个遍历二叉树的每个节点&#xff0c;每遍历到一个节点就将节点的值序列化到字符串中。以前序遍历的顺序遍历二叉…

latex设置图片的位置

Latex提供了一些命令来控制图片的位置。我们可以通过使用\begin{figure}[位置选项]来控制图片的位置。位置选项可以有h、t、b、p、!这五个&#xff0c;分别表示以下含义&#xff1a; h:表示放在当前位置&#xff0c;不过有时由于论文的格式限制&#xff0c;可能放不下。 t:表示…

VulnHub jarbas

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【python】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏…

ps5计时计费管理系统软件怎么使用教学,佳易王PS5体验馆计时收费管理倒计时提醒软件试用下载

ps5计时计费管理系统软件怎么使用教学&#xff0c;佳易王PS5体验馆计时收费管理倒计时提醒软件试用下载 每台机子可以自由设置倒计时提醒的时间&#xff0c;到了时间后&#xff0c;电脑会发出语音提醒同时改变颜色双重提醒方式。也可以在中途关闭提醒或更改提醒时间。每个机子可…

6.Spark共享变量

概述 共享变量 共享变量的工作原理Broadcast VariableAccumulator 共享变量 共享变量的工作原理 通常&#xff0c;当给 Spark 操作的函数(如 mpa 或 reduce) 在 Spark 集群上执行时&#xff0c;函数中的变量单独的拷贝到各个节点上&#xff0c;函数执行时&#xff0c;使用…

Linux应用开发基础知识——交叉编译与gcc编译(一)

前言&#xff1a; 源文件需要经过编译才能生成可执行文件。在 Windows 下进行开发时&#xff0c;只需 要点几个按钮即可编译&#xff0c;集成开发环境(比如 Visual studio)已经将各种编译 工具的使用封装好了。Linux 下也有很优秀的集成开发工具&#xff0c;但是更多的时候是 直…

【复盘】记录一次JVM 异常问题 java.lang.OutOfMemoryError: unable to create new native thread

背景是最新运营提了一个需求&#xff0c;需要根据用户信息拉去三分机构的信贷数据&#xff0c;需要达到一天百万级别&#xff0c;但是经过实际测试&#xff0c;也只能达到40W量级&#xff0c;具体就是通过起多个Spring Boot项目&#xff0c;每个项目1S拉一个用户&#xff0c;基…

【Head First 设计模式】-- 观察者模式

背景 客户有一个WeatherData对象&#xff0c;负责追踪温度、湿度和气压等数据。现在客户给我们提了个需求&#xff0c;让我们利用WeatherData对象取得数据&#xff0c;并更新三个布告板&#xff1a;目前状况、气象统计和天气预报。 WeatherData对象提供了4个接口&#xff1a; …

网络验证码--你到底是爱它还是恨它?

互联网安全防火墙&#xff08;1&#xff09;--网络验证码的科普 1 戏言部分 为了在网络上吸引大家读这个文章&#xff0c;在想标题的时候&#xff0c;也是够了。本来是严肃的科普学术帖&#xff0c;但是却一股强烈的“不转不是中国人&#xff0c;让男孩沉默女孩流泪” 这种…

OpenSSL生成CA证书

基本概念 证书类别 根证书&#xff1a;生成服务端证书&#xff0c;客户端证书的基础。自签名。服务端证书&#xff1a;由根证书签发。配置在服务器上。客户端证书&#xff1a;由根证书签发。配置在浏览器、移动APP等客户端上。 认证方式 单向认证&#xff08;Client鉴权Serv…

《视觉SLAM十四讲》-- 概述与预备知识

文章目录 01 概述与预备知识1.1 SLAM 是什么1.1.1 基本概念1.1.2 视觉 SLAM 框架1.1.3 SLAM 问题的数学表述 1.2 实践&#xff1a;编程基基础1.3 课后习题 01 概述与预备知识 1.1 SLAM 是什么 1.1.1 基本概念 &#xff08;1&#xff09;SLAM 是 Simultaneous Localization a…

第二章 02Java基础-数据类型、标识符、键盘录入

文章目录 前言一、数据类型二、标识符三、键盘录入总结前言 今天我们学习Java基础,数据类型、标识符、键盘录入 一、数据类型 1.数据类型大体上可以分为两类,一类是基本数据类型,另外一类是引用数据类型。今天我们学习基本数据类型。 2.基本数据类型可以分为四类八种,整…

【网络安全技术】公钥密码体制

一、两种基本模型 1.加密模型 A要给B发信息&#xff0c;那就拿B的公钥加密&#xff0c;传给B&#xff0c;B收到后会拿他自己的私钥解密得到明文。 2.认证模型&#xff08;数字签名&#xff09; A用自己的私钥加密&#xff0c;传输之后&#xff0c;别人拿A的公钥解密&#xff…

亚马逊云科技大语言模型下的六大创新应用功能

目录 前言 亚马逊云科技的AI创新应用 ​编辑 Amazon CodeWhisperer Amazon CodeWhisperer产品的优势 更快地完成更多工作 自信地进行编码 增强代码安全性 使用收藏夹工具 自定义 CodeWhisperer 以获得更好的建议 如何使用Amazon CodeWhisperer 步骤 1 步骤 2 具体…

辅助驾驶功能开发-功能规范篇(22)-9-L2级辅助驾驶方案功能规范

1.3.7.2 行人、骑行者(横向)AEB 系统 1.3.7.2.1 状态机 1.3.7.2.2 信号需求列表 同 1.3.2.1.2。 1.3.7.2.3 系统开启关闭 同 1.3.2.1.3。 触发横向 AEB 的目标包括横向运动的行人、骑行者(包括自行车、摩托车、电瓶车和平衡车上的行人)。 1.3.7.2.4 制动预填充 制动系统…

pyusb环境搭建和无法发包问题

pyusb环境搭建和无法发包问题 项目需要对usb设备进行开发调试&#xff0c;选择搭建pyusb环境进行调试测试&#xff0c;这里记录下完整流程和中间解决的一些问题。 我使用的环境是window10 64bit, vscode 1.84.0 , Python 3.11.6 1 安装流程 参考github上的 https://github.…

伪随机序列——m序列及MATLAB仿真

文章目录 前言一、m 序列1、m 序列的产生2、m 序列的性质①、均衡性②、游程分布③、移位相加特性④、自相关函数⑤、功率谱密度⑥、伪噪声特性 二、M 序列1、m 序列的产生2、m 序列的性质 三、MATLAB 中 m 序列1、m 序列生成函数的 MATLAB 代码2、MATLAB 仿真 前言 在通信系统…