一文搞懂Redis是单线程还是多线程?

2,571 阅读4分钟

「这是我参与2022首次更文挑战的第27天,活动详情查看:2022首次更文挑战

前言

Redis,在工作中我们经常使用到,这也决定了它是面试的常客。我们总说Redis是单线程的,其实这也没错也错了,因为Redis不仅仅是单线程,从4.0开始,Redis就引进了多线程的概念。

单线程

我们常说的Redis单线程其实是指事件处理上的。在了解事件处理上,先了解下redis的工作机制。

Redis服务器是一个事件驱动程序,服务器需要处理以下两类事件:

  • 文件事件:Redis服务器通过套接字与客户端(或者其他Redis服务器)进行连接,而文件事件就是服务器对套接字操作的抽象;服务器与客户端的通信会产生相应的文件事件,而服务器则通过监听并处理这些事件来完成一系列网络通信操作,比如连接acceptreadwriteclose
  • 时间事件:Redis服务器中的一些操作需要在给定的时间点执行,而时间事件就是服务器对这类定时操作的抽象,比如过期键清理

Redis将文件事件和时间事件进行抽象,时间轮训器会监听I/O事件表,一旦有文件事件就绪,Redis就会优先处理文件事件,接着处理时间事件。

在所有事件处理上,Redis都是以单线程形式处理,所以说Redis是单线程的。

同时,Redis基于Reactor模式开发了自己的I/O事件处理器,也就是文件事件处理器,Redis在I/O事件处理上,采用了I/O多路复用技术,同时监听多个套接字,并为套接字关联不同的事件处理函数,通过一个线程实现了多客户端并发处理。

但是,实际上Redis也并不是单线程的,比如生成RDB文件,就会fork一个子进程来实现。

在4.0之前,Redis在处理客户端命名时都是以单线程形式运行的,期间不响应其他客户端请求;但是如果发生一条耗时很长的命令,如:删除一个大key,导致Redis服务器卡住了几秒,对负载高的缓存系统是一种灾难,因此,在4.0版本引入了Lazy Free机制,将慢操作异步化了。

I/O多路复用
- I/O :网络 I/O 
- 多路:多个客户端连接(连接就是套接字描述符,即 socket 或者 channel) 
- 复用:复用一个或几个线程。就是说一个或一组线程处理多个 TCP 连接,使用单进程就能够实现同时处理多个客户端的连接 

就是说`一个服务端进程可以同时处理多个套接字描述符`

Lazy Free机制

将大键的删除操作异步化,采用非阻塞删除(对应命令UNLINK),大键的空间回收交由单独线程实现,主线程只做关系解除,可以快速返回,继续处理其他事件,避免服务器长时间阻塞。

Redis6.0后

Redis在4.0版本引入了Lazy Free,自此Redis有了一个Lazy Free线程专门用于大键的回收,但是大家都知道,Redis的性能瓶颈并不在CPU上,而是在内存和网络上。所以6.0发布的多线程并未将事件处理改成多线程,而是在I/O上

如果把事件处理改成多线程,不但会导致锁竞争,而且会有频繁的上下文切换,即使用分段锁来减少竞争,对Redis内核也会有较大改动,性能也不一定有明显提升。

但是,6.0版本的多线程并非彻底的多线程,I/O线程只能同时执行读或者同时执行写操作,期间事件处理线程一直处于等待状态,并非流水线模型,有很多轮训等待开销。

在 Redis6.0 中,多线程机制默认是关闭的,需要在 redis.conf 中完成以下两个设置才能启用多线程。

  • 设置 io-thread-do-reads 配置项为 yes,表示启用多线程。

    io-threads-do-reads yes
    
  • 设置线程个数

    io-threads 6
    

    ⼀般来说,线程个数要小于 Redis 实例所在机器的 CPU 核数,例如,对于⼀个 4 核的机器来说,Redis 官⽅建议配置 2 个 或 3个IO 线程。 线程数并不是越大越好,官方认为超过了8个基本就没什么意义了

    总结

    Redis的单线程,主要是指事件处理上,但是Redis的其他功能,如持久化、异步删除这些都是由额外的线程执行的。在Redis6.0版本的多线程并非彻底的多线程,I/O线程只能同时执行读或者同时执行写操作。在开启多线程后,并不会存在线程并发的安全问题,因为Redis的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行。