Redis的单线程模型

Redis的单线程模型,第1张

Redis的单线程模型
redis 和 memcached 有啥区别?

redis 支持复杂的数据结构

redis 相比 memcached 来说,拥有更多的数据结构,能支持更丰富的数据 *** 作。如果需要缓存能够支持更复杂的结构和 *** 作, redis 会是不错的选择。

redis 原生支持集群模式

在 redis3.x 版本中,便能支持 cluster 模式,而 memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。


redis 的网络线程模型:单Reactor模型

从Redis V1.0到V6.0版本之前,Redis的核心网络模型一直都是典型的单Reactor模型(单线程)

redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。

文件事件处理器的结构包含 4 个部分:

  • 多个 socket
  • IO 多路复用程序
  • 事件分派器
  • 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

多个 socket 可能会并发产生不同的 *** 作,每个 *** 作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。

来看客户端与 redis 的一次通信过程:

 

要明白,通信是通过 socket 来完成的。

首先,redis 服务端进程初始化的时候,会将 server socket 的 AE_READABLE 事件与连接应答处理器关联。

1、客户端 socket01 向 redis 的 server socket 请求建立连接,此时 server socket 会产生一个 AE_READABLE 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该事件压入队列中。文件事件分派器从队列中获取该事件,交给连接应答处理器。连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的 AE_READABLE 事件与命令请求处理器关联。

2、假设此时客户端发送了一个 set key value 请求,此时 redis 中的 socket01 会产生 AE_READABLE 事件,IO 多路复用程序将事件压入队列,此时事件分派器从队列中获取到该事件,由于前面 socket01 的 AE_READABLE 事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 key value 并在自己内存中完成 key value 的设置。 *** 作完成后,它会将 socket01 的 AE_WRITABLE 事件与命令回复处理器关联。

3、如果此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产生一个 AE_WRITABLE 事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次 *** 作的一个结果,比如 ok,之后解除 socket01 的 AE_WRITABLE 事件与命令回复处理器的关联。

这样便完成了一次通信。关于 Redis 的一次通信过程,推荐读者阅读《Redis 设计与实现——黄健宏》进行系统学习。

为什么Redis采用单线程的方式?

使用多线程的利与弊:

使用多线程可以利用到多核CPU的优势,可以提高CPU利用率

涉及到线程安全问题,涉及到锁竞争问题

线程的创建和销毁,线程的上下文切换,都是额外的开销

1、在我们的日常系统中,主要可以区分为两种:CPU 密集型 和 IO 密集型。

CPU 密集型:

线程的工作时段中大部分被CPU占据

这种系统就说明 CPU 的利用率很高,CPU处于忙碌中,增加线程也不会压榨出更多的CPU时间,而且增加线程还会导致线程的上下文切换而带来额外的开销,所以使用多线程效率可能会不升反降。

IO 密集型:

线程的工作时段中大部分被IO占据

IO *** 作也可以分为磁盘 IO 和网络 IO 等 *** 作。当线程进行IO *** 作时,CPU处于空闲状态,此时为了更多地压榨出CPU的时间,为了更好的利用CPU,开出多个线程,当A线程在等待IO的过程中,B线程可以利用CPU的时间片。

2、Redis的瓶颈并不在于CPU,而在于网络IO

比较通俗的解释一下就是说,Redis的文件事件处理器中,开启单个线程就足可以应对几万/秒的网络并发请求了,几万/秒的网络并发请求出现的socket事件,开一个线程去应对已足够。

其中最主要的原因在于IO多路复用,因为有IO多路复用机制,所以Redis的文件事件处理器的那个线程并不会在某个socket事件阻塞等待,而是通过监听机制,发现文件描述符可读或可写了之后去 *** 作就可以了,而且所有 *** 作都是在内存中进行,所以这个线程工作时段中大部分都被CPU占据。

当然,对于多核CPU *** 作系统而言,如果Redis的文件事件处理器的线程数=CPU核数,那么此时文件事件处理器可以处理更多的网络IO请求。


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

  • 纯内存 *** 作。
  • 核心是基于非阻塞的 IO 多路复用机制。
  • C 语言实现,一般来说,C 语言实现的程序“距离” *** 作系统更近,执行速度相对会更快。
  • 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。

4、如何实现Redis的多线程呢?

对于那些想利用多核优势提升性能的用户来说,Redis 官方给出的解决方案也非常简单粗暴:在同一个机器上多跑几个 Redis 实例。事实上,为了保证高可用,线上业务一般不太可能会是单机模式,更加常见的是利用 Redis 分布式集群多节点和数据分片负载均衡来提升性能和保证高可用。

Redis真的是单线程吗?

所谓的单线程,只是说 Redis 的处理客户端的网络请求(即执行命令)时,是单线程去执行的,并不是说整个 Redis 都是单线程。

V4.0之前:整个Redis都是单线程的

V4.0:引入多线程处理异步任务

V4.0 - V6.0:核心网络模型中是单线程的,整体是多线程的

V6.0:正式在网络模型中实现IO多线程

引入多线程处理异步任务:

Redis 在 v4.0 版本的时候就已经引入了的多线程来做一些异步 *** 作,此举主要针对的是那些非常耗时的命令,通过将这些命令的执行进行异步化,避免阻塞单线程的事件循环。

我们知道 Redis 的 DEL 命令是用来删除掉一个或多个 key 储存的值,它是一个阻塞的命令,大多数情况下你要删除的 key 里存的值不会特别多,最多也就几十上百个对象,所以可以很快执行完,但是如果你要删的是一个超大的键值对,里面有几百万个对象,那么这条命令可能会阻塞至少好几秒,又因为事件循环是单线程的,所以会阻塞后面的其他事件,导致吞吐量下降。

于是,在 Redis v4.0 之后增加了一些的非阻塞命令如 UNlink、FLUSHALL ASYNC、FLUSHDB ASYNC。

UNlink 命令其实就是 DEL 的异步版本,它不会同步删除数据,而只是把 key 从 keyspace 中暂时移除掉,然后将任务添加到一个异步队列,最后由后台线程去删除,不过这里需要考虑一种情况是如果用 UNlink 去删除一个很小的 key,用异步的方式去做反而开销更大,所以它会先计算一个开销的阀值,只有当这个值大于 64 才会使用异步的方式去删除 key,对于基本的数据类型如 List、Set、Hash 这些,阀值就是其中存储的对象数量。

Redis 多线程网络模型

前面提到 Redis 最初选择单线程网络模型的理由是:CPU 通常不会成为性能瓶颈,瓶颈往往是内存和网络,因此单线程足够了。那么为什么现在 Redis 又要引入多线程呢?很简单,就是 Redis 的网络 I/O 瓶颈已经越来越明显了。

参考:

Redis之线程模型_猎户星座-CSDN博客

Redis 多线程网络模型全面揭秘 - 知乎

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5687242.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存