作为内存型数据库,redis因其性能和速度广为人知。
但是,Redis内部采用了单线程架构。
为什么这样一个单线程的设计会又这么高的性能。如果采用多线程并发处理请求,性能不会更好嘛?
长话短说
Redis的高性能归因于四个主要因素:
- 基于内存的数据存储
- 优化的数据结构
- 单线程架构
- NIO(非阻塞IO)
基于内存的数据存储
存储类型 | 速度 | 放大比较 |
Register | 0.3ns | 3s |
L1 Cache | 0.9ns | 9s |
L2 Cache | 2.8ns | 28s |
L3 Cache | 12.9ns | 2mins 9s |
RAM | 120ns | 20mins |
SSD | 150us | 17.35days |
HDD | 10ms | 115.74days |
Redis是基于内存的KV数据库。
Redis中每次读取和写入操作都相当于直接读取内存中的数据。
直接访问内存的速度比访问磁盘快好几个数量级,因此,基于内存这一特性,使得Redis比其他数据库更快。
优化的数据结构
作为一个基于内存的数据库,Redis使用了各种底层数据结构来高效存储数据,而不用担心如何将它们保存到持久存储中。
比如,Redis试用链表实现list类型,在链表首位插入和删除只需要O(1)的时间复杂度。另一方面,Redis他的sorted set类型使用跳表(skip list)来实现,它提供了更快的查询和插入速度。
简而言之,由于不需要担心持久化数据,Redis中的数据能够通过不同的数据结构进行快速检索
Single-Threaded
Redis的读写操作是非常快的,而且Redis不需要关心CPU的使用率。
根据Redis官方文档,在普通 Linux 系统上运行时,Redis 每秒可以传递多达 100 万个请求。
然而,瓶颈来自于网络I/O。Redis的处理时间大多浪费在等待网络I/O上。
尽管多线程架构可以通过切换上下文让应用并发处理任务,但是Redis的性能提升很小,因为大多数线程最终会在 I/O 中被阻塞。
通过采用一个单线程架构,Redis有以下好处:
- 最小化由于线程创建或销毁引起的 CPU 消耗
- 最大限度地减少由于上下文切换引起的 CPU 消耗
- 减少锁开销(lock overhead),因为多线程应用程序需要锁来进行线程同步,这很容易出错
- 能够使用各种“线程不安全”命令,例如 Lpush
NIO(非阻塞I/O)
为了处理传入请求,服务器需要在套接字(socket)上调用系统调用来读取网络缓冲区上的字节到用户空间。
这通常是一个阻塞操作,线程被阻塞并且在完全接收到来自客户端的数据之前什么都做不了。
为什么我们不在确定套接字中的数据已经准备好被读取时才调用系统调用呢?
这就是 I/O 多路复用发挥作用的地方
I/O多路复用模块同时监控多个套接字(socket),仅返回那些已经可读的套接字(socket)
准备好读取的套接字被推送到单线程事件循环中,并由相应的处理程序使用 Reactor Pattern 进行处理
简而言之,
- 由于网络I/O的阻塞性,其速度很慢
- Redis内存操作速度快,Redis收到命令后可以快速执行
因此,Redis 有意识地做出以下决定
- 使用 I/O 多路复用来缓解缓慢的网络 I/O 问题
- 使用单线程架构减少锁开销