平时接触的开源产品如Redis、ACE,事件模型都使用的Reactor模式;而同样做事件处理的Proactor,由于操作系统的原因,相关的开源产品也少;这里学习下其模型结构,重点对比下两者的异同点;Reactor 和 Proactor 是基于事件驱动,在网络编程中经常用到两种设计模式。
说到异步IO,其实现在很难实现真正的异步,大部分情况下仍然需要阻塞在某个多路复用函数,比如select 或者 epoll 上,得到就绪描述符,然后调用注册在相应描述符上的回调函数。这种方式是现在的反应堆设计的基本思路。我截取一段反应堆模型的图给大家看看。
事件循环阻塞查看描述符是否就绪,当就绪后返回可读或可写的描述符,也有可能带外数据或者出错等情况。
因为 select 很多文章都介绍了,下面我就以 epoll 为例,貌似是2.4.6还是哪个版本以后加入的IO多路复用方式。
epoll 较select 的一些优点就不多说了,内核采用红黑树机制,大大提高了epoll 的性能。著名的 libevent Nginx等内部都采用这个机制。
反应器Reactor
在事件驱动的应用中,将一个或多个客户的服务请求分离(demultiplex)和事件分发器 (dispatch)给应用程序。
上下文
在事件驱动的应用中,同步地、有序地处理同时接收的多个服务请求。
问题
在分布式系统尤其是服务器这一类事件驱动应用中,虽然这些请求最终会被序列化地处理,但是必须时刻准备着处理多个同时到来的服务请求。在实际应用中,这些请求总是通过一个事件(如CONNECTOR、READ、WRITE等)来表示的。在有 序地处理这些服务请求之前,应用程序必须先分离和调度这些同时到达的事件。为了有效地解决这个问题,我们需要做到以下4方面:
为了提高系统的可测量性和反应时间,应用程序不能长时间阻塞在某个事件源上而停止对其他事件的处理,这样会严重降低对客户端的响应度。
为了提高吞吐量,任何没有必要的上下文切换、同步和CPU之间的数据移动都要避免。
引进新的服务或 改良已有的服务都要对既有的事件分离和调度机制带来尽可能小的影响。
大量的应用程序代码需要隐藏在复杂的多线程和同步机制之后。
解决方案
在一个或多个事件源上等待事件的到来,例如,一个已经连接的Socket描述符就是一个事件源。
将事件的分离和调度整合到处理它的服务中,而将分离和调度机制从应用程序对特定事件的处理中分离开,
也就是说分离和调度机制与特定的应用程序无关。
具体来说,每个应用程序提供的每个服务都有一个独立的事件处理器与之对应。
由 事件处理器处理来自事件源的特定类型的事件。每个事件处理器都事先注册到Reactor管理器中。
Reactor管理器使用同步事件分离器在一个或多个事件源中等待事件的发生。
当事件发生后,同步事件分离器通知Reactor管理器,最后由Reactor管理器调度和该事件相关的事件处理器来完成请求的服务。
结构
在Reactor模式中,有5个关键的参与者。
描述符(handle):
由操作系统提供,用于识别每一个事件,如Socket描述符、文 件描述符等。在Linux中,它用一个整数来表示。
事件可以来自外部,如来自客户端 的连接请求、数据等。事件也可以来自内部,如定时器事件。
同步事件分离器 (demultiplexer):
是一个函数,用来等待一个或多个事件的发生。调用者会被阻 塞,直到分离器分离的描述符集上有事件发生。
Linux的select函数是一个经常被使 用的分离器。
事件处理器接口(event handler):
是由一个或多个模板函数组成的接口。这些模板函数描述了和应用程序相关的对某个事件的操作。具体的事件处理器:是事件处理器接口的实现。它实现了应用程序提供的某个服务。每个具体的事件处理器总和一个描述符相关。它使用描述符来识别事件、识别应用程序提供的服务。
Reactor管理器(reactor):
定义了一些接口,用于应用程序控制事件调度,以及应用程序注册、删除事件处理器和相关的描述符。
它是事件处理器的调度核心。Reactor管理器使用同步事件分离器来等待事件的发生。一旦事件发生,Reactor管理器先是分离每个事件,然后调度事件处理器,最后调用相关的模板函 数来处理这个事件。
通过上述分析,我们注意到,是Reactor管理器而不是应用程序负责等待事件、分离事件和调度事件。
实际上,Reactor管理器并没有被具体的事件处理器调用,而是管理器调度具体的事件处理器,由事件处理器对发生的事件做出处理。
这就是类似Hollywood原则的“反向控制”。应用程序要做的仅仅是实现一个具体的事件处理器,然后把它注册到Reactor管理器中。
接下来的工作由管理 器来完成。
image.png
注意:这里提及的反应堆模型,实际上就是外国人设计的一个概念,将我们从面向过程编程转换为一个面向对象编程的一个东西,我们可以简单的认为,直接操作IO模型,是一个面向过程的操作,而由一个反应堆来操作的,是一个面向对象的操作,期间,面相对象操作会提高部分性能。
image.png
Reactor包含如下角色:
1.Handle 句柄;用来标识socket连接或是打开文件;
2.Synchronous Event Demultiplexer:同步事件多路分解器:由操作系统内核实现的一个函数;用于阻塞等待发生在句柄集合上的一个或多个事件;(如select/epoll;)
3.Event Handler:事件处理接口
4.Concrete Event HandlerA:实现应用程序所提供的特定事件处理逻辑;
5.Reactor:反应器,定义一个接口,实现以下功能:
1)供应用程序注册和删除关注的事件句柄;
2)运行事件循环;
3)有就绪事件到来时,分发事件到之前注册的回调函数上处理;
网友评论