Redis 是单线程还是多线程?
结论:命令执行是单线程的,网络 IO 与部分后台任务是多线程的。Redis 6.0 起引入 IO 多线程只分担 socket 读写与协议解析,命令仍在主线程串行执行;4.0 起引入后台线程处理慢删除与 AOF fsync。所以准确说法是"核心命令单线程,周边多线程"。
一、哪些线程在干活
| 线程/机制 | 引入版本 | 职责 | 是否影响命令原子性 |
|---|---|---|---|
| 主线程 | 始终 | 解析命令、执行命令、维护数据结构 | 是,唯一执行者 |
| IO 线程(io-threads) | 6.0 | 读 socket、解析请求、写回响应 | 否,命令仍交主线程 |
| bio close file | 4.0 | 异步关闭大文件 fd、释放对象 | 否,UNLINK/FLUSHASYNC 后台做 |
| bio AOF fsync | 4.0 | everysec 策略下后台刷盘 | 否,最多丢 1 秒 |
| bio lazy free | 4.0 | 大 key 惰性释放内存 | 否,释放与命令解耦 |
| 集群总线线程 | 3.0 | Cluster gossip 通信 | 否,独立端口 |
二、6.0 的 IO 多线程到底多在哪
6.0 之前(单线程全包) 6.0 之后(IO 多线程)
┌──────────────────────────┐ ┌──────────────────────────────┐
│ 主线程:epoll_wait │ │ 主线程:epoll_wait,分发任务 │
│ read → 解析 → 执行 │ │ ├─ IO 线程组:read + 解析 │
│ → 写回 │ │ ├─ 主线程:执行命令(串行) │
└──────────────────────────┘ │ └─ IO 线程组:写回响应 │
└──────────────────────────────┘
关键限制:同一时刻只有一个线程在执行命令,因此不需要给字典、跳表加锁,Lua、MULTI、INCR 的原子性依旧成立。多线程只处理"一次请求中与内存数据结构无关"的部分,收益出现在网络带宽打满、单核跑满而 CPU 有富余的场景。
# redis.conf:默认关闭,按机器核数谨慎开启
io-threads 4
io-threads-do-reads yes
经验值:io-threads 不超过 8,且小于等于物理核数;4 核机器开 23 个即可。开太多反而因为线程调度与锁竞争掉性能,官方压测显示 68 线程后收益趋于平坦。
三、验证方式
# 查看 IO 线程配置与运行状态
redis-cli CONFIG GET io-threads
redis-cli INFO server | grep -E "redis_version|io_threads_active"
redis-cli INFO commandstats | head -20
# 观察主线程是否被慢命令拖住(latency 监控)
redis-cli --latency -h 127.0.0.1
redis-cli SLOWLOG GET 10
# 单线程模型下必须避免的阻塞命令
KEYS * # O(N),全库扫描
FLUSHALL # 同步释放,用 FLUSHALL ASYNC
HGETALL big_hash # 大 key 返回,网络与内存双杀
DEL big_key # 同步释放,用 UNLINK
四、单机撑不住时的扩展顺序
| 手段 | 成本 | 收益 | 注意事项 |
|---|---|---|---|
| 加 Pipeline / 批量命令 | 低 | RTT 从 N 次降到 1 次 | 单次批量别过大,避免内存峰值 |
| 加本地缓存挡住热点 | 中 | 减少 80% 以上读请求 | 需处理失效广播 |
| 读写分离(主写从读) | 中 | 读能力线性扩展 | 从库有延迟,读到旧数据 |
| 主从 + 多分片 | 高 | 容量与 QPS 同时扩展 | 跨分片事务与批量操作受限 |
| IO 多线程 | 低 | 网络密集型场景提升 30%~100% | 核数不足时无效 |
扩展的优先级是"先省请求、再加副本、最后才分片",因为分片会带来跨槽操作、事务与运维复杂度,代价最高。
五、常见追问
- 问:单线程怎么处理成千上万个连接?答:epoll 多路复用 + 非阻塞 IO,一个线程轮询就绪事件,不为每个连接创建线程,避免了上下文切换与内存开销。
- 问:既然单线程,为什么还需要分布式锁?答:单线程保证"单条命令原子",但"读取-判断-写回"跨多条命令仍需 Lua、WATCH 或分布式锁才能原子。
- 问:6.0 为什么不把命令执行也改成多线程?答:多线程执行要为核心数据结构加锁,锁竞争与复杂度会吃掉收益;真正的扩展手段是分实例、Cluster 分片,而不是单实例多线程。
- 问:Redis 7.0 有什么线程相关变化?答:引入多线程处理 AOF rewrite 的 IO、以及 Function 与 ACL 的增强,但命令执行模型未变。
- 问:单线程会不会成为瓶颈?答:会。单实例通常 8~10 万 QPS 量级,热点 key 或大 key 会显著拉低;解决方向是拆分 key、加本地缓存、横向扩分片。
小结
- 记住一句话:命令单线程、IO 可多线程、后台任务另有线程。
- 单线程换来无锁与原子性,代价是单核上限,扩展靠多实例与 Cluster。
- 生产上真正拖慢 Redis 的往往不是"单线程",而是大 key、O(N) 命令和集中过期。