相信在面试中会有不少人被问到:Redis是单线程吗?为什么单线程能这么快?
Redis是单线程吗?
首先 Redis 是单线程,主要是指 Redis 的网络IO和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值对存储服务的主要流程。但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,都是由额外的线程来执行的。所以 Redis 并不全部是单线程。
要理解 Redis 为什么用单线程,我们可以先来了解下多线程的开销。
“使用多线程,可以增加系统吞吐率”。确实,在合理的资源分配情况下,使用多线程能提升同时处理的请求数。但实际效果却是,当我们刚开始增加线程数的时候,系统吞吐率会增加;随着进一步增加线程,系统吞吐率就会增长缓慢,甚至出现下降的情况。
之所以会这样,是因为系统中通常会存在被多线程同时访问的共享资源。当多线程修改这个共享资源时,就需要有额外的机制来保证,因此就会带来额外的开销。并发访问控制是多线程中的难点,如果只是简单加互斥锁,即使增加了线程,大部分线程也是在等待获取访问共享资源的互斥锁,并行变串行,系统的吞吐率并没有随着线程的增加而增加。
单线程 Redis 为什么那么快?
一方面,redis是基于内存的,加上采用了高效的数据结构,例如哈希表和跳表。另一方面,redis采用了多路复用机制,使其在网络IO操作中能并发处理大量的客户端请求,实现高吞吐率。
基本的IO模型与阻塞点
在这里的网络IO中,潜在的阻塞点,分别是 accept() 和 recv()。当redis监听到有一个客户端连接请求时,但一直没有成功建立连接,就会阻塞在 accept() 这里,导致其他客户端无法与redis建立连接。当redis通过recv()从一个客户端读取数据时,如果数据一直不到达,redis也会一直阻塞在recv()。
这会导致redis整个线程阻塞,无法处理其他客户端请求,效率低下。幸好有socket非阻塞模式,如下:
虽然此时Redis线程可以不用继续等待,但还需要有机制继续在监听套接字上等待后续的连接请求,并及时通知redis;也需要有机制继续监听已连接套接字,并在有数据到达时及时通知redis。
这样才能保证redis线程,既不会像基本IO模型一样在阻塞点等待,也不会导致无法处理实际到达的连接请求或数据。
基于I/O多路复用的网络模型
Linux中的IO多路复用机制是指一个线程处理多个IO流,就是select/epoll机制。该机制允许内核中,同时存在多个监听socket和已连接socket。内核会一直监听这些socket上的连接请求或数据请求。一旦有请求到达,就交给redis线程处理。
这是基于多路复用的redis IO模型,FD代表套接字。reids调用epoll机制,让内核监听这些socket。此时redis线程不会阻塞在某个特定的socket。所以,redis可以同时和多个客户端连接并处理请求,提高并发性。
为了在请求到达时能通知到redis线程,select/epoll一旦监测到FD上有请求到达时,就会出发相应的事件。
这些事件会被放进一个事件队列,reids单线程对该队列不断处理。这样,redis无需一直轮询是否有请求实际发生,这就可以避免造成cpu资源浪费。同时,redis在对事件队列进行处理时,会调用相应的处理函数,实现了基于事件的回调。因为redis一直在对事件队列进行处理,所以能及时响应客户端请求,提升redis的响应性能。