第二篇:Redis 为什么选择单线程?性能为什么还能这么高?

第二篇:Redis 为什么选择单线程?性能为什么还能这么高?
Redis 为什么选择单线程性能为什么还能这么高上一篇我们聊了《Redis 为什么这么快它真的只是因为内存吗》最后提到了 Redis 高性能的四个关键原因数据存储在内存单线程模型I/O 多路复用高效的数据结构其中**“单线程”**几乎是所有初学者都会疑惑的问题。现在 CPU 都是多核时代一个 Redis 为什么还坚持单线程它真的不会成为性能瓶颈吗今天我们就来把这个问题彻底讲明白。单线程其实是一个误解很多文章一上来就说Redis 是单线程。这句话并不严谨。准确地说Redis 的命令执行是单线程的而不是整个 Redis 只有一个线程。例如 Redis 内部还有很多后台线程负责AOF 刷盘Lazy Free 异步删除BIO 后台任务Redis 6 的网络 I/O 线程真正只有一个线程完成的是命令执行。也就是说不管有多少客户端连接真正修改数据的永远只有一个线程。这也是 Redis 整个设计的核心。如果改成多线程会发生什么假设现在有两个客户端同时修改同一个 Key。客户端 AINCR money客户端 BDECR money如果两个线程同时执行很容易出现数据竞争。例如线程A读取 money 100 线程B读取 money 100 线程A写入 101 线程B写入 99最后得到的结果可能是 99也可能是 101而正确答案应该还是 100。为了避免这种情况就必须加锁。例如Lock ↓ 读取数据 ↓ 修改数据 ↓ 释放锁或者使用 CAS、自旋锁、内存屏障等各种同步机制。问题来了。这些同步操作本身也是需要 CPU 时间的。很多时候锁的开销甚至比真正执行一条 Redis 命令还大。Redis 选择了另一条路Redis 没有解决如何让多个线程安全地修改数据。而是直接避免了这个问题。它的思路非常简单所有命令排队一个一个执行。整个过程类似这样客户端1 ─┐ 客户端2 ─┼──► EventLoop ─► 执行命令 客户端3 ─┘任何时刻都只有一个线程真正执行命令。于是天然就没有锁竞争数据竞争死锁CAS 重试线程切换整个数据库的数据结构也因此天然线程安全。这就是 Redis 单线程最大的优势。可是一个线程不会很慢吗很多人觉得一个线程肯定没有八个线程快。其实这是一个很常见的误区。判断一个程序快不快首先要知道它到底在消耗什么。对于 Redis 来说大部分命令都是这种复杂度GET O(1) SET O(1) HGET O(1) LPUSH O(1)例如执行一条 GET。底层其实就是查 Hash 表 ↓ 找到 Key ↓ 返回 Value整个过程非常短。真正消耗时间的很多时候反而不是计算而是网络收发系统调用内存访问也就是说Redis 根本不是一个 CPU 密集型程序。既然 CPU 都没有忙满再增加多个计算线程收益自然有限。Redis 真正快在哪里很多人把 Redis 的性能全部归功于单线程。其实不是。单线程只是其中一个原因。真正决定 Redis 性能的是下面几个设计共同作用。第一所有数据都在内存这是最基础的一点。传统数据库CPU ↓ 磁盘 IO ↓ 返回数据RedisCPU ↓ Memory ↓ 返回数据省去了机械磁盘或者 SSD访问带来的大量等待时间。第二没有锁竞争由于命令串行执行。整个执行过程中不需要synchronizedMutexCASReadWriteLockCPU 可以一直执行真正的业务逻辑而不是浪费时间做线程同步。第三一个线程管理成千上万个连接很多人看到单线程就误以为一个连接对应一个线程。其实 Redis 从来不是这样工作的。它使用的是I/O 多路复用。整个模型更像这样大量客户端 │ ▼ epoll │ ▼ EventLoop │ ▼ 顺序执行命令一个线程就可以同时监听几万个甚至几十万个 Socket。只有真正有数据到来时Redis 才会去处理。因此并不会因为连接数增加就创建大量线程。这也是 Redis 能支撑高并发连接的重要原因。至于 I/O 多路复用到底是怎么做到的我们下一篇会继续展开。第四高效的数据结构Redis 内部并不是直接使用 Java 那样的标准数据结构。例如String 使用 SDSList 使用 QuickListZSet 使用 SkipListHash 使用不同 Encoding 自动切换这些都是针对性能专门优化过的实现。后面的几篇文章我们会分别介绍它们为什么这样设计。Redis 6 为什么又加入了多线程很多人看到这里又会产生新的疑问。既然单线程这么好为什么 Redis 6 又加入了多线程答案很简单。Redis 发现随着网络越来越快真正的瓶颈已经不是命令执行而是网络 I/O。例如读取 Socket协议解析返回数据这些工作其实可以提前交给多个线程完成。因此 Redis 6 引入了 I/O Threads。注意这里很多人又理解错了。Redis 并没有把命令执行改成多线程。真正执行命令的仍然只有主线程。多线程负责的是读取网络数据 ↓ 协议解析 ↓ 主线程执行命令 ↓ 回写网络数据因此Redis 既利用了多核 CPU又没有破坏单线程执行命令这一核心设计。为什么直到今天Redis 仍然坚持单线程执行命令原因其实很简单。对于 Redis 来说大多数命令执行时间非常短。如果为了让它支持多线程执行而引入大量锁、同步机制和复杂的数据一致性控制带来的收益远远小于付出的代价。换句话说。Redis 并不是做不到多线程而是没有必要。它把最需要并发的地方——网络 I/O——交给多线程把最需要保证一致性的地方——命令执行——继续保持单线程。这正是 Redis 最经典的设计思想。总结很多人认为Redis 快因为它是单线程。也有人认为Redis 快因为数据都在内存。这两种说法都不完整。Redis 的高性能是多个设计共同作用的结果数据存储在内存减少磁盘 I/O。命令串行执行避免锁竞争。I/O 多路复用一个线程管理大量连接。高效的数据结构让每条命令都尽可能接近 O(1)。所以Redis 选择单线程不是技术做不到而是经过大量工程权衡后的最佳方案。上一篇《Redis 为什么这么快它真的只是因为内存吗》下一篇《Redis 为什么只有五种数据类型却能支撑几乎所有业务》如果这篇文章对你有帮助欢迎点赞、收藏。你觉得 Redis 如果把命令执行也改成多线程性能一定会更高吗欢迎把你的想法写在评论区我们下一篇继续揭秘 Redis 五种数据类型背后的设计思想。