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 时会主动进入阻塞状态,等待事件发生(如某个套接字变为可读或可写)。 那么事件一直没有发生,岂不是就完全阻塞等死了。





相关推荐
地理探险家23 分钟前
各类有关NBA数据统计数据集大合集
数据库·数据集·数据·nba·赛季
SelectDB技术团队2 小时前
顺丰科技:从 Presto 到 Doris 湖仓构架升级,提速 3 倍,降本 48%
大数据·数据库·数据仓库·信息可视化·数据分析·doris·实时分析
wangbaowo2 小时前
MySQL数据库下篇
数据库·经验分享·笔记·学习·mysql·安全
ABdolphin2 小时前
Spring-博客系统项目
数据库·sql·spring
伤不起bb2 小时前
MySQl 数据库操作
数据库·mysql
是店小二呀3 小时前
【金仓数据库征文】金仓数据库(KingbaseES)迁移与集群部署实战:从MySQL到KES的全流程解析
数据库·mysql·金仓数据库 2025 征文·数据库平替用金仓
一只专注api接口开发的技术猿3 小时前
1688 API 自动化采集实践:商品详情实时数据接口开发与优化
大数据·运维·数据库·自动化
RingWu3 小时前
redis sentinel和redis cluster的主从切换选举过程
redis
昔我往昔3 小时前
MySQL中为什么使用B+树结构、B+树和普通的平衡树的区别
数据库·b树·mysql
翁正存3 小时前
MySQL为什么选择B+树
数据库·b树·mysql