跳到正文

Redis 线程模型 ​

一句话:Redis 用单线程串行执行命令,换掉锁竞争和缓存失效;网络读写交给 epoll,必要时再加 I/O 线程。代价是一条慢命令会阻塞所有客户端。

适用版本:Redis 7.x / 8.x。6.0 引入 I/O 多线程,7.x 默认仍是 io-threads 1(不启用)。

为什么需要 ​

Redis 的命令执行本质是内存访问:一次哈希查找约 100 ns,改一个整数更快。真正花时间的是网络:TCP 协议栈、系统调用、内核态与用户态的数据拷贝、RESP 协议解析,单次请求常在几十到几百微秒。

如果为了压榨这 100 ns 去加线程,就要给所有数据结构加锁,一次锁竞争或上下文切换约 1 µs 起步,比操作本身贵一个数量级。所以 Redis 的选择是:命令执行保持单线程,把「网」这部分能并行的部分拆出去。

单线程还有一个隐性收益:热点数据长期留在 L1/L2 缓存里。多线程反复切换会把缓存冲刷掉,十万 QPS 下这个差距会被放大。

核心机制 ​

事件循环:epoll + 单 Reactor ​

主线程只在 epoll_wait 上阻塞,就绪事件按类型分发;真正耗时的命令执行始终只有一条路径。

  • socket 设为非阻塞,epoll_wait 只返回真正有事件的 fd,避免遍历上万个连接。
  • Redis 用水平触发(LT, Level Trigger):只要缓冲区还有数据就继续通知,编程简单、不易丢事件;Nginx 常用边缘触发(ET)。
  • 所有命令仍在主线程串行执行,所以单条命令天然原子,不需要为命令加锁。

为什么命令执行不加线程 ​

成本项量级说明
一次内存操作~0.1 µs哈希查找、整数自增
一次锁竞争/上下文切换~1 µs 起高并发下更高
一次网络往返几十–几百 µs真正的瓶颈

给 0.1 µs 的操作加 1 µs 的锁,收益为负;而且多线程会让缓存局部性变差。这是「单线程」的真正理由,不是「作者懒得写」。

6.0 的 I/O 多线程:只并行网络读写 ​

I/O 线程负责 socket 读写和 RESP 协议解析,命令执行仍在主线程。因此它不会让一条慢命令变快,只能提高网络吞吐。

参数默认值(7.x)作用
io-threads11 表示不启用;设为 N 表示主线程 + N−1 个 I/O 线程
io-threads-do-readsno默认只并行写回;设为 yes 才并行读和协议解析

两个参数都是启动时配置,CONFIG SET 不支持。I/O 线程是惰性启用的:待处理客户端数量超过阈值(约 io-threads × 2)才真正并行,否则退回主线程。官方说明里,4 核机器建议 2–3 个 I/O 线程,8 核约 6 个,超过 8 个通常收益很小。

开启判据:先确认 CPU 时间花在 redis-server 的网络读写/协议解析上,而不是慢命令;且实例有至少 4 个可用核。压测对比 redis-benchmark 的 QPS 与 P99 后再决定。TLS 或 Unix socket 场景下 I/O 线程不生效(以官方文档为准)。

BIO 后台线程 ​

BIO 线程一直存在,专门处理会阻塞主线程的重活:close(fd)、AOF fsync、UNLINK 的惰性释放。它和 6.0 的 I/O 线程是两回事:BIO 处理「慢系统调用」,I/O 线程处理「网络读写」。

最小上手 ​

bash
docker run -d --name redis7 -p 6379:6379 redis:7.4
redis-cli

# 看当前配置(默认 1 / no)
CONFIG GET io-threads io-threads-do-reads

# 看命令耗时分布,确认瓶颈不是某条慢命令
INFO commandstats

# 看网络延迟与吞吐
redis-cli --latency
redis-benchmark -t set,get -n 100000 -c 50

启用 I/O 线程要改配置文件后重启:

conf
# redis.conf
io-threads 4
io-threads-do-reads yes

生产动作 ​

  • 先定位瓶颈:SLOWLOG GET 10 有慢命令时,加 I/O 线程没用,先解决 KEYS、大 Key DEL、大集合 HGETALL。
  • 线程数不超过核数:I/O 线程与主线程争抢 CPU;容器里要按 --cpus 限额算,不要按宿主机核数。
  • 读并行默认关闭:io-threads-do-reads yes 会增加延迟抖动,先用默认值压测,确有读瓶颈再开。
  • 减少系统调用:Pipeline 把多条命令合并一次发送,比加线程更直接地降低网络开销;单次 Pipeline 控制在 100–1000 条。
  • 关注 CPU 构成:INFO cpu 看 used_cpu_sys/used_cpu_user,INFO clients 看连接数;连接数暴涨往往比 CPU 先出问题。

边界 ​

  • 「Redis 是单线程」不准确:主线程之外还有 BIO 线程、可选 I/O 线程、fork 出的 RDB/AOF 子进程,以及模块自己的线程。
  • I/O 线程不解决慢命令:单条 O(N) 命令仍然阻塞所有人,扩容只能加实例或拆分数据。
  • 不要让单实例跑满 CPU:I/O 线程的并行度受主线程命令执行限制,接近饱和时加线程收益递减。
  • 命令执行顺序是全局的:多线程只改变网络读写,不改变命令语义;依赖命令顺序的逻辑仍然成立。

讲清楚 ​

Redis 快不是因为「单线程」这四个字,而是因为命令执行本来就是内存操作,真正贵的是网络。单线程让命令天然原子、没有锁竞争,热点数据还能留在 CPU 缓存里;6.0 加的 I/O 线程只并行 socket 读写和协议解析,命令执行仍然串行。

所以判断要不要开 I/O 线程,先看 CPU 是不是花在网络读写上、机器有没有多余核。如果是慢命令或大 Key 把主线程卡住,加线程没有用,得从命令和数据规模上解决。

把一门技术讲透到既懂也会用