理解Reactor模式: 基于线程和事件驱动

在web服务器开发中,有2种常见的架构:基于线程的架构和事件驱动的架构。

基于线程的架构

最初多线程server的实现一般都是采用每个连接一个线程的方法。这对于那些需要兼容非线程安全库的站点比较合适。

也有使用多进程模型来隔离每个请求,这样单个请求出问题不会影响到其它请求。

进程太重,上下文切换很慢而且内存消耗很大。因此,为了更好的扩展性,每个请求一个线程的方式更为常用。尽管多线程程序易于出错且难以调度。

为了优化线程数量以获得最佳的整体性能,同时为了避免线程创建/销毁的开销,通常在实际应用中,会在一个数量有限的阻塞队列上使用一个单独的线程用于分发,再加上一个线程池。分发线程阻塞在socket上等待新的连接,并将它们发送到阻塞队列上。虽然这种方式会使超过了队列限制的连接被drop掉,但是已接受连接上的延迟是可预知的。线程池在阻塞队列上使用poll来接受请求,然后处理并响应。

不幸地是,使用这种方式对于连接和线程来说仍然是1:1的关系。长时间存活而且不活跃的连接会导致大量的线程处于空闲状态。比如,文件访问,网络等等。另外,成百上千的并发线程会浪费大量的栈空间。

事件驱动架构

采用事件驱动架构能够分隔线程和连接,在事件驱动架构中,使用单独的线程处理事件和特定的callbacks或handlers.

事件驱动架构由事件发生器和事件消费者构成,发生器是事件的源头,只关心事件的产生.消费者是关心事件发生的实体.它们可能处理事件也可能只是被事件所影响.

Reactor模式

reactor模式是事件驱动架构的一种实现.通常,它使用一个线程进行事件循环,阻塞在产生事件的资源上,当事件到来时分发给相关的handlers和callbacks.

采用事件驱动架构不需要阻塞在I/O上,只要将handerls和callbacks注册到关心的事件上.事件实例可能是新到来的连接请求,fd上读写就绪等等.在多核环境下,可以使用线程池来处理这些handlers和callbacks.

reactor模式解耦了模块化的应用代码与可复用的reactor实现

在reactor模式中有2个重要的组成部分:

  1. Reactor
    Reactor在独立的线程中运行,它的作用是对IO事件做出响应并将它们分发给合适的handler.这有点儿像公司里的接线员将电话转接给合适的人.
  2. Handlers
    Handler对I/O事件执行实际的工作,Handler类似于客户实际想打给的人。

reactor将I/O事件分发给合适的handler,Handlers执行非阻塞的操作。

Reactor架构的好处

解决C10K问题

Reactor架构允许应用程序基于事件驱动并进行多路复用,将来自一个或多个客户的请求进行分发。

reactor将会一直监听事件并在事件触发时通知相关的事件handler处理。

Reactor模式是一种同步多路利用和按事件到达顺序处理的设计模式。

它按事件到来的顺序处理来自多个客户端的消息,请求和连接. Reactor设计模式能够避免为每个消息,请求,连接创建一个线程的问题. 它从一系列handlers上接收事件并将它们按顺序分发给相关的事件处理器。

总结: 能够用于处理C10K级别的客户端,而Tomcat, Glassfish, JBoss, or HttpClient这些每个连接一个线程的程序不能够很好的扩展规模.

使用reactor模式的程序只需要使用一个线程就能同时处理这些事件.

基本上,标准的Reactor允许一个主线程同时处理多个事件,这样能够保持线程的简单性. 

多路复用指的是在一个输入上产生多个输出。

Reactor允许使用一个线程高效地处理多个阻塞任务。Reactor同时也能够管理事件集合。当执行任务时,它连接到合适的handler并激活它。

事件循环:

  • 找到所有活跃和非阻塞的handlers,或者将这项工作代理给一个调度器实现.

  • 顺序执行每个handler直到执行完成或者它们阻塞于某个点上。完成的handlers变为不活跃状态,事件循环继续.

  • 重复执行步骤1

Node.js, Vert.x, Reactive Extensions, Jetty, Ngnix很多程序都使了reactor模式,因此你有必要对其进行了解和关注.