赞
踩
上一篇介绍了 Redis 定义的九大数据类型。这节开始介绍 Redis 中的线程模型:单线程模型和多线程模型。
Redis 的单线程,主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的。但其他部分功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。
Redis 采用单线程(网络 I/O 和执行命令)还那么快,有如下几个原因:
CPU 并不是制约 Redis 性能表现的瓶颈所在,更多情况下是受到内存大小和网络I/O的限制,所以 Redis 使用单线程并没有什么问题。但是如果你想要使用服务器的多核 CPU,可以在一台服务器上启动多个节点或者采用切片集群的方式。
Redis 为什么要使用单线程模型?多线程不是可以提高性能吗?
多线程模式会面临执行顺序的不确定、共享资源的并发访问控制等问题,同时可能存在线程切换、加锁解锁和死锁的问题,需要额外的性能开销。而 Redis 主要的工作都是键值对的读写,所以单线程反而性能更高。且 Redis 的性能瓶颈主要在磁盘 IO 和网络 IO 方面,而多线程主要用于解决 CPU 的性能瓶颈。
上文提到 Redis 的单线程是指网络 IO 和键值对读写由主线程完成,但是还有其他工作是交给子线程/进程来完成的。
Redis 在启动的时候,会启动后台线程(BIO):
当我们要删除大量数据的时候,不要使用 del 命令删除,因为 del 是在主线程处理的,会导致 Redis 主线程卡顿,应该使用 unlink 命令来异步删除。
后台线程相当于一个消费者,生产者把耗时任务丢到任务队列中,消费者不停轮询这个队列,拿出任务就去执行对应的方法即可。
关闭文件、AOF 刷盘、释放内存这三个任务都有各自的任务队列:
网络模式大致可以分为 Reactor 和 Proactor,Reactor 是非阻塞同步网络模式,而 Proactor 是异步网络模式。
Redis 使用的是 Reactor 模式,所以先简单介绍一下 Reactor。
Reactor 模式主要由 Reactor 和处理资源池这两个核心部分组成:
Reactor 模式是灵活多变的,可以应对不同的业务场景,Reactor 的数量可以是单个或多个,处理资源的进程/线程也可以是单个或多个,所以就有了这四种方案:
其中,「多 Reactor 单进程 / 线程」实现方案相比「单 Reactor 单进程 / 线程」方案,不仅复杂而且也没有性能优势,因此实际中并没有应用。
Redis 的单线程模型使用的是「单 Reactor 单进程」模式。
进程里有 Reactor、Acceptor、Handler 这三个对象:
「单 Reactor 单进程」方案:
单 Reactor 单进程的方案因为全部工作都在同一个进程内完成,所以实现起来比较简单,不需要考虑进程间通信,也不用担心多进程竞争。
但是,这种方案存在 2 个缺点:
所以,单 Reactor 单进程的方案不适用计算密集型的场景,只适用于业务处理非常快速的场景。
在单线程模型下,IO 多路复用允许内核中,同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。Redis 可以同时和多个客户端连接并处理请求,从而提升并发性。
为了在请求到达时能通知到 Redis 线程,select/epoll 提供了基于事件的回调机制,即针对不同事件的发生,调用相应的处理函数。这些事件会被放进一个事件队列,Redis 单线程对该事件队列不断进行处理。
Redis 使用的 IO 多路复用技术主要有:基于 Linux 系统下的 select 和 epoll 实现,基于 FreeBSD 的 kqueue 实现,基于 Solaris 的 evport 实现。
每个 IO 多路复用函数库在 Redis 源码中都对应一个单独的文件,比如
ae_select.c
,ae_epoll.c
,ae_kqueue.c
等。Redis 会根据不同的操作系统,按照不同的优先级选择多路复用技术。
Redis 6.0 版本之前的单线模式如下图:
可以看到网络 I/O 和命令处理都是单线程。 Redis 初始化的时候,会做下面这几件事情:
初始化完后,主线程就进入到一个事件循环函数,主要会做以下事情:
随着网络硬件的性能提升,Redis 的性能瓶颈有时会出现在网络 IO 的处理上,也就是说,单个主线程处理网络请求的速度跟不上底层网络硬件的速度。Reids6.0 提出了多线程模型,可以采用多个 IO 线程来处理网络请求(默认关闭),提高网络请求处理的并行度。
仍然使用单线程执行读写命令操作,是因为命令操作往往不会有性能瓶颈,单线程处理可以避免额外的互斥操作,性能更高。
Redis 6.0 以后的多线程模型采用的是「单 Reactor 多线程」方案。
「单 Reactor 多线程」方案:
上面的三个步骤和单 Reactor 单线程方案是一样的,接下来的步骤就开始不一样了:
单 Reator 多线程的方案优势在于能够充分利用多核 CPU 的能,但是引入多线程,自然就带来了多线程竞争资源的问题。要避免多线程由于竞争共享资源而导致数据错乱的问题,就需要在操作共享资源前加上互斥锁,以保证任意时间里只有一个线程在操作共享资源,待该线程操作完释放互斥锁后,其他线程才有机会操作共享数据。
主要流程:
线程数是不是越多越好?
因为 Redis 的多线程模型采用的是固定数量的线程池,线程数是在启动 Redis 时就固定的,不能动态调整。如果线程数过多,会导致线程之间频繁地切换上下文,这样会浪费CPU资源,降低 Redis 的性能。另外,线程数过多还会占用过多的内存资源,可能会导致系统崩溃。
官方建议线程数设置为小于 CPU 核数,且不要超过 8 个。
本文介绍了 Redis 的单线程模型和多线程模型,单线程模型是指网络 IO 和读写键值对由主线程这一个线程来完成,而其他操作可以交由子线程完成。多线程则是在单线程的基础上,采用多个线程来处理网络请求。下一节将介绍 Redis 之间是如何进行通信的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。