深度解析redis单线程模型

585 阅读6分钟

背景:但凡你用redis都知道redis的一大特点就是单线程,那么在如今服务器普遍多核的时代下,为什么redis要采用单线程模型?单线程模型好在哪里?不用单线程模型redis是不是可以更快?带着这些问题我们一起来回顾一下redis的单线程模型。

首先认识一下redis的单线程模型是什么: redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。 文件事件处理器的结构包含 4 个部分: 多个 socket IO 多路复用程序 文件事件分派器 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

     首先,redis 服务端进程初始化的时候,会将 server socket 的 AE_READABLE 事件与连接应答处理器关联。 客户端 socket01 向 redis 进程的 server socket 请求建立连接,此时 server socket 会产生一个 AE_READABLE 事件, IO 多路复用程序监听到 server socket 产生的事件后,将该 socket 压入队列中。

文件事件分派器从队列中获取 socket,交给连接应答处理器。连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的 AE_READABLE 事件与命令请求处理器关联。

    假设此时客户端发送了一个 set key value 请求,此时 redis 中的 socket01 会产生 AE_READABLE 事件,IO 多路复用程序将 socket01 压入队列,此时事件分派器从队列中获取到 socket01 产生的 AE_READABLE 事件,由于前面 socket01 的 AE_READABLE 事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。

   命令请求处理器读取 socket01 的 key value 并在自己内存中完成 key value 的设置。操作完成后,它会将 socket01 的 AE_WRITABLE 事件与命令回复处理器关联。 如果此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产生一个 AE_WRITABLE 事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操作的一个结果,比如 ok,之后解除 socket01 的 AE_WRITABLE 事件与命令回复处理器的关联。 这样便完成了一次通信。 

为啥 redis 选择单线程模型?

    Redis 从一开始就选择使用单线程模型处理来自客户端的绝大多数网络请求,这种考虑其实是多方面的,分析了相关的资料,笔者发现其中最重要的几个原因如下:

 1、 使用单线程模型能带来更好的可维护性,方便开发和调试,单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。 

2、使用单线程模型也能并发的处理客户端的请求; I/O多路复用 使用单线程模型也并不意味着程序不能并发的处理任务,Redis虽然使用单线程模型处理用户的请求,但是它却使用 I/O 多路复用机制并发处理来自客户端的多个连接,同时等待多个连接发送的请求。 在 I/O 多路复用模型中,最重要的函数调用就是 select/epoll 以及类似函数,该方法的能够同时监控多个文件描述符(也就是客户端的连接)的可读可写情况,当其中的某些文件描述符可读或者可写时,select方法就会返回可读以及可写的文件描述符个数。

 3、性能瓶颈redis选择单线程模型的决定因素) Redis 并不是 CPU 密集型的服务,如果不开启 AOF 备份,所有 Redis 的操作都会在内存中完成不会涉及任何的 I/O 操作,这些数据的读写由于只发生在内存中,所以处理速度是非常快的;整个服务的瓶颈在于网络传输带来的延迟和等待客户端的数据传输,也就是网络 I/O,所以使用多线程模型处理全部的外部请求可能不是一个好的方案。 

 注:AOF 是 Redis 的一种持久化机制,它会在每次收到来自客户端的写请求时,将其记录到日志中,每次 Redis 服务器启动时都会重放 AOF 日志构建原始的数据集,保证数据的持久性。 

 多线程虽然会帮助我们更充分地利用 CPU 资源,但是操作系统上线程的切换也不是免费的,线程切换其实会带来额外的开销, 频繁的对线程的上下文进行切换可能还会导致性能地急剧下降,这可能会导致我们不仅没有提升请求处理的平均速度,反而进行了负优化,所以这也是为什么 Redis 对于使用多线程技术非常谨慎。

 Redis 6.0前的版本是用一个线程来读取网络请求并进行解析,并根据请求的具体命令操作进行数据读写的。Redis 6.0开始,网络请求的解析可以用多线程来执行,但是读写内存还是一个线程。

为啥 redis 单线程模型也能效率这么高? 

   纯内存操作。 核心是基于非阻塞的 IO 多路复用机制。 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。 Redis 的单线程指 Redis 的网络 IO 和键值对读写由一个线程来完成的(这是 Redis 对外提供键值对存储服务的主要流程,Redis 6.0开始,网络请求的解析可以用多线程来执行) Redis 的持久化、异步删除、集群数据同步等功能是由其他线程而不是主线程来执行的,所以严格来说,Redis 并不是单线程 

Redis 6.0 版本为什么又引入了多线程? 

Redis 的瓶颈不在 CPU ,而在内存和网络,内存不够可以增加内存或通过数据结构等进行优化 但 Redis 的网络 IO 的读写占用了大部分 CPU 的时间,如果可以把网络处理改成多线程的方式,性能会有很大提升 所以总结下 Redis 6.0 版本引入多线程有两个原因 

1.充分利用服务器的多核资源 

2.多线程分摊 Redis 同步 IO 读写负荷 执行命令还是由单线程顺序执行,只是处理网络数据读写采用了多线程,而且 IO 线程要么同时读 Socket ,要么同时写 Socket ,不会同时读写。

注:单线程模型部分参考石杉老师的讲解