众所周知,Redis 在内存数据库领域内,可谓是独领风骚,应用非常广泛。这主要得益于其丰富的数据类型和极高的性能。
我们可能也听说了,Redis 是单线程的,并且在面试中也会经常被问到 “为什么单线程的 Redis 性能这么快?”,这篇文章我们就聊聊此问题。
首先,我们需要先领清楚一个事实,我们通常说的 Redis 是单线程,主要是指它的网络请求和执行命令的流程是单线处理的, 而整个 Redis Server 是多线程的 。比如持久化、lazyfree、集群数据同步等都是额外的线程处理的。
所以,严格来说,Redis 并不是单线程,但是我们一般把 Redis 称为单线程高性能。接下来,我也会把 Redis 称为单线程模式。而且,这也会促使你紧接着提问:“为什么用单线程?为什么单线程能这么快?”
为什么使用单线程
官方FAQ表示,因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。
本想着会有很高大上的技术,没想到就是一句官方看似糊弄我们的回答!但是,我们已经可以很清楚的解释了为什么Redis这么快,并且正是由于在单线程模式的情况下已经很快了,就没有必要在使用多线程了!
基于内存的操作
Redis 是一个 Key-value 内存数据库,它内部构是一个哈希表,根据指定的 Key 访问时,计算出 Key 的 hash 值后,只需要 O(1) 的时间复杂度就可以快速的定位到对应的数据。同时,Redis 提供了丰富的数据类型,并且其底层也是用了高效的 哈希表、跳表等高性能的数据结构,同时也使用高效的操作方式进行操作,这些操作都在内存中进行,并不会大量消耗 CPU 资源,所以速度极快。
IO多路复用技术
Linux 中的 IO 多路复用机制是指一个线程处理多个 IO 流,也就是 我们经常听到的 select / epoll 机制。Redis 网络框架调用 epoll 机制,让内核监听这些套接字。此时,Redis 线程不会阻塞在某一个特定的监听或已连接套接字上,也就是说,不会阻塞在某一个特定的客户端请求处理上。正因为此,Redis 可以同时和多个客户端连接并处理请求,从而提升并发性。
同时,Redis 利用了 IO 多路复用技术的事件驱动模型,这样,Redis 无需一直轮询是否有请求实际发生,这就可以避免造成 CPU 资源浪费。同时,Redis 在对事件队列中的事件进行处理时,会调用相应的处理函数,这就实现了基于事件的回调。因为 Redis 一直在对事件队列进行处理,所以能及时响应客户端请求,提升 Redis 的响应性能。
单线程的优点
基于以上特性,Redis采用单线程已足够达到非常高的性能,所以 Redis 没有采用多线程模型。
另外,单线程模型还带了以下好处:
- 代码更清晰,处理逻辑更简单
- 不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
- 不存在多进程或者多线程导致的切换而导致的消耗
所以Redis正是基于以上这些方面,所以采用了单线程模型来完成请求处理的工作。
单线程的缺点
如果某个请求是比较耗时的操作,那么整个Redis就会阻塞住,其他请求也无法进来,直到这个耗时久的操作处理完成并返回,其他请求才能被处理到。
无法发挥多核 CPU 性能,不过可以通过在单机开多个Redis实例来完善
多线程优化
- Redis4.0 之后,Redis引入了
lazyfree
的机制,提供了unlink
、flushall aysc
、flushdb async
等命令和lazyfree-lazy-eviction
、lazyfree-lazy-expire
等机制来异步释放内存,它主要是为了解决在释放大内存数据导致整个redis阻塞的性能问题。 - Redis 6.0 又引入了多线程来完成请求数据的协议解析,进一步提升性能。它主要是解决高并发场景下,单线程解析请求数据协议带来的压力。请求数据的协议解析由多线程完成之后,后面的请求处理阶段依旧还是单线程排队处理。
总结
Redis是纯内存数据库,一般都是简单的存取操作,所有读写速度都很快
再说一下 IO,Redis使用的是非阻塞IO,IO多路复用。
Redis采用了单线程的模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争。
另外,数据结构也帮了不少忙,Redis全程使用 hash 结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。
还有一点,Redis采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。