redis是全部只有一个线程吗?
Redis是单线程,主要是指Redis的网络IO和键值读写是一个线程完成的,这也是Redis对外提供键值存储服务的主要流程。但Redis的其他功能,比如持久化、异步删除、集群数据同步等,是由额外的线程执行的(Redis 4.0增加功能)。
所以从整体看,Redis并不只有一个线程完成了所有工作,而是主要的网络IO读写、键值服务都是一个线程完成的。比如bigkey的删除,一定是采用unlink去进行异步的删除,而不是直接del,会阻塞其他客户端的命令执行。
Redis为什么采用单线程?
- 主要是一个基于数据结构的内存操作服务,如果用多线程需要考虑对数据结构并发操作的线程安全问题,比如对临界资源进行排队,反而增加了复杂度和效率。
- 对于Redis来说,瓶颈不在CPU,而在内存和网络带宽,采用单线程去处理读写命令即可。设计更简单。
redis单线程为什么这么快?
- 基于内存进行操作,效率高于磁盘
- 处理命令是单线程,避免频繁的上下文切换和临界资源的锁竞争。
- 底层使用优化过的数据结构,比如hash表和跳表等,读取的时间复杂度都很低。
- 采用了非阻塞的IO多路复用模型,可以非阻塞的同时处理多个客户端的socket请求。
IO多路复用模型
阻塞IO模型与阻塞点
比如对于一个get请求,服务端处理的步骤如下:
- 监听客户端请求(bind/listen)
- 和客户端建立连接(accept)
- 从socket中读取数据(recv),java中就是IO的read操作。
- 解析客户端的请求(parse)
- 执行get操作
- 返回给客户端结果,向socket中写回数据(send)。
其中阻塞点在于accept建立连接和recv读取数据(联想Java中的BIO),比如服务端如果阻塞在read数据,就无法响应其他客户端的连接请求,是阻塞的。
非阻塞模式
socket网络模型本身支持非阻塞模式。
- 针对监听套接字,可以设置非阻塞模式:当服务端调用accept()但一直未有连接到达时,Redis线程可以返回处理其他操作,不需要一直阻塞,有一套机制在监听套接字有连接时通知到Redis。
- 同样的针对已连接套接字,Redis调用recv()之后,没有数据到达进行读操作,也可以先返回非阻塞,利用相同机制当有数据读时通知到Redis
基于多路复用的高性能IO模型
IO多路复用是指的一个线程处理多个IO流,在linux中是通过select/poll/epoll三个机制实现的。
简单来说,IO多路复用就是内核监测文件的读写事件,再通知Redis完成对应的回调操作,保证Redis的非阻塞IO能顺利完成。
多路:多个socket连接
复用:复用一个线程来处理多个socket连接请求事件、
在Redis的单线程模型中,IO多路复用是在内核中,同时存在多个监听套接字和已连接套接字,内核会一直监听这些监听套接字和已连接套接字,如果有对应的请求到达,才会交给Redis的服务端线程处理,Redis服务端的单线程通过文件事件处理队列去排队,文件事件处理器去分派处理,达到多个客户端的socket请求(多路),单线程处理(复用)的高性能线程模型。
Redis IO模型中,利用内核的epoll机制,让内核去监听客户端的socket连接,Redis并不会阻塞在某个特定的accept或者read请求上,当epoll监听到对应的事件请求,会放入就绪队列中,通过基于事件的回调来通知到Redis来处理对应的请求。
Redis使用基于epoll实现的网络通信模型,可以避免Redis自身的线程去轮询FD来监听是否有请求事件发生,而是交给了内核,由epoll来完成监听。Redis基于事件回调自身只从事件队列中拿出事件来处理,及时响应客户端的请求,达到IO多路复用的效果。
Redis的IO多路复用的工作模型:
Redis 6.0 中的多线程模型是怎么样的?
Redis6.0的多线程只是说多线程来处理事件的读写和协议解析,真正执行对命令的还是单线程机制。默认是不开启的,这样的多线程模型为了提高IO效率,解决在网络IO上的瓶颈。
在 Redis 6.0 中新增了多线程的功能来提高 I/O 的读写性能,他的主要实现思路是将主线程的 IO 读写任务拆分给一组独立的线程去执行,这样就可以使多个 socket 的读写可以并行化了,采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),将最耗时的Socket的读取、请求解析、写入单独外包出去,剩下的命令执行仍然由主线程串行执行并和内存的数据交互。