Redis的IO多路复用

1 传统的socket编码模型

传统 Socket 模型通常采用 多线程/多进程 或 阻塞 I/O 的方式处理网络请求。以下是典型实现步骤:

  1. 创建套接字(Socket)

    步骤:调用 socket() 创建一个 TCP/UDP 套接字。通常把这个套接字称为【主动套接字】(Active Socket)

  2. 绑定本地地址(Bind)

    步骤:将套接字绑定到本地 IP 和端口

c 复制代码
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
server_addr.sin_addr.s_addr = INADDR_ANY;
bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
  1. 监听连接(Listen)
    步骤:调用 listen 函数,将主动套接字转换为【监听套接字】,开始监听客户端的连接。
c 复制代码
listen(sockfd, 5);  // 允许最多 5 个等待连接
  1. 接受连接(Accept)

    步骤:通过 accept() 接受新连接,返回新套接字(称为:【已连接套接字】)用于通信。

  2. 处理请求

    关键点:为每个新连接创建独立线程或进程,处理读写操作:

  3. 读写数据(阻塞 I/O)

    线程/进程内的操作:

    读取数据:调用 read() 从客户端读取数据(阻塞直到数据到达)。

c 复制代码
listenSocket = socket(); //调用socket系统调用创建一个主动套接字
bind(listenSocket); //绑定地址和端口
listen(listenSocket); //将默认的主动套接字转换为服务器使用的被动套接字,也就是监听套接字
while (1) {  //循环监听是否有客户端连接请求到来
 connSocket = accept(listenSocket);  //接受客户端连接 
 recv(connsocket);  //从客户端读取数据,只能同时处理一个客户端  
 send(connsocket); //给客户端返回数据,只能同时处理一个客户端
}

虽然它能够实现服务器端和客户端之间的通信,但是程序每调用一次 accept 函数,只能处理一个客户端连接。因此,如果想要处理多个并发客户端的请求,我们就需要使用多线程的方法,来处理通过 accept 函数建立的多个客户端连接上的请求。

c 复制代码
listenSocket = socket(); //调用socket系统调用创建一个主动套接字
bind(listenSocket);  //绑定地址和端口
listen(listenSocket); //将默认的主动套接字转换为服务器使用的被动套接字,即监听套接字
while (1) { //循环监听是否有客户端连接到来
   connSocket = accept(listenSocket); //接受客户端连接,返回已连接套接字
   pthread_create(processData, connSocket); //创建新线程对已连接套接字进行处理
   
}

//处理已连接套接字上的读写请求
processData(connSocket){
   recv(connsocket); //从客户端读取数据,只能同时处理一个客户端
   send(connsocket); //给客户端返回数据,只能同时处理一个客户端
}

2:为什么redis不采用传统的socket编程模型

虽然这种方法能提升服务器端的并发处理能力,遗憾的是,Redis 的主执行流程是由一个线程在执行,无法使用多线程的方式来提升并发处理能力。所以,该方法对 Redis 并不起作用。

传统 Socket 模型通常采用 多线程/多进程 或 阻塞 I/O,而 Redis 的多路复用模型在设计上存在以下关键差异:

3 Redis 多路复用的编程模型实现原理

多路I/O复用模型是指使用一个线程来监控多个文件描述符(fd)的读写状态,当某个fd准备好执行读或写操作时,就通知相应的事件处理器来处理。

多路I/O复用模型 , 就是通过少量线程 监控大量 连接,提升了 线程的利用率,减少了线程阻塞,提高了I/O效率和利用率。避免了阻塞式I/O模型中,单个线程只能等待一个fd的问题。

例如Linux系统中提供了多种多路I/O复用技术的实现方式,如select、poll、epoll等。

select/epoll等IO多路复用技术提供了一种基于事件触发的回调模式,每当有不同事件发生时,Redis能够迅速调用相应的事件处理器,始终保持在处理事件的状态,从而提升了其响应速度。

下面是 IO多路复用技术 的一个基础的 流程图:

Redis通过使用IO多路复用技术(如epoll、kqueue或select等),在一个线程内同时监听多个socket连接,当有网络事件发生时(如读写就绪),再逐一处理。

由于Redis线程并不会因为等待某个特定socket的IO操作完毕而停滞,它可以流畅地在多个客户端间切换,即时响应每个客户端的不同请求,从而实现在单线程环境下对大量并发连接的有效处理和高并发性能。

这样可以处理大量并发连接,并在单线程中高效地调度网络事件,使得单线程也能应对高并发场景。

所以Redis服务端,整体来看,就是一个以事件驱动的程序。

它的操作都是基于事件的方式进行的。客户端请求被分发给文件事件分派器,再由事件处理器处理。

在Redis中,事件驱动架构通过监听和处理各种网络I/O事件以及定时事件,使得Redis服务端能够在一个线程内高效地服务于多个客户端连接,并执行相关的命令操作。

1. 事件循环(Event Loop)

Redis 的主线程通过一个无限循环(Event Loop)持续监听所有注册的 socket 的事件(如可读、可写、超时等)。当事件发生时(如客户端发送请求),事件循环会触发对应的回调函数处理该事件。

2. 系统级多路复用技术

Redis 使用操作系统提供的多路复用 API(如 epoll(Linux)、kqueue(BSD/macOS)、select/poll(通用))来高效管理大量 socket 连接:

epoll(Linux):

水平触发(LT, Level Triggered):当 socket 可读时,只要事件未被处理,每次 epoll_wait 都会返回该事件。

边缘触发(ET, Edge Triggered):当 socket 可读时,只有第一次 epoll_wait 会返回该事件,后续需确保数据完全处理。

Redis 默认使用 epoll 的 边缘触发模式,避免重复触发事件。

kqueue(BSD/macOS):

提供更细粒度的事件控制,支持同时监听读、写、错误等事件。

Redis 在 macOS 上默认使用 kqueue。

3. 事件处理流程

注册事件:将 socket 的读/写事件注册到多路复用器(如 epoll)中。

等待事件:主线程通过 epoll_wait(或 kqueue 的 kevent)阻塞等待事件发生。

处理事件:当事件触发时,主线程依次处理每个事件(如读取客户端请求、写入响应)。

循环执行:重复上述步骤,形成事件循环。

4. 单线程模型

Redis 是单线程的,所有网络 I/O 和命令处理都在同一个线程中顺序执行。通过多路复用技术,单线程可以高效处理大量连接,避免了多线程的上下文切换开销。即多路复用复用的其实是这个单线程。

4 Redis 多路复用的实现细节

1. 网络 I/O 框架

Redis 的网络模块(networking.c)封装了多路复用逻辑:

事件注册:通过 aeApiAddEvent 将 socket 的读/写事件注册到 epoll/kqueue。

事件循环:aeMain 函数是事件循环的核心,持续调用多路复用 API 等待事件。

回调处理:事件触发后,调用对应的回调函数(如 readQueryFromClient 处理客户端请求)。

2. 非阻塞 I/O

Redis 的所有 I/O 操作(如读取客户端请求、写入响应)均采用非阻塞模式:

读操作:当 epoll 触发可读事件时,调用 read 读取数据,直到缓冲区为空或无法读取。

写操作:当 epoll 触发可写事件时,将响应数据写入发送缓冲区,直到数据全部发送或缓冲区满。

3. 时间事件(Timeout)

Redis 还支持时间事件(如键过期、持久化触发等),通过 aeCreateTimeEvent 注册,并在事件循环中处理。

5、优缺点对比

6、 适用场景

7:问题环节,概念澄清

问题1:

等待事件:主线程通过 epoll_wait(或 kqueue 的 kevent)阻塞等待事件发生。

Redis 的所有 I/O 操作(如读取客户端请求、写入响应)均采用非阻塞模式:

这2句是不矛盾,前面说阻塞,后面说非阻塞。





问题2:

主线程调用 epoll_wait 时会主动进入阻塞状态,等待事件发生(如某个套接字变为可读或可写)。 那么事件一直没有发生,岂不是就完全阻塞等死了。





相关推荐
科技小花4 小时前
数据治理平台架构演进观察:AI原生设计如何重构企业数据管理范式
数据库·重构·架构·数据治理·ai-native·ai原生
一江寒逸4 小时前
零基础从入门到精通MySQL(中篇):进阶篇——吃透多表查询、事务核心与高级特性,搞定复杂业务SQL
数据库·sql·mysql
D4c-lovetrain4 小时前
linux个人心得22 (mysql)
数据库·mysql
阿里小阿希5 小时前
CentOS7 PostgreSQL 9.2 升级到 15 完整教程
数据库·postgresql
荒川之神5 小时前
Oracle 数据仓库雪花模型设计(完整实战方案)
数据库·数据仓库·oracle
做个文艺程序员5 小时前
MySQL安全加固十大硬核操作
数据库·mysql·安全
不吃香菜学java5 小时前
Redis简单应用
数据库·spring boot·tomcat·maven
一个天蝎座 白勺 程序猿6 小时前
Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南
数据库·apache·etl·iotdb
不知名的老吴6 小时前
Redis的延迟瓶颈:TCP栈开销无法避免
数据库·redis·缓存
YOU OU6 小时前
三大范式和E-R图
数据库