简单介绍了Redis 6.0的多线程模型。
此前我们学习了Redis的单线程模型: Redis的线程模型—文件事件处理器的详解,但是Redis 6.0版本中引入多线程,下面简单的来看看吧!
1 Redis与单线程
因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。
既然CPU不会成为瓶颈,而使用单线程模型又能带来更好的可维护性,方便开发和调试,那就顺理成章地采用单线程的方案了。如果使用多线程,那么将会使编程更加复杂,并且要考虑到加锁以及上下文切换等问题。
虽然Redis的处理客户端请求采用的是单线程Reactor工作模型,但实际上,Redis 在 4.0 之后的版本中就已经加入了对多线程的支持,在执行一些命令时就会使用“主处理线程”之外的其他后台线程协助处理,例如 UNLINK、FLUSHALL ASYNC、FLUSHDB ASYNC 等非阻塞的删除操作。
对于新增的可以使用多线程的命令,它们的特点是:大键值对的删除操作的命令。使用这些命令就会使用主处理之外的其他线程来“异步处理”。
因为对于删除操作,如果键值对所占用的内存空间较小,单线程同步删除损耗也不会太大,如果键值对占用的内存空间较大,几十兆的文件,毫秒内是无法删除的,且释放内存也会有时间消耗,这些操作会阻塞其它操作,但是这些删除释放操作对其它操作并没有任何影响,所以可以异步执行,通过多线程非阻塞的释放内存空间,提高执行效率。
2 Redis 6.0的多线程
Redis6.0引入多线程主要是为了提高网络 IO 读写性能,读写网络的read/write系统调用占用了Redis执行期间大部分CPU时间,瓶颈主要在于网络的IO消耗(还有一个瓶颈是内存)。
redis支持多线程主要就是可以充分利用服务器多核CPU资源,多线程并行的进行网络IO操作,因为目前的线程模型只能利用一个核(一个线程)。
虽然Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用,执行命令仍然是单线程顺序执行。因此不需要担心线程安全问题。
Redis6.0的多线程默认是禁用的,只使用主线程,并且建议只在4核以及更多核心的机器上开启。如需开启需要修改 redis 配置文件 redis.conf 的io-threads-do-reads为true。开启多线程后,还需要设置线程数,否则是不生效的。同样需要设置 redis 配置文件 redis.conf的io-threads 属性,4核的机器建议设置为2或3个线程,8核的建议设置为6个线程。
完整的配置和描述如下:
################################ THREADED I/O #################################
# Redis 主要是单线程的,但是也有某些线程操作,例如 UNLINK、慢 I/O 访问和其他在辅助线程上执行的操作。
# 现在还可以在不同的 I/O 线程中处理 Redis 客户端套接字读取和写入。
# 由于写入速度特别慢,Redis 用户通常使用流水线来加速每个内核的 Redis 性能,并生成多个实例以扩展更多。
# 使用 I/O 线程可以轻松地将 Redis 加速两倍,而无需借助管道或实例分片。
#
# 默认情况下线程是禁用的,我们建议只在至少有 4 个或更多内核的机器上启用它,至少留下一个备用内核。
# 使用超过 8 个线程不太可能有太大帮助。
# 我们还建议您仅在确实存在性能问题时才使用线程 I/O,因为 Redis 实例能够使用相当大比例的 CPU 时间,否则使用此功能没有意义。
#
# 4核的机器建议设置为2或3个线程,8核的建议设置为6个线程。通过io-threads设置线程数:
#
# io-threads 4
#
# 将 io-threads 设置为 1 表示将像往常一样使用主线程。
# 当启用 I/O 线程时,我们只使用多线程进行写入,即线程化 write(2) 系统调用并将客户端缓冲区传输到套接字。
# 但是,也可以使用以下配置指令启用读取线程和协议解析,将其设置为 yes:
#
# io-threads-do-reads no
#
# 通常线程读取没有多大帮助。
Redis6.6引入多线程之后的请求处理流程:
- 主线程仍然是负责接收连接请求,并负责监听各种事件。
- 读事件(收到请求)到来时,主线程会将该Socket加入一个等待队列,队列满了之后会将队列中的Socket通过Round Robin算法(RR)分配给IO线程组中的线程,Socket和对应的IO线程绑定。
- 此后主线程等待IO线程组将Socket中的请求数据读取并解析完成(这里只是读数据和解析,并不执行);
- 如果所有的Socket都读取完毕,那么主线程按照队列顺序依次执行所有命令,保证执行命令时是单线程的。
- 命令执行完毕之后,再次阻塞等待所有的IO线程组将数据写回Socket完毕,最后主线程中解除Socket和IO线程的绑定。
开启多线程之后,如下特点:
- IO线程组要么同时在读,要么同时在写,不会同时读或写。
- IO线程只负责读写 socket 解析命令,不负责命令处理,命令的执行由主线程串行执行(保持单线程)。
- 无须担心命令执行的安全问题。
相关文章:
- https://redis.io/topics/data-types
- https://redis.io/topics/data-types-intro
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!