【redis】线程IO模型

Redis线程IO模型

总结:在redis5.0及之前,redis线程io模型是单线程。那么Redis单线程如何处理那么多的并发客户端连接的?原因两点:1)非阻塞io 2)多路复用(事件轮询)

以下,我就针对上面的总结来展开说说redis线程io模型。

1、客户端与redis服务器的通信过程

当客户端执行redis.set("key","value")命令时,

  • 客户端通过操作系统创建一个套接字
    • 这个套接字会连接到运行 Redis 服务器的机器地址和端口(通常是 6379)。
    • 一旦连接建立成功,你的程序就可以通过这个套接字向 Redis 服务器发送数据
  • 客户端将命令通过 write() 进入 内核写缓冲区
  • 内核写缓存区的数据会从网卡 → redis服务端的内核读缓冲区。
  • redis服务端通过多路复用(epoll)得知内核读缓存区有命令需要执行。
  • 执行之后的响应数据进入Redis服务器的 内核写缓冲区
  • 接着内核写缓存区的数据又会从网卡 → redis客户端的内核读缓冲区。
  • 客户端通过 read() 获取响应数据。

当客户端执行redis.get("key")命令时,

  • 客户端将命令通过 write() 进入 内核写缓冲区
  • 内核写缓存区的数据会从网卡 → redis服务端的内核读缓冲区。
  • redis服务端通过多路复用(epoll)得知内核读缓存区有命令需要执行。
  • 执行之后的响应数据进入Redis服务器的 内核写缓冲区
  • 接着内核写缓存区的数据又会从网卡 → redis客户端的内核读缓冲区。
  • 客户端通过 read() 获取响应数据。

GET 命令全流程:

客户端详细视角:

服务端详细视角:

redis服务端视角就能体现Redis高性能的核心

通过非阻塞IO + 多路复用(epoll/kqueue)单线程监听所有连接的缓冲区状态

  • 当某连接的读缓冲区有数据 → 触发Redis读取命令
  • 当某连接的写缓冲区有空闲 → 触发Redis发送响应

此时你可能还不了解非阻塞IO和多路复用究竟是什么?没事,继续看下去。

这里要特别注意,**这里的非阻塞IO是指redis服务器,而非客户端。**因此下面将对比介绍阻塞IO和非阻塞IO,以及多路复用(事件循环)。

2、非阻塞IO & 阻塞IO

阻塞IO:典型的例子就是Java 的 Jedis(同步阻塞客户端)

jedis.get("key") :

  • 触发 write() :客户端将 GET key 命令写入内核写缓冲区 → 通常瞬间完成

    • 缓冲区未满:写入数据
    • 缓冲区满了:线程卡在 write() 调用(阻塞)
  • 触发read() :客户端尝试从内核读缓冲区读取 Redis 的响应(如 "value"

    • 有数据:立即返回结果 → 线程继续执行

    • 无数据:线程卡在 read() 调用(阻塞)

非阻塞IO:典型的例子就是Redis 服务器内部

Redis 将 socket 设置为 Non_Blocking

  • 调用 read() 时无数据,也会立刻返回 EAGAIN 错误而非阻塞。
  • 调用 write() 时无空间,也会立刻返回 EAGAIN 错误而非阻塞。

这边想要再扩展一个非阻塞IO的例子:

支持非阻塞的客户端库(如 Lettuce

  1. commands.get() 将命令写入内核写缓冲区(非阻塞写)。
  2. 客户端库 注册回调函数 并立即返回。
  3. 库内部用 Selector 轮询 内核读缓冲区 → 数据到达后调用回调。

其实,redis只有在感知到内核读缓冲区有数据时,才会调用 read() 去读取数据。

那么redis是怎么感知到内核读缓冲区有数据的?当缓冲区满了,又是怎么知道要什么时候能继续写入数据?答案就是通过多路复用(epoll)。

3、多路复用(epoll)

java 复制代码
// Redis 事件循环伪代码
void eventLoop() {
    while(server.running) {
        // 0. 计算超时时间(动态值,假设为200ms)
    	timeout = calculate_timeout(); 
        
        // 1. 获取待处理事件(核心:epoll_wait 在此等待,最多只会阻塞等待timeout时间,如果在这个超时时间内有数据了就会恢复运行态,如果在这个超时时间内依然没有数据,那么就会去处理其他事件,比如时间事件)
        events = epoll_wait(epoll_fd, events, MAX_EVENTS, timeout); 

        // 2. 处理文件事件(网络请求,处理命令)
        for each event in events:
            if event.is_readable:  // 可读事件 → 执行客户端命令
                readQueryFromClient()
            if event.is_writable: // 可写事件 → 发送响应
                writeReplyToClient()

        // 3. 处理时间事件(定时任务,如RDB备份、Key过期)
        processTimeEvents()
    }
}

流程图如下:

以上代码,基本可以说明redis单线程都在干什么了:

一个永不停止的循环(while(1)),用 单线程 同时监听 所有客户端连接 + 定时任务 + 内部任务 ,通过事件分发处理请求。

这里具体介绍epoll机制:

java 复制代码
// Redis 仅通过 epoll 做一件事:
int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, timeout);
  • 监听对象:所有客户端连接的 socket
  • 监听事件
    • EPOLLIN内核读缓冲区有数据可读(客户端命令到达)
    • EPOLLOUT内核写缓冲区有空闲空间(可发送响应)

epoll 感知事件的底层原理是全程无CPU轮询!依赖硬件中断

java 复制代码
// 系统调用流程
int epoll_wait() {
    // 1. 检查就绪队列(快速路径)
    if (!list_empty(rdllist)) 
        return events; // 立即返回
    
    // 2. 无事件时:让出CPU
    set_current_state(TASK_INTERRUPTIBLE); // 标记为阻塞态
    schedule(); // 主动让出CPU → 其他进程运行
}

也就是说,进入epoll_wait方法,该redis单线程是处于阻塞态的,不占cpu的任何消耗。

这里我还想补充一点,就是之前我学的,redis的rdb持久化以及aof重写过程中会fork一个子进程、aof是开启后台线程刷屏、以及redis4.0出现的懒惰删除也是在后台线程进行的。以上三种情况与redis的单线程又是什么关系呢?

可以这样理解:Redis的「单线程」本质上是对命令执行模型的核心描述,但整个系统确实存在多线程/多进程协作。

BIO后台线程(Background I/O Threads):

  • AOF fsync :将AOF缓冲数据刷盘,因为 fsync() 可能阻塞30ms+
  • lazy free :异步删除大Key,避免主线程阻塞数秒。(BIO线程删除Key时,主线程已将该Key标记为逻辑删除(移除key的指针的指针引用),BIO只是物理释放内存)

子进程(Child Process):

  • AOF重写 触发 BGREWRITEAOF,fork子进程,父子进程共享内存页,Copy-On-Write 写时复制
  • RDB持久化 执行 SAVE / 定时保存,fork子进程,父子进程共享内存页,Copy-On-Write 写时复制

总结:主线程独占写操作(100%),而子进程是只读数据,而BIO线程只处理非数据操作。

  • AOF重写 触发 BGREWRITEAOF,fork子进程,父子进程共享内存页,Copy-On-Write 写时复制
  • RDB持久化 执行 SAVE / 定时保存,fork子进程,父子进程共享内存页,Copy-On-Write 写时复制

总结:主线程独占写操作(100%),而子进程是只读数据,而BIO线程只处理非数据操作。

相关推荐
海兰7 分钟前
使用 Spring AI 打造企业级 RAG 知识库第二部分:AI 实战
java·人工智能·spring
历程里程碑25 分钟前
二叉树---二叉树的中序遍历
java·大数据·开发语言·elasticsearch·链表·搜索引擎·lua
小信丶38 分钟前
Spring Cloud Stream EnableBinding注解详解:定义、应用场景与示例代码
java·spring boot·后端·spring
无限进步_42 分钟前
【C++】验证回文字符串:高效算法详解与优化
java·开发语言·c++·git·算法·github·visual studio
亚历克斯神43 分钟前
Spring Cloud 2026 架构演进
java·spring·微服务
七夜zippoe1 小时前
Spring Cloud与Dubbo架构哲学对决
java·spring cloud·架构·dubbo·配置中心
海派程序猿1 小时前
Spring Cloud Config拉取配置过慢导致服务启动延迟的优化技巧
java
阿维的博客日记1 小时前
为什么不逃逸代表不需要锁,JIT会直接删掉锁
java
William Dawson1 小时前
CAS的底层实现
java
九英里路1 小时前
cpp容器——string模拟实现
java·前端·数据结构·c++·算法·容器·字符串