之前被面试官问redis为什么是单线程的、脑子一片空白?首先想到的答案是没有必要、因为redis基本都是操作内存的、效率很快、没必要开启多线程、显然这样的答案比较肤浅,回家赶紧恶补一下相关知识点。
Redis 并不是 CPU 密集型的服务
redis是单线程的原因在于redis用单个CPU绑定一块内存的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的。redis核心就是 如果我的数据全都在内存里,我单线程的去操作就是效率最高的。所以,redis是单线程。
一、 Redis为什么那么快
1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
3、采用单线程,保证操作的原子性,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路I/O复用模型,非阻塞IO;
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
以上几点都比较好理解,下边针对多路 I/O 复用模型进行简单的探讨:
(1)多路 I/O 复用模型
多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。
这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。
二、为什么Redis是单线程的
首先要明白,上边的种种分析,都是为了营造一个Redis很快的氛围。官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。
这里我们一直在强调的单线程,只是在处理我们的网络请求的时候只有一个线程来处理,一个正式的Redis Server运行的时候肯定是不止一个线程的,这里需要大家明确的注意一下。例如Redis进行持久化的时候会以子进程或者子线程的方式执行。
三、Redis多线程版本、以及多线程操作
其实最新版本也支持了多线程、面试官的说法也不严谨
Redis在版本5.0中引入了多线程的支持。在此之前,Redis是单线程运行的,这意味着Redis无法充分利用多核处理器的性能优势。为了提高Redis的并发处理能力,Redis引入了多线程技术,从而在一定程度上提高了Redis的性能
Redis作为一个基于内存的缓存系统,一直以高性能著称,因没有上下文切换以及无锁操作,即使在单线程处理情况下,读速度仍可达到11万次/s,写速度达到8.1万次/s。但是,单线程的设计也给Redis带来一些问题:
- 只能使用CPU一个核;
- 如果删除的键过大(比如Set类型中有上百万个对象),会导致服务端阻塞好几秒;
- QPS难再提高。
在Redis中,删除大键可能会导致阻塞,特别是当键的大小超过了服务器的处理能力时。为了避免删除大键时阻塞Redis服务器,可以使用UNLINK
命令来异步删除键。
UNLINK
命令类似于DEL
命令,但它是异步执行的,这意味着Redis会在后台删除给定的键,而不会阻塞当前的连接。
默认情况下,Redis是以单线程模式运行的,但是可以通过配置文件来开启多线程IO。
在redis.conf配置文件中,可以通过修改以下两个参数来开启多线程IO:
-
io-threads-do-reads yes:开启多线程读操作。
-
io-threads 4:设置线程数为4。
示例配置:
io-threads-do-reads yes
io-threads 4
在开启多线程IO后,Redis会使用多个线程来处理网络读写,但是仍然使用单个线程来处理实际的命令操作。
注意:在生产环境中,应当充分测试多线程IO模式对性能的影响,因为启用该特性可能会影响到Redis的性能和稳定性。