Redis 线程模型:揭穿单线程神话

发布: (2025年12月25日 GMT+8 07:41)
7 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的具体文本内容,我将按照要求保留源链接并进行简体中文翻译。

Redis 是 “单线程”。还是不是?

我在约 5 年前构建 EventStreamMonitor 项目时学到的东西。

TL;DR

  • 命令执行SETGET 等核心工作)在 单个线程 中运行。
  • 其他所有工作——磁盘 I/O、惰性释放、网络 I/O、RDB 快照——都可以使用 后台线程
  • 这种设计选择赋予了 Redis 传奇般的速度、简洁性和可预测性。

1. 什么是单线程的?

区域会发生什么为什么保持单线程
命令执行运行实际的 Redis 命令(SETGETLPUSH,…)保证原子性,消除锁竞争。
主事件循环接受连接、解析请求、分发命令让整个服务器运行在单个核心上,避免上下文切换。
数据结构访问读取/写入内存对象(哈希、列表、有序集合,…)没有锁 → 没有竞争条件,提升 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. 为什么单线程实际上更

  1. 没有锁的开销

    • 非竞争锁大约消耗 ~100‑1 000 CPU 周期。
    • 竞争锁可能超过 >10 000 周期。
    • 通过避免锁,Redis 完全消除了这部分开销。
  2. 更好的 CPU 缓存使用

    • L1 缓存访问 ≈ 1 ns,而主存 60‑100 ns。
    • 单线程可以让热点数据在缓存中停留更久,降低内存延迟。
  3. 没有上下文切换

    • 切换线程会迫使操作系统保存/恢复寄存器、栈等。
    • 单线程持续运行,避免了该开销。

4. “Redis 不会出现竞争条件” – 到底什么是真相?

  • 单个命令是原子的。
    单个 SETGETINCR 等会在任何其他命令执行之前完整完成。

  • 一系列命令不是原子的。
    如果你使用 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 = 87.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 RAM39 M QPS,延迟

关键洞察: CPU 很少成为 Redis 的瓶颈;通常是内存带宽或网络 I/O。单线程已经足以让 CPU 饱和,因此为命令执行增加更多线程的收益递减。

7. 收获(我学到的)

  1. Command execution stays single‑threaded – intentional, not a limitation.
    命令执行保持单线程 – 这是有意为之,而非限制。

  2. Background threads exist for disk I/O, lazy freeing, and (since 6.0) network I/O.
    后台线程存在,用于磁盘 I/O、惰性释放,以及(自 6.0 起)网络 I/O。

  3. Atomicity = per‑command, not per‑transaction unless you use MULTI/EXEC.
    原子性 = 每个命令,除非使用 MULTI/EXEC,否则不是每个事务。

  4. Blocking commands block only the client, not the whole server.
    阻塞命令仅阻塞客户端,而不是整个服务器。

  5. Performance is already massive; most applications never need more than a few million ops/s per instance.
    性能已经非常强大;大多数应用每实例所需的操作数永远不会超过几百万 ops/s。

  6. 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
Back to Blog

相关文章

阅读更多 »

C# Minimal API:输出缓存

Minimal API:输出缓存 将生成的响应存储在服务器上,并直接提供,而无需重新执行端点。Microsoft Docs https://learn.m...

Go 的秘密生活:原子操作

第10章:The Indivisible Moment 星期二早晨,哈德逊河吹来刺骨的寒风,摇晃着图书馆档案馆的旧式平开窗。Ethan…