发布于 

为什么单线程的Redis也能如此高效?

提起 Redis,一个印象就是速度很快的开源内存数据库,而且还是单线程的。

但印象有时会骗人:

1、redis 6.0 后采用了多线程

2、redis 7.4 之后修改了开源协议

单线程为什么速度还很快?

1、Redis 的客户端调用服务器经过三个过程:发送命令、执行命令和返回结果。在命令执行阶段,由于 Redis 处理命令是单线程的,在服务器上到达的所有命令不会立即被执行。所有命令都进入队列并按顺序执行。多个客户端发送的命令执行顺序不确定。但是可以确定的是两个命令不会同时执行,避免了并发问题。这是 Redis 的基本单线程模型。

2、Redis 完全基于内存,数据存储在内存中。大多数请求都是纯内存操作,所以速度非常快。与传统的磁盘文件数据存储相比,Redis 避免了通过磁盘 I/O 读取数据到内存的开销。

3、使用单线程可以节省很多上下文切换和 CPU 消耗的时间,没有竞争条件,不需要考虑各种锁定问题,并且不会因为死锁而导致性能开销。此外,它还允许使用各种「线程不安全」的命令,例如 Lpush。

4、需要注意的是,当我们强调单线程时,我们指的是使用一个线程来处理网络 I/O 和键值对读写。换句话说,一个线程处理所有网络请求。但 Redis 的其他功能,如持久性、异步删除和集群数据同步,实际上是由额外的线程执行。也就是说即便是在 6.0 之前的版本,也不是绝对的单线程。

5、Redis 虽然是纯内存操作,但仍然会涉及到网络 I/O 和磁盘 I/O(持久化操作),Redis 使用非阻塞 I/O 和 I/O 多路复用技术(如 selectpollepoll)来处理大量的并发连接。这意味着一个线程可以同时监听多个客户端的连接请求,并在有数据可读或可写时进行处理。这种方式避免了线程在等待 I/O 操作完成时的阻塞,从而提高了系统的并发处理能力。

  • 非阻塞 I/O:允许程序在 I/O 操作未完成时继续执行其他任务。
  • I/O 多路复用:通过单线程监控多个 I/O 操作,提升高并发下的效率。

6、官方对单线程的解释是:因为 CPU 不是 Redis 的瓶颈,最有可能的是机器内存或网络带宽。由于单线程易于实现,并且 CPU 不会成为瓶颈,采用单线程解决方案是有意义的。

为什么 6.0 又采用了多线程?

Redis 在 6.0 版本引入多线程支持,主要是为了应对现代硬件架构的变化和日益复杂的业务需求,同时解决单线程模型在高并发场景下的性能瓶颈。

1、提升网络 I/O 性能

Redis 的主要性能瓶颈在于网络 I/O,尤其是在高并发场景下,单线程模型难以高效处理大量的网络请求。虽然 Redis 使用了 I/O 多路复用技术(如 epoll)来优化网络处理,但随着硬件性能的提升,单线程的网络 I/O 处理能力逐渐成为瓶颈。

通过引入多线程,Redis 可以将网络 I/O 操作(如读取客户端请求和写回响应)分配到多个线程并行处理,从而显著提升网络吞吐量和降低延迟。

2、利用多核 CPU

现在的服务器普遍配备多核 CPU,而 Redis 的单线程模型只能利用一个 CPU 核心,无法充分发挥硬件的性能潜力。通过引入多线程,Redis 可以将网络 I/O 任务分配到多个线程,充分利用多核 CPU 的计算能力,从而提高整体性能。

3、保持核心逻辑的简单性和一致性

Redis 6.0 的多线程模型采用了混合设计:网络 I/O 操作由多个线程并行处理,而命令执行仍然由单线程顺序执行。这种设计既保留了单线程模型的简单性和一致性优势(如避免线程安全问题、保证命令的原子性),又通过多线程提升了网络 I/O 的性能。和之前的版本相比,只是将更多的事情由多线程来处理。

4、优化资源利用

在高负载场景下,单线程模型可能导致 CPU 和内存资源的浪费。通过引入多线程,Redis 可以更高效地利用系统资源,减少请求的等待时间,从而提升整体性能。

5、如何在 Redis 6.0 中启用多线程?

默认情况下,Redis 的多线程是禁用的,如果要启用多线程功能,需要修改 Redis 的配置文件,涉及到两个配置项:

io-threads:

  • 该选项用于设置 I/O 线程的数量
  • 默认值为 1,即不启用多线程
  • 如果设置为大于 1 的值,Redis 会启用多线程来处理网络 I/O
  • 建议将 io-threads 设置为小于 CPU 核心数的值,通常为 CPU 核心数的 1/2 到 2/3。

io-threads-do-reads:

  • 该选项用于控制是否启用多线程处理读操作
  • 默认值为 no,即不启用多线程读操作
  • 如果需要启用多线程读操作,可以将其设置为 yes

配置示例如下:

1
2
3
4
5
# 启用多线程,设置 I/O 线程数为 4
io-threads 4

# 启用多线程处理读操作
io-threads-do-reads yes

修改了开源协议,有什么替代方案

Redis 从 7.4 版本开始修改了开源协议,从 BSD 变更为 RSALv2 和 SSPLv1 双重许可,这意味着 Redis 在 OSI(开放源代码促进会)定义下不再被视为严格的开源软件。

这一变更对云服务商和开发者产生了较大影响,尤其是那些依赖 Redis 提供商业服务的厂商。

可以使用 Valkey 进行代替,Valkey 由 Linux 基金会支持,采用 BSD 许可证,确保了项目的开源性质。自 2024 年 3 月由 Redis 项目的贡献者和 Linux 基金会联合发起以来,Valkey 已经得到了包括亚马逊云科技在内的 40 多家公司的支持和贡献。

我现在在研究的 RAGFlow 使用的就是 Valkey 。下面是 RAGFlow 中部署 Valkey 的 docker-compose 部分代码:

1
2
3
4
5
6
7
8
9
10
11
12
redis:
image: valkey/valkey:8
container_name: ragflow-redis
command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 128mb --maxmemory-policy allkeys-lru
env_file: .env
ports:
- ${REDIS_PORT}:6379
volumes:
- redis_data:/data
networks:
- ragflow
restart: on-failure