Redis 为什么快?“单线程”解释远远不够
Redis 快,不是因为“单线程天然比多线程快”。单线程只是让命令执行路径避免了大量共享数据竞争;真正的性能来自内存数据集、高效数据结构、事件驱动网络模型和对慢操作的主动规避。忽略这些条件,照样能把 Redis 用成系统瓶颈。
1. 先澄清:Redis 并非所有工作都单线程
通常所说的单线程,是指核心命令在主线程上按顺序执行。Redis 还会使用后台线程或子进程处理持久化、异步释放、集群任务;新版本也支持 I/O 线程分担网络读写。命令对数据的修改仍保持清晰的串行语义,客户端无需为单条命令中的数据竞争加锁。
好处包括:
- 避免锁竞争、死锁和大量上下文切换;
- 命令原子性容易理解;
- 数据结构实现可针对串行访问优化;
- 延迟更可预测。
代价也很明确:一个耗时命令就可能阻塞后续所有请求。
2. 数据主要在内存中
内存随机访问比磁盘 I/O 快得多。Redis 的读写通常操作内存数据结构,持久化由 RDB/AOF 在另一条路径完成。但“在内存”不等于永远微秒级:缺页、内存换页、fork 写时复制、NUMA、过大的对象和网络排队都会抬高延迟。
生产机器应避免 swap 压力,并为操作系统、fork 与复制留出内存余量。used_memory 不是进程全部 RSS,也不等于容器所需上限。
3. I/O 多路复用解决大量连接
Redis 使用事件循环监听多个 socket 的可读、可写事件。一个线程不必为每个连接阻塞等待,而是在连接就绪时执行对应处理。这让它能以较低线程调度成本服务大量连接。
socket 就绪 → 解析协议 → 执行命令 → 写入响应
↑
核心命令串行
因此连接多不一定可怕,“同时到达的大量重命令”才危险。客户端还要使用连接池、合理超时和背压,不能无限建立连接或积压请求。
4. 为场景定制的数据结构
Redis 并非所有 String 都是普通字符数组,也不是所有 Hash 都用同一种结构。它会根据元素数量、长度和配置在紧凑编码与通用结构之间转换,以平衡内存和操作复杂度。
- 字符串使用 SDS,保存长度并预留安全的二进制能力;
- Hash、ZSet 等小对象可使用紧凑编码;
- 跳表让有序集合范围查询保持良好复杂度;
- 渐进式 rehash 避免一次性迁移整个哈希表;
- 位图、HyperLogLog 等用紧凑表示解决特定问题。
复杂度仍然重要。GET 很快,不代表对百万成员集合执行全量操作也快。
5. 批处理减少网络往返
许多应用瓶颈不是服务器计算,而是 RTT。依次发送 1000 个 GET 需要大量往返;Pipeline 可以连续发送后批量读取响应。
try (Jedis jedis = pool.getResource()) {
Pipeline p = jedis.pipelined();
for (String key : keys) p.get(key);
List<Object> values = p.syncAndReturnAll();
}
Pipeline 不是事务,也不会让多条命令原子执行。批次过大还会占用客户端与服务端缓冲区,造成队头阻塞。通常应分批并压测确定大小。
Lua 脚本可把多个步骤放到服务端原子执行,却也在主执行路径上运行。脚本必须短小、确定,禁止遍历无界数据。
6. 哪些操作会破坏“快”
- 对大集合执行
SMEMBERS、HGETALL; - 在线上使用
KEYS *; - 删除巨大 Key,释放内存耗时;
- Lua 脚本做复杂循环;
- 大 value 导致网络、复制和持久化放大;
- AOF 重写或 RDB fork 期间内存压力过高;
- 热 Key 把所有流量压到单个分片;
- 客户端慢读导致输出缓冲增长。
优先用 SCAN 家族做增量遍历,用 UNLINK 异步释放大对象,并设置命令超时、慢日志阈值和输出缓冲限制。
7. 如何建立性能证据
关注:
redis-cli --latency
redis-cli --latency-history
redis-cli slowlog get 20
redis-cli info commandstats
redis-cli info memory
还应采集实例 CPU、网络吞吐、命中率、evicted_keys、客户端连接数和 p99 延迟。基准测试要使用接近生产的 value 大小、Pipeline 批次、持久化策略与集群拓扑。
8. 检查清单
- 是否把“核心命令单线程”误写成 Redis 完全单线程?
- 是否存在 O(N) 命令作用于无界集合?
- 是否用 Pipeline 降低 RTT,并限制批次?
- 是否监控慢日志、热 Key、大 Key 和客户端缓冲?
- 是否为持久化、复制与 fork 留出内存和 I/O 余量?
Redis 的快是一组工程选择共同作用的结果。保持它快的关键,是让每条命令短小、数据有界、网络往返可控,并让慢工作离开主执行路径。