Redis 线程模型:揭穿单线程神话
Source: Dev.to
请提供您希望翻译的具体文本内容,我将按照要求保留源链接并进行简体中文翻译。
Redis 是 “单线程”。还是不是?
我在约 5 年前构建 EventStreamMonitor 项目时学到的东西。
TL;DR
- 命令执行(
SET、GET等核心工作)在 单个线程 中运行。 - 其他所有工作——磁盘 I/O、惰性释放、网络 I/O、RDB 快照——都可以使用 后台线程。
- 这种设计选择赋予了 Redis 传奇般的速度、简洁性和可预测性。
1. 什么是单线程的?
| 区域 | 会发生什么 | 为什么保持单线程 |
|---|---|---|
| 命令执行 | 运行实际的 Redis 命令(SET、GET、LPUSH,…) | 保证原子性,消除锁竞争。 |
| 主事件循环 | 接受连接、解析请求、分发命令 | 让整个服务器运行在单个核心上,避免上下文切换。 |
| 数据结构访问 | 读取/写入内存对象(哈希、列表、有序集合,…) | 没有锁 → 没有竞争条件,提升 CPU 缓存局部性。 |
2. 什么是多线程?
| 子系统 | 线程使用情况 | 起始版本 |
|---|---|---|
| 磁盘后台 I/O(fsync,关闭文件) | 专用 “bio” 线程 | 早期 Redis 版本(via bio.c) |
| 惰性释放(内存回收) | 后台线程释放大对象 | Redis 4.0 |
| 网络 I/O(socket 读写) | 可选 I/O 线程池 | Redis 6.0 |
| RDB 快照(fork‑based 备份) | 子进程完成主要工作;父进程可使用后台线程进行清理 | 始终(fork)+ 后台 I/O |
结论: Redis 在最关键的部分(命令执行)上大多数是单线程的,但在能带来明显收益的地方会启动线程。
3. 为什么单线程实际上更快
-
没有锁的开销
- 非竞争锁大约消耗 ~100‑1 000 CPU 周期。
- 竞争锁可能超过 >10 000 周期。
- 通过避免锁,Redis 完全消除了这部分开销。
-
更好的 CPU 缓存使用
- L1 缓存访问 ≈ 1 ns,而主存 60‑100 ns。
- 单线程可以让热点数据在缓存中停留更久,降低内存延迟。
-
没有上下文切换
- 切换线程会迫使操作系统保存/恢复寄存器、栈等。
- 单线程持续运行,避免了该开销。
4. “Redis 不会出现竞争条件” – 到底什么是真相?
-
单个命令是原子的。
单个SET、GET、INCR等会在任何其他命令执行之前完整完成。 -
一系列命令不是原子的。
如果你使用MULTI/EXEC,或者仅仅连续执行多个命令,其他客户端的命令可能会在它们之间交叉执行。官方文档对此有警告。 -
阻塞命令(例如
BLPOP)不会冻结服务器。
它们只会阻塞发出该命令的客户端连接;事件循环仍会继续为其他客户端提供服务。
5. 为什么不把 所有 都做多线程?
“执行
LPUSH的线程需要为执行LPOP的其他线程提供服务。收益有限,却要增加大量复杂性。” – Antirez(Redis 创始人)
- Redis 数据结构(列表、集合、有序集合、流)需要细粒度锁定。
- 哈希再散列、键过期和驱逐等操作将变得更加复杂且容易出错。
- 单线程核心消除了整类 bug,并保持代码库可维护。
6. 基准测试与真实世界数据
| 基准测试 | 配置 | 吞吐量 |
|---|---|---|
| 流水线 SET | 单实例,无 I/O 线程 | 1.5 M+ ops/s |
| 流水线 GET | 同上 | 1.8 M+ ops/s |
| Redis 8.0(带 I/O 线程) | io-threads = 8 | 7.4 M ops/s |
| Redis 6.0 I/O 线程 | 8 线程 | 提升 37 %–112 %(相较于单线程) |
| AWS ElastiCache 7.1 | 生产节点 | >1 M req/s |
| Twitter(生产环境) | 10 k+ 实例,105 TB RAM | 39 M QPS,延迟 — |
关键洞察: CPU 很少成为 Redis 的瓶颈;通常是内存带宽或网络 I/O。单线程已经足以让 CPU 饱和,因此为命令执行增加更多线程的收益递减。
7. 收获(我学到的)
-
Command execution stays single‑threaded – intentional, not a limitation.
命令执行保持单线程 – 这是有意为之,而非限制。 -
Background threads exist for disk I/O, lazy freeing, and (since 6.0) network I/O.
后台线程存在,用于磁盘 I/O、惰性释放,以及(自 6.0 起)网络 I/O。 -
Atomicity = per‑command, not per‑transaction unless you use
MULTI/EXEC.
原子性 = 每个命令,除非使用MULTI/EXEC,否则不是每个事务。 -
Blocking commands block only the client, not the whole server.
阻塞命令仅阻塞客户端,而不是整个服务器。 -
Performance is already massive; most applications never need more than a few million ops/s per instance.
性能已经非常强大;大多数应用每实例所需的操作数永远不会超过几百万 ops/s。 -
Design simplicity = reliability – fewer bugs, easier maintenance, predictable latency.
设计简洁 = 可靠性 – 更少的 bug,更易维护,可预测的延迟。
8. 进一步阅读
- Redis 官方文档 – https://redis.io/documentation
- 了解后端服务中的连接和线程 – 关于线程模型的完整指南。
- 我遇到的 6 大常见 Redis 与 Kafka 挑战 – 来自 EventStreamMonitor 项目的真实挑战。
- EventStreamMonitor – 我的开源监控平台(如果公开则提供链接)。
希望这能澄清“单线程 vs 多线程”的困惑,并帮助您设计更好的缓存策略!
# ect – My microservices monitoring platform using Redis and Kafka