Redis是一个高性能的内存键值数据库。根据官方测试报告,它可以在单机上支持大约10万QPS(每秒查询数)。然而,Redis在其设计中使用了单线程架构。
为什么Redis在单线程设计下仍然具有如此高的性能?使用多线程进行并发请求处理不是更好吗?
在本文中,让我们探讨为什么Redis采用单线程架构,并且仍然保持其速度。重点关注以下四个方面:
- 内存中的数据存储
- 高效的数据结构
- 单线程架构
- 非阻塞I/O
让我们详细分析每一个方面。
内存中的数据存储
Redis完全基于内存,数据存储在内存中。绝大多数请求都是纯粹的内存操作,速度极快。与传统的磁盘文件数据存储相比,Redis避免了通过磁盘I/O将数据从磁盘读取到内存的开销。
高效的数据结构
Redis共有5种数据类型:字符串、列表、哈希、集合和有序集合。
不同的数据类型在底层使用一种或多种数据结构来支持它们,目的是为了达到更快的速度。
单线程架构
使用单线程节省了大量上下文切换和CPU消耗的时间,没有竞争条件,不需要考虑各种锁问题,也没有可能由于死锁而导致性能开销的加锁和解锁操作。此外,它允许使用各种“非线程安全”的命令,例如Lpush
。
请注意,当我们强调单线程时,我们指的是使用一个线程来处理网络I/O和键值对读写(文件事件分派器)。换句话说,一个线程处理所有网络请求,但是Redis的其他功能,例如持久化、异步删除和集群数据同步,实际上是由额外的线程执行的。
那么为什么使用单线程呢?官方的答案是,因为CPU不是Redis的瓶颈,它最可能是机器内存或网络带宽。由于单线程易于实现,并且CPU不会成为瓶颈,因此采用单线程解决方案是有意义的。
虽然多线程架构允许应用程序通过上下文切换并发处理任务,但它只为Redis提供了轻微的性能提升,因为大多数线程最终会被网络I/O阻塞。
同样重要的是要注意,因为Redis使用单线程,如果一个命令执行时间过长(例如hgetall
命令),它可能会导致阻塞。Redis是一个内存数据库,设计用于快速执行,因此在使用lrange
、smembers
、hgetall
等命令时务必谨慎。
非阻塞I/O
使用基于网络I/O多路复用(非阻塞I/O)的线程模型,允许处理并发连接,并有助于缓解网络I/O速度慢的问题。
多路复用I/O模型利用select、poll和epoll同时监控多个流的I/O事件的能力。空闲时,当前线程被阻塞。当一个或多个流有I/O事件时,线程从阻塞状态被唤醒,程序轮询所有流(epoll只轮询产生事件的流),然后顺序处理就绪的流。这种方法避免了大量的无用操作。
这里,“多路复用”指的是多个网络连接,“重用”指的是重用同一个线程。使用多路复用I/O技术允许单个线程有效地处理多个客户端网络I/O连接请求(以最大限度地减少在网络I/O上花费的时间)。
Redis的网络事件处理器基于Reactor模式,也称为文件事件处理器。
文件事件处理器使用I/O多路复用来同时监听多个套接字,并将套接字执行的任务与不同的事件处理器关联。
文件事件以单线程方式运行,但是通过使用I/O多路复用程序来监听多个套接字,文件事件处理器实现了一个高性能的网络通信模型。
Redis通过单个顺序主线程处理客户端请求,包括接收(套接字读取)、解析、执行和发送(套接字写入),这就是所谓的单线程模型。
多个套接字可能会产生不同的操作,每个操作对应一个不同的文件事件。但是,I/O多路复用程序监听多个套接字并排队套接字生成的事件。事件分派器每次从队列中检索一个事件,并将事件传递给相应的事件处理器进行处理。
Redis客户端对服务器的调用经过三个过程:发送命令、执行命令和返回结果。在命令执行阶段,由于Redis在处理命令时是单线程的,因此到达服务器的每个命令不会立即执行。所有命令都进入队列并逐一执行。多个客户端发送的命令的执行顺序是不确定的。但是可以肯定的是,两个命令不会同时执行,避免了并发问题。这就是Redis的基本单线程模型。
Redis 6.0多线程的解释
为什么在Redis 6.0版本之前不使用多线程?
Redis使用单线程方法来实现高可维护性。虽然多线程在某些方面可能表现良好,但它会引入程序执行顺序的不确定性,导致一系列并发读写问题。这增加了系统复杂性,并可能导致由于线程切换、加锁和解锁,甚至死锁而导致性能损失。
为什么Redis 6.0引入了多线程?
Redis 6.0引入多线程是因为它的瓶颈不在于内存,而在于网络I/O模块,它消耗了CPU时间。因此,引入多线程来处理网络I/O并充分利用CPU资源,减少网络I/O阻塞造成的性能损失。
如何在Redis 6.0中启用多线程?
默认情况下,Redis中禁用多线程,可以在conf文件中启用:
io-threads-do-reads yes
io-threads [线程数]
根据官方指南,推荐的线程数是为4核机器设置2-3个线程,为8核机器设置6个线程。线程数应小于机器核心数,如果可能,不应超过8个线程。
多线程模式下是否存在线程并发问题?
如图所示,Redis请求涉及建立连接、获取要执行的命令、执行命令,最后将响应写入套接字。
在Redis的多线程模式下,接收、发送和解析命令可以配置为在多个线程中执行,因为它们是我们已经识别出的主要耗时点。但是,涉及内存操作的命令执行仍然在一个线程中顺序运行。
因此,Redis的多线程部分仅用于处理网络数据读写和协议解析。命令执行仍然在一个线程中顺序执行,因此不存在并发安全问题。
????????