当前位置: 首页 > article >正文

深刻理解redis高性能之IO多路复用

目录

1.文件描述符(File Descriptor)简称(fd):

1.1文件描述符的作用

2.*select()函数、poll()函数、epoll()函数*

2.1 select函数:

2.2 poll函数

2.3 epoll函数

1.文件描述符(File Descriptor)简称(fd):

文件描述符是操作系统为每个打开的文件/网络连接分配的一个整数编号,来告诉系统这是一个什么操作。

*你可以把它理解成一个身份证号*

每个打开的文件、socket连接、管道都有一个唯一的编号。

常见的文件描述符

1.1文件描述符的作用

1.文件操作:

int fd = open("file.txt", O_RDWR);
write(fd, "hello", 5);
close(fd);

这里 fd 就是 file.txt 的文件描述符,你可以通过 fd 告诉操作系统哪个文件做什么操作,这里就是告诉操作系统去写一个hello字符串。

2.网络编程:

int server_fd = socket(AF_INET, SOCK_STREAM, 0);
int client_fd = accept(server_fd, ...);

server_fd 和 client_fd 也是文件描述符!网络 socket 连接和文件一样,都是 FD 资源。

总结:这个文件操作符号就是一个标识,告诉操作系统的一个标识,操作系统只能识别0,1,2,3。这些数字,我们总不能写一段话告诉操作系统你给我这么做吧,不现实。

下面就是IO多路复用的核心

2.*select()函数、poll()函数、epoll()函数*

知识:select、poll 和 epoll 都是 I/O 多路复用(I/O Multiplexing)技术,主要用于管理多个文件描述符(如网络连接、文件、标准输入输出等),避免阻塞式 I/O 导致的性能瓶颈。它们的主要作用是在多个 I/O 事件(如可读、可写、异常)中进行监听,并在有事件发生时通知程序处理。

2.1 select函数:

int select (int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, conststruct timeval *timeout);

2.1.1 功能:

1.select这个函数负责监听多个文件描述符,看哪些文件发生了(可读、可写、或者异常)

2.select使用了固定大小的fd_set数组(一般最大支持1024个文件描述符)。

3.调用select时候,需要遍历所有的文件描述符,检查哪个就绪(每次都遍历一遍数组效率低)

2.1.2 工作流程:

假设你的应用是一个 Web 服务器,用户请求一个数据查询接口,整个过程如下:
1. 用户发送请求,Web 服务器通过一个 socket 接收到该请求,这时会有一个对应的文件描述符。
2. 服务器把文件描述符添加到 select() 的监视集合(
fd_set数组中)中。
3. select() 开始阻塞,等待某个文件描述符的事件发生。
4. 一旦服务器查询到数据库并准备好返回数据,文件描述符变成可读状态。
5. select() 检测到该文件描述符变为可读,返回并通知应用程序。
6. 应用程序从该文件描述符读取数据,并将查询结果返回给用户。

总结:

结合功能和例子可以发现select的缺点是我们在每次发生事件调用select的时候,都需要整体遍历一遍数组,这个效率低,而且数组的长度最大是1024.这是缺点。

2.2 poll函数

poll函数实现了动态数组,解决了数组长度的限制,但是还是没有解决每次遍历的问题。

pollfd 结构体:

struct pollfd {
    int fd;       // 需要监听的文件描述符
    short events; // 监听的事件(如可读、可写等)
    short revents; // 返回的事件(当某个事件发生时,表示该文件描述符上的事件)
};

动态数组中存放的是pollfd类型的结构体,每个结构体里有对应的文件描述符。

动态数组扩容:

我想大家都想知道动态数组那么数组的扩容怎么办,这个扩容是我们要自己写的扩容方式。

使用系统提供的realloc函数

假如超过初始数组大小:

if (nfds >= max_clients) {
    max_clients *= 2;  // 扩展数组的大小
    fds = realloc(fds, max_clients * sizeof(struct pollfd));
}

总结:他只是解决了数组大小还是没有解决遍历的问题。

2.3 epoll函数

这个函数就是集合了上面两个的缺点,然后出现的它,一般我们在redis中默认都是使用的它。

首先epoll的结构和前面的那两个不一样。

工作流程:

1.创建epoll实例

int epfd = epoll_create(1);  // 创建 epoll 实例

2.使用epoll_ctl注册文件描述符

struct epoll_event event;

event.events = EPOLLIN; // 监听可读事件

event.data.fd = socket_fd; // 添加文件描述符 socket_fd

epoll_ctl(epfd, EPOLL_CTL_ADD, socket_fd, &event); // 将文件描述符注册到 epoll 实例中

3.使用 epoll_wait 来获取发生事件的文件描述符

struct epoll_event events[10];  // 用来存储就绪事件的数组

int num_events = epoll_wait(epfd, events, 10, -1);  // 阻塞直到事件发生
for (int i = 0; i < num_events; i++) {
    if (events[i].events & EPOLLIN) {
        // 处理可读事件
    }
}

总结:记住这几个名词 ***epoll_ctl(添加/删除/修改/fd) , epoll_wait(和select函数类似,用来返回已经发生事件的数据),epoll(这个实例内部会维护一个事件表)***

下面是他的结构信息:

结构:

1.事件表:记录epoll监听的所有的fd。

2.红黑树:事件表的内部数据结构,用来存储所有注册的fd,高效的增,删,改

3.就绪队列:记录已经发生的事件fd,epoll_wait() 直接返回。

示例分析:

假设你的应用是一个 Web 服务器,用户请求一个数据查询接口,整个过程如下:

1. 用户发送请求,Web 服务器通过一个 socket 接收到该请求,这时会有一个对应的文件描述符(fd)。
2. 服务器创建了一个epoll实例内部维护了一个事件表,它的数据结构是红黑树。
3. 使用epoll_ctl向这个树添加fd。

4.这个内部的事件表等待某个fd的事件发生(这里是查询事件)。
5. 一旦服务器查询到数据库并准备好返回数据,fd变成可读状态。

6.当fd变为可读的同时,把这个fd添加到就绪队列中。
5. epoll_wait遍历就绪队列,返回并通知应用程序。
6. 应用程序从该fd读取数据,并将查询结果返回给用户。

总结:

1.红黑树存储所有的fd是因为有高效的增,删,改。(解决了存储长度问题

2.Linux 内核使用 I/O 事件驱动机制(Interrupt + 回调机制),当 fd 发生可读/可写事件时,内核会自动把它放到就绪队列里,不需要遍历所有 fd。(解决了遍历问题

3.就绪队列存储的只有已经发生的事件。


http://www.kler.cn/a/570775.html

相关文章:

  • Golang学习笔记_40——模版方法模式
  • Tauri+React跨平台开发环境搭建全指南
  • IDEA 接入 Deepseek
  • 如何使用Docker一键本地化部署LibrePhotos搭建私有云相册
  • Android开发,多宫格列表实现
  • Java——通配符以及上下限
  • 人工智能AI在汽车设计领域的应用探索
  • CPU负载高告警问题的定位与优化建议
  • 【MySQL】使用LOAD DATA INFILE导入数据时报错:Errcode: 2 - No such file or directory
  • UnrealEngine UE5 可视化 从地球观察火星 金星 土星 运动轨迹
  • PyTorch 中实现模型训练看板实时监控训练过程中的关键指标
  • 【开发环境配置】基于Openssh的git多key配置
  • Magic 1-For-1: 在一分钟内生成一分钟视频片段(基于Python实现,视频生成模型)
  • 蓝桥杯C语言组:基于蓝桥杯煤球数目问题的数列累加解决方案研究
  • SQL调优
  • AI大模型之一 GodeGPT调用Dify+DeepSeek属于自己私域模型
  • Zabbix zbx_auditlog_global_script SQL注入漏洞缓解和修复方案
  • C++014(elif语句)
  • Highcharts 配置语法详解
  • Redis除了做缓存还能做什么?