Redis 线程模型

面试官问:"Redis 是单线程还是多线程的?"

小陈说:"单线程的。"

面试官追问:"为什么单线程的 Redis 这么快?"

小陈说:"因为在内存里操作。"

面试官继续追问:"那 I/O 多路复用是什么?Redis 是怎么处理多个客户端连接的?"

小陈开始支支吾吾。

Redis 的单线程 + I/O 多路复用是 Redis 高性能的核心原因之一。这道题能说清楚"单线程处理什么"和"多路复用如何工作"的候选人,对 Redis 的架构设计有较深的理解。

一、Redis 的线程模型 🔴

1.1 常见误解

误解 1: Redis 完全单线程

正确: Redis 的主流程是单线程,但有后台线程处理持久化、内存清理等

误解 2: 单线程 = 慢

正确: Redis 的操作是 CPU 密集型(在内存中),不是 IO 密集型
       单线程避免了锁竞争,反而更快

1.2 Redis 6.0 前的线程模型

                    ┌──────────────────────────────────────┐
                    │         主线程(Main Thread)         │
                    │                                       │
┌─────────┐         │  ┌────────┐   ┌────────┐   ┌────────┐ │
│ 客户端1  │─────────┼─→│  socket│→→│ AE     │→→│ 命令   │←─┤ 客户端2
│ 客户端2  │─────────┼─→│ accept │   │ 事件   │   │ 处理   │←─┤ 客户端3
│ 客户端3  │─────────┼─→│        │   │ 循环   │   │        │←─┤ 客户端4
└─────────┘         │  └────────┘   └────────┘   └────────┘ │
                    │                                       │
                    │  I/O 多路复用(select/poll/epoll)    │
                    └──────────────────────────────────────┘

核心组件

  1. I/O 多路复用:监听多个 socket 的可读/可写事件
  2. 文件事件分派器:将就绪的 socket 分派给对应处理器
  3. 命令请求处理器:读取 socket 数据,解析命令,执行命令
  4. 命令回复处理器:将执行结果写回 socket

1.3 ❌ 错误示范

候选人原话:"Redis 单线程,所以只能用 1 个 CPU 核心。"

问题诊断:部分正确。Redis 主线程确实只能用 1 个核心,但 Redis 的后台进程(BIO)会用其他核心,且现代 Redis 支持多线程 IO。

候选人原话 2:"Redis 快是因为用了 epoll。"

问题诊断:不精确。epoll 是 Linux 的 I/O 多路复用机制,Redis 用它来监听多个客户端连接,但 Redis 快的主要原因是在内存中操作数据(O(1) 或 O(log n)),而不是 epoll。

【面试官心理】 这道题我会从 I/O 多路复用的原理切入。如果候选人能说出"一个线程监听多个 socket,哪个 socket 就绪就读哪个",说明他理解了这套机制的核心思想。

二、I/O 多路复用原理 🔴

2.1 三种 I/O 模型对比

模型原理缺点
select轮询检查所有 FDFD 数量限制(1024)、O(n) 扫描
poll链表存储 FD无数量限制但仍是 O(n) 扫描
epoll红黑树管理 FD,只返回就绪的只能在 Linux 上用

2.2 epoll 的工作原理

// epoll 三板斧:
// 1. epoll_create: 创建 epoll 实例(红黑树)
int epfd = epoll_create(1);

// 2. epoll_ctl: 注册 socket 到 epoll 实例
struct epoll_event ev;
ev.events = EPOLLIN;  // 监听读事件
ev.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);

// 3. epoll_wait: 等待事件就绪
struct epoll_event events[1024];
int nfds = epoll_wait(epfd, events, 1024, -1);
// 返回就绪的 socket 列表(不需要遍历所有 FD)

2.3 Redis 的事件循环

// Redis 主事件循环(aeMain)
void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        // 处理所有已触发的事件
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}

// 事件类型:
// - 文件事件:socket 可读/可写
// - 时间事件:定时任务(如 serverCron)

三、命令执行流程 🔴

3.1 完整的请求处理流程

// 1. 客户端发送命令
//    SET name Tom\r\n

// 2. I/O 多路复用监听到 socket 可读

// 3. 主线程读取命令
//    read(sockfd, buf, BUF_SIZE)

// 4. 命令解析
//    - 读取协议数据
//    - 解析命令参数 argv[], argc

// 5. 命令执行
//    - 查找命令(dict 命令表)
//    - 执行命令(command->proc())

// 6. 结果写回
//    - write(sockfd, result, len)

3.2 为什么单线程快?

-- Redis 快的三个原因:
-- 1. 内存操作(O(1) 或 O(log n))
--    GET name → 内存 hash table 查找 → 1 次内存访问

-- 2. 非阻塞 I/O(epoll)
--    单线程同时处理多个客户端连接

-- 3. 无锁竞争
--    单线程执行,不需要加锁

-- 对比:MySQL 查询需要磁盘 IO(毫秒级)
-- Redis 操作在内存(纳秒级)
-- 差距:100,000 倍

四、Redis 6.0 多线程 IO 🟡

4.1 多线程 IO 的引入

Redis 6.0 之前:
┌────────────────────────────────────┐
│ 单线程:读命令 → 执行 → 写回        │
└────────────────────────────────────┘

Redis 6.0+(可选):
┌────────────────────────────────────┐
│ 主线程:协调 + 执行命令              │
│ IO 线程:读命令(并行)、写回(并行) │
└────────────────────────────────────┘

4.2 配置方式

# 开启多线程 IO(默认关闭)
io-threads-do-reads yes

# 设置线程数(通常 = CPU 核心数)
io-threads 4

# 注意:
# - 命令执行仍在主线程
# - 只有 IO 读取和响应写回是多线程
# - 线程数过多反而降低性能

4.3 性能提升

-- 单线程 IO:
-- 瓶颈在单线程处理大量 socket 的读取/写入

-- 多线程 IO:
-- 读取:多个 IO 线程并行读取多个 socket
-- 执行:主线程执行(保持顺序)
-- 写入:多个 IO 线程并行写回
-- 提升:QPS 可提升 50%~100%(高并发场景)

【面试官心理】 Redis 6.0 的多线程 IO 是 Redis 演进中的重要特性。能说清楚"多线程 IO 只是读取/写回多线程,执行还是单线程"的候选人,说明他对 Redis 的架构演进有持续关注。


级别考察重点期望回答
P5基本模型Redis 单线程 + I/O 多路复用
P6机制理解epoll 原理、事件循环、命令执行流程
P7演进理解Redis 6.0 多线程 IO、适用场景