注册 登录
  • 欢迎访问"运维那点事",推荐使用Google浏览器访问,可以扫码关注本站的"微信公众号"。
  • 如果您觉得本站对你有帮助,那么可以扫码捐助以帮助本站更好地发展。

Redis网络架构及单线程模型

Redis 彭东稳 5991次浏览 已收录 0个评论

这篇博客主要介绍我对Redis网络层架构以及线程模型的一些了解,希望能对大家有所帮助。

一、网络基础架构

网络编程离不开Socket,网络I/O模型最常用的无非是同步阻塞、同步非阻塞、异步阻塞、异步非阻塞。高性能网络服务器最常见的线程模型也就是基于EventLoop模式的单线程模型。我们看看Redis的网络架构是怎么样的:

Redis网络架构及单线程模型

这里解释下上图涉及的组件,Redis网络层基础组件主要包括四个部分:

1)EventLoop事件轮询器,这部分实现在AE里面。

2)提供Socket句柄事件的多路复用器,这部分分别对于不同平台提供了不同的实现,比如epoll和select可以用于Linux平台、kqueue可以用于苹果平台、evpoll可以用于Solaris平台,这里并没有看到iocp,也就是Redis对于Windows支持并不是很好。

3)包括网络事件处理器实现的networking,这部分主要包括两个重要的今天要讲的事件处理器:acceptTcpHandler和acceptCommonHandler。

4)处理网络比较底层的部分,比如网络句柄创建、网络的读写等。

二、单进程单线程模型

要理解Redis的单线程模型,我们先抛出一些问题,当我们有多个客户端同时去跟Redis Server建立连接,之后又同时对某个key进行操作,这个过程中发生了什么呢?会不会有并发问题?这些问题先丢在这了,我们看看Redis启动初始化的过程中会做什么事情,这里尽量省略了与本文无关的部分:

1)初始化Redis Server参数,这部分代码通过initServerConfig实现。

2)初始化Redis Server,这部分代码在initServer里面。

3)启动事件轮询器。

对,这里我们就把Redis的启动部分简化为三步,跟网络操作有关的主要在第二步和第三步里面,来看看initServer里面发生了什么:

initServer流程

Redis网络架构及单线程模型

initServer里面首先创建了一个EventLoop,然后监听Server的IP对应的端口号,假设我们监听的是 127.0.0.1:3333 这个IP:端口对,我们得到的一个Server Socket句柄,最后通过createFileEvent将我们得到的Server Socket句柄和我们关心的网络事件mask注册到EventLoop上面。EventLoop是什么呢,我们看看它的定义:

上面我们关注的主要是两个东西:events和fired。他们分别是两个数组,events用于存放被注册的事件以及相应的句柄,fired用于存放当EventLoop线程从多路复用器轮询到有事件的句柄的时候,EventLoop线程会把它放入fired数组里面,然后处理。

Redis网络架构及单线程模型

我用上面的示意图描述createFileEvent做的事情,就是将Server Socket句柄和关心的事件mask以及当事件产生的时候的事件处理器accptHandler生成一个aeFileEvent注册到EventLoop的events的数组里面,当然在这之前会首先将事件注册到多路复用器上,也就是epoll、kqueue等这些组件上。事件注册完之后需要对多路复用器进行轮询,来分离我们关心切发生的事件,那就是最后一步,启动事件轮询器。

接收网络连接

上面的步骤完成了服务端的网络初始化,而且事件轮询器已经开始工作了,事件轮询器做什么事情呢,就是不断轮询多路复用器,看看之前注册的事件有没有发生,如果有发生,则将会将事件分离出来,放入EventLoop的fired数组中,然后处理这些事件。

很显然,上面注册的事件是客户端建立连接这个事件,因此当有两个客户端同时连接Redis服务器的时候,事件轮询器会从多路复用器上面分离出这个事件,同时调用acceptHandler来处理。acceptHandler做的事情主要是accept客户端的连接,创建socket句柄,然后将socket句柄和读事件注册到EventLoop的events数组里面,不一样的是对于客户端的事件处理器是readQueryClient。

Redis网络架构及单线程模型

上面示意图表示了acceptHandler处理客户端连接,得到句柄之后再将这个句柄注册到多路复用器以及EventLoop上的示意图。之后再同样再处理下一个客户端的连接,这些都是串行的。

事件轮询

上面接收客户端这部分其实都发生在事件轮询的主循环里面:

Redis会不断的轮询多路复用器,将网络事件分离出来,如果是accept事件,则新接收客户端连接并将其注册到多路复用器以及EventLoop中,如果是查询事件,则通过读取客户端的命令进行相应的处理,这一切都是单线程,顺序的执行的,因此不会发生并发问题。

三、高性能单线程模型

根据官方的测试结果《How fast is Redis?》来看,在操作内存的情况下,CPU 并不能起到决定性的作用,反而可能带来一些其他问题。比如锁,CPU 切换带来的性能开销等。这一点我们可以根据官方的测试报告,提供的数据来证明。而且官方提供的数据是可以达到100000+的QPS(每秒内查询次数),这个数据并不比采用单进程多线程 Memcached 差!所以在基于内存的操作,CPU 不是 Redis 瓶颈的情况下,瓶颈在网络 I/O 上面,我们一般提供较好的网络环境就可以提升Redis的吞吐量,比如提高网络带宽,除此之外还可以通过合并命令提交批处理请求来代替单条命令一次次请求从而减少网络开销,提高吞吐量。

好了,说完单线程设计后,我们再来讨论讨论单线程的设计为什么能支持高并发?原因基本有以下几点:

第一,我们请求 Redis 更多的是操作内存。直接操作内存就很快啊,数据存在内存中,类似于 HashMap。HashMap 的优势就是查找和操作的时间复杂度都是 O(1)。

第二,单线程,没有 CPU 上下文切换带来的开销问题。而且上面也说了,内存操作和 CPU 的多核影响不大。直接采用单线程,就不用考虑各种锁,与之相关的加锁,解锁,死锁等问题就不复存在了。

第三,多路 IO 复用。这个后面我会具体的来讲讲它。能谈到这一点说明对 Redis 有一定的理解。这涉及到基于操作系统的网络 IO 模型。Reactor 网络模式,epoll,poll,select,kqueue 等多路复用 IO。

第四,依赖第二点。由于是单线程的,所以就存在一个顺序读写问题。大家可以比较以下,随机读写和顺序读写的速度。

第五,Redis 的数据结构,是经过专门的研究和设计的。所以操作起来简单且快。

第六,Redis 自己构建了VM 机制 。因为一般的调用系统函数,会浪费一定的时间。

综合以上内容,Redis 才有单线程,高性能的特点。

最后,再说一点,Redis 是单进程和单线程的设计,并不是说它不能多进程多线程。比如备份时会 fork 一个新进程来操作;再比如基于 COW 原理的 RDB 操作就是多线程的。


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (1)or分享 (0)
关于作者:

您必须 登录 才能发表评论!