Redis真的是单线程吗?
这种问法其实并不严谨,为啥这么说呢?
Redis的版本很多3.x、4.x、6.x,版本不同架构也是不同的,不限定版本问是否单线程也不太严谨。
版本3.x ,最早版本,也就是大家口口相传的redis是单线程,阳哥2016年讲解的redis就是3.X的版本。
版本4.x,严格意义来说也不是单线程,而是负责处理客户端请求的线程是单线程,但是开始加了点多线程的东西(异步删除)。
2020年5月版本的6.0.x后及2022年出的7.0版本后,告别了大家印象中的单线程,用一种全新的多线程来解决问题。
所以说,当别人问我们redis是单线程的时候,我们应该针对具体的版本进行回答
下面是一张Redis关键版本的时间线图:
1. 我么常说的单线程是什么意思?
Redis是单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的,Redis在处理客户端的请求时包括获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个顺序串行的主线程处理,这就是所谓的“单线程”。这也是Redis对外提供键值存储服务的主要流程。
注意:
但Redis的其他功能,比如持久化RDB、AOF、异步删除、集群数据同步等等,其实是由额外的线程执行的。Redis命令工作线程是单线程的,但是,整个Redis来说,是多线程的
2. Redis3.x版本是单线程为啥还这么快
内存优化:
Redis 将数据存储在内存中,而内存访问速度远远高于磁盘访问速度。
Redis 使用紧凑的数据结构和数据压缩技术,减少了存储空间的占用。
Redis 采用了特定的数据结构,如哈希表、跳表和位图等,用于高效存储和处理不同类型的数据。
非阻塞I/O:
- Redis 使用了非阻塞式I/O模型,通过单个线程处理多个客户端连接和I/O操作。
- 在I/O操作等待数据传输的过程中,Redis可以继续处理其他请求,充分利用CPU资源。
- 非阻塞I/O避免了线程切换的开销,提高了并发性能。
单线程优化:
- 单线程避免了多线程之间的线程切换和锁竞争带来的开销。
- Redis 的单线程模型简化了数据访问的逻辑,避免了复杂的并发控制问题。
- 单线程模型使得Redis的内部操作是原子性的,无需考虑并发写冲突的问题。
异步操作:
- Redis 支持异步操作,如批量操作、管道(pipeline)操作和订阅/发布等。
- 异步操作允许客户端一次发送多个请求,减少了网络通信的开销。
- 异步操作可以批量执行多个命令,减少了客户端和服务器之间的往返次数。
高效的网络通信:
- Redis 使用高性能的网络库,如epoll、kqueue等,提高了网络通信的效率。
- Redis采用了自定义的协议,使用简单且高效,减少了数据传输的开销。
- Redis的客户端和服务器之间采用TCP连接,保证了可靠性和稳定性。
对于Redis系统来说,主要的性能瓶颈是内存或者网络带宽而并非 CPU。
3. 既然单线程这么好,为什么Redis又逐渐加入了多线程?
3.1. 单线程也有单线程的苦恼呀!
尽管 Redis 在早期版本中采用了单线程模型,并且在许多场景下表现出色,但随着数据量和并发访问的增加,单线程模型也存在一些潜在的限制。因此,Redis 在较新的版本中引入了多线程的特性,主要有以下几个原因:
- 多核利用:单线程模型无法充分利用多核处理器的计算能力。在单线程模型下,无法并行处理多个任务,导致某些操作的性能受限。通过引入多线程模型,可以将不同任务分配给不同的线程,在多核处理器上并行执行,提高整体的并发处理能力。
- 长时阻塞问题:在某些情况下,Redis 的单线程模型可能面临长时阻塞的问题。例如,当执行慢查询、持久化操作(如RDB快照或AOF重写)或大规模数据迁移时,这些操作可能会导致Redis无法及时响应其他请求。通过引入多线程模型,可以将这些长时阻塞的操作分配给独立的线程,保持主线程的高响应性。
- I/O密集型操作:尽管 Redis 的主要瓶颈通常是CPU而不是I/O,但在某些场景下,I/O密集型操作(如大量的网络请求)可能成为性能瓶颈。通过引入多线程模型,可以在主线程处理CPU密集型操作的同时,将I/O密集型操作分配给其他线程,以提高整体的处理效率。
3.2. Redis单线程模式
3.3. 为什么引入?
查看官方文档发现:
- 在使用 Redis 时,Redis 主要受限是在内存和网络上,CPU 几乎没有性能瓶颈的问题。
- 以Linux 系统为例子,在Linux系统上Redis 通过 pipelining 可以处理 100w 个请求每秒,而应用程序的计算复杂度主要是 O(N) 或 O(log(N)) ,不会消耗太多 CPU。
- 使用了单线程后,提高了可维护性。多线程模型在某些方面表现优异,却增加了程序执行顺序的不确定性,并且带来了并发读写的一系列问题,增加了系统复杂度。同时因为线程切换、加解锁,甚至死锁,造成一定的性能损耗。
- Redis 通过 AE 事件模型以及 IO 多路复用等技术,拥有超高的处理性能,因此没有使用多线程的必要。
可以看出,Redis对CPU计算力的要求并不迫切,相反单线程机制让 Redis 内部实现的复杂度大大降低,同时降低了因为上下文切换和资源竞争造成的性能损耗。那既然单线程这么好用,为什么要引入多线程模式。
4. Redis6.x多线程特性和IO多路复用
4.1. 原因
近年来底层网络硬件性能越来越好,Redis 的性能瓶颈逐渐体现在网络 I/O 的读写上,单个线程处理网络 I/O 读写的速度跟不上底层网络硬件执行的速度。
在Redis 6/7中,引入了多线程是一个备受关注的新特性。之前,Redis一直以其单线程架构而闻名。尽管某些命令操作可以使用后台线程或子进程执行(如数据删除、快照生成、AOF重写),但从网络IO处理到实际的读写命令处理,都是由单个线程完成的。
为了应对这个问题,Redis 6/7采用了多个IO线程来处理网络请求,提高了网络请求处理的并行度。
然而,需要注意的是,Redis的多IO线程仅用于处理网络请求,对于读写操作命令,Redis仍然使用单线程来处理。这是因为在Redis处理请求时,网络处理经常成为瓶颈。通过多个IO线程并行处理网络操作,可以提升实例的整体处理性能。然而,为了保证Lua脚本和事务的原子性,继续使用单线程执行命令操作是必要的。这样一来,Redis的线程模型实现就更为简单,无需额外开发多线程互斥加锁机制(无论如何实现加锁操作)。
因此,Redis 6/7中的多线程特性主要针对网络IO处理,而仍然保持了单线程处理读写操作的优势。这种组合使得Redis在性能和简单性之间找到了一个平衡点。
4.2. 优化方法
- 提高网络IO性能:这种方法旨在改善Redis执行期间读写网络的性能瓶颈。一种常见的实现方式是使用DPDK(Data Plane Development Kit)来替代内核网络栈。DPDK是一个开源软件开发套件,它提供了用户空间网络堆栈,可以实现更高的网络数据包处理性能。通过使用DPDK,可以绕过内核的网络协议栈,直接在用户空间进行网络数据包处理,从而提高Redis的网络IO性能。
- 使用多线程:通过引入多线程,Redis可以更充分地利用多核CPU的资源。相比之下,主线程只能利用一个CPU核心。通过使用多线程,可以将Redis的负载分散到多个线程中,以实现并行处理。这样一来,多个线程可以同时执行任务,从而提高Redis的整体性能。类似的实现案例是Memcached,它也使用了多线程来提高性能。
协议栈优化是一种改善网络性能的方法,与Redis的关系并不直接。因此,最方便和高效的方式是支持多线程。通过支持多线程,Redis可以充分利用多核CPU资源,并将同步IO的读写负载分摊到多个线程中,从而降低延迟和提高性能。
4.3. IO多路复用执行流程
具体步骤如下:
- 主线程建立连接,并接受数据,并将获取的 socket 数据放入等待队列;
- 通过轮询的方式将 socket读取出来并分配给 IO 线程;
- 之后主线程保持阻塞,一直等到 IO 线程完成 socket 读取和解析;
- I/O 线程读取和解析完成之后,返回给主线程 ,主线程开始执行 Redis 命令;
- 执行完Redis命令后,主线程阻塞,直到IO 线程完成 结果回写到socket 的工作;
- 主线程清空已完成的队列,等待客户端新的请求。
本质上是将主线程 IO 读写的这个操作 独立出来,单独交给一个I/O线程组处理。 这样多个 socket 读写可以并行执行,整体效率也就提高了。同时注意 Redis 命令还是主线程串行执行。
注:本文参考了以下内容www.cnblogs.com/wzh2010/p/1…