redis专题十八:redis中的多线程
Redis是目前熟知的内存数据库。Redis6.0版本推出了多线程模型。
Redis不是已经采用了多路复用技术吗?不是号称很高的性能了吗?为啥还要采用多线程模型呢?
一、Redis为什么最初设计成单线程?
Redis的单线程指的是"其网络IO和键值对读写是由一个线程完成的"。也就是只有网络请求模块和数据操作模块是单线程的,而其他持久化存储模块、集群支撑模块等是多线程的。
二、多线程适用场景?
计算机执行过程操作:读写操作和计算操作。
读写主要涉及IO操作,包括网络IO和磁盘IO。计算操作主要涉及CPU。
多线程的目的:通过并发的方式提高IO利用率和CPU利用率。
Redis为什么不需要通过多线程的方式来提升IO利用率和CPU利用率?
- 首先,不需要提高CPU利用率,因为redis的操作是基于内存的,CPU不是Redis的性能瓶颈。
- Redis确实是一个IO密集型框架,数据操作过程中确实会有大量的网络IO和磁盘IO,提高IO利用率毋庸置疑。
但是提高IO利用率,不是只有多线程一条路。
多线程的弊端:线程安全、共享资源的并发控制;线程切换带来的性能开销。
redis采用的多路复用IO。
小结
Redis并没有在网络请求模块和数据操作模块中使用多线程模型,主要是基于以下四个原因:
1、Redis 操作基于内存,绝大多数操作的性能瓶颈不在 CPU
2、使用单线程模型,可维护性更高,开发,调试和维护的成本更低
3、单线程模型,避免了线程间切换带来的性能开销
4、在单线程中使用多路复用 I/O技术也能提升Redis的I/O利用率
还是要记住:Redis并不是完全单线程的,只是有关键的网络IO和键值对读写是由一个线程完成的。
三、redis的多路复用
linux的多路复用:通过一个线程来处理多个IO流。多个进程的IO可以注册到同一管道上,这个管道会统一个内核进行交互。当管道中某一个请求需要的数据准备好之后,进程再把对应的数据拷贝到用户空间中。
下图是网上找的一个IO复用模型的图:
IO多路复用在Linux下包括了三种,select、poll、epoll,抽象来看,他们功能是类似的,但具体细节各有不同。
其实,Redis的IO多路复用程序的所有功能都是通过包装操作系统的IO多路复用函数库来实现的。
在Redis 中,每当一个套接字准备好执行连接应答、写入、读取、关闭等操作时,就会产生一个文件事件。因为一个服务器通常会连接多个套接字,所以多个文件事件有可能会并发地出现。
一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。
四、为什么redis6.0引入多线程?
Redis6.0中的多线程,也只是针对网络请求过程采用了多线程,而数据的读写命令仍然是单线程处理的。
为什么呢?不是多路复用技术已经大大提高IO利用率了吗?
主要对Redis有着更高的要求。
根据测算,Redis 将所有数据放在内存中,内存的响应时长大约为 100 纳秒,对于小数据包,Redis 服务器可以处理 80,000 到 100,000 QPS,这么高的对于 80% 的公司来说,单线程的 Redis 已经足够使用了。
但随着越来越复杂的业务场景,有些公司动不动就上亿的交易量,因此需要更大的 QPS。
为了提升QPS,很多公司的做法是部署Redis集群,并且尽可能提升Redis机器数。但是这种做法的资源消耗是巨大的。
而经过分析,限制Redis的性能的主要瓶颈出现在网络IO的处理上,虽然之前采用了多路复用技术。但是我们前面也提到过,多路复用的IO模型本质上仍然是同步阻塞型IO模型。
下面是多路复用IO中select函数的处理过程:
从上图我们可以看到,在多路复用的IO模型中,在处理网络请求时,调用 select (其他函数同理)的过程是阻塞的,也就是说这个过程会阻塞线程,如果并发量很高,此处可能会成为瓶颈。
虽然现在很多服务器都是多个CPU核的,但是对于Redis来说,因为使用了单线程,在一次数据操作的过程中,有大量的CPU时间片是耗费在了网络IO的同步处理上的,并没有充分的发挥出多核的优势。
如果能采用多线程,使得网络处理的请求并发进行,就可以大大的提升性能。多线程除了可以减少由于网络 I/O 等待造成的影响,还可以充分利用 CPU 的多核优势。
所以,Redis 6.0采用多个IO线程来处理网络请求,网络请求的解析可以由其他线程完成,然后把解析后的请求交由主线程进行实际的内存读写。提升网络请求处理的并行度,进而提升整体性能。
但是,Redis 的多 IO 线程只是用来处理网络请求的,对于读写命令,Redis 仍然使用单线程来处理。
那么,在引入多线程之后,如何解决并发带来的线程安全问题呢?
这就是为什么我们前面多次提到的"Redis 6.0的多线程只用来处理网络请求,而数据的读写还是单线程"的原因。
Redis 6.0 只有在网络请求的接收和解析,以及请求后的数据通过网络返回给时,使用了多线程。而数据读写操作还是由单线程来完成的,所以,这样就不会出现并发问题了。