了解反应器模式:基于线程和事件驱动

本文翻译自Understanding Reactor Pattern: Thread-Based and Event-Driven

为了处理Web请求,有两种竞争的Web体系架构:基于线程的架构和事件驱动型架构。

基于线程的架构

实现多线程服务器最直观的方法是遵循每个连接使用一个线程来处理(One-Connection-Per-Thread)的方式。对于使用了非线程安全库而又要避免线程竞争的站点来说,这是适当的方式。

它还使用了多路处理模块(MPM)来隔离多个请求,因此单个请求中出现的问题不会影响到其他的请求。

了解反应器模式:基于线程和事件驱动_第1张图片
image.png

进程开销过重,并伴随着更慢的上下文切换,以及更多的内存消耗。因此,每个连接用一个线程处理的方式提供了更好的可伸缩性,即使多线程编程更容易出错,并更难于调试。

为了调整线程数以获得最佳的整体性能,并避免线程创建/销毁的开销,通常将一个单独的调度线程放置于一个有容量上限的阻塞队列和线程池之前。调度程序在套接字上阻塞新连接并将它们放入阻塞队列,超出队列限制的连接将被丢弃,但接受连接的延迟变得可预测。线程池轮询队列中的传入请求,然后处理并响应。

不幸的是,连接和线程之间总是存在一对一的关系。像Keep-Alive这样的长连接会导致大量的工作线程在空闲状态下等待,例如:访问文件系统,网络等。另外,数百甚至数千并发连接所创建的线程会浪费存储器中的大量堆栈空间。

事件驱动架构

事件驱动的方式能够将线程从连接中分离出来,这些连接只使用线程来处理特定的回调或处理器上的事件。

事件驱动架构由事件创建者和事件消费者组成。作为事件源的创建者只知道事件已经发生。消费者是需要知道事件已经发生的实体。它们可能参与事件处理,或者只是受事件影响。

反应器模式

反应器模式是事件驱动架构的一种实现方式。简而言之,它使用一个单线程事件轮询阻塞所有的资源并在事件触发后将它们发送到相应的处理程序或回调函数中。

我们可以定义一些事件,比如新连接传入,准备读取,准备写入等等,只要事件处理程序或回调函数被注册用来处理它们,那么我们就不再需要阻塞I/O。 这些处理程序或回调函数可以在多核环境中利用线程池技术。

该模式将模块化应用程序级代码与可重用的反应器实现分离开来。

反应器模式中有两个重要的参与者:

1. Reactor

Reactor运行在一个单独的线程中,其工作是将工作分派给适当的处理程序来对I/O事件作出反应。这就像一家公司的电话接线员接听客户电话并将该线路转接到适当的联系人。

2. Handlers

Handler 执行与I/O事件相关的实际工作。一个 Reactor 通过调度适当的 Handler 来响应I/O事件。Handlers 执行非阻塞的操作。

反应器模式的目标

Reactor 架构允许事件驱动的应用程序解复用和分配从一个或多个客户端传递给应用程序的服务请求。

一个 reactor 将持续查找事件,并在事件触发后通知相应的事件处理程序进行处理。它接收来自多个并发客户端的消息,请求和连接,并使用事件处理程序按顺序处理它们。Reactor 设计模式的目的是为了避免为每个消息,请求和连接创建单独的线程。

避免这个问题是为了避免着名的问题:C10K。

了解反应器模式:基于线程和事件驱动_第2张图片
image.png

很多时候,服务器不得不同时处理超过10000的并发连接,而 Tomcat,Glassfish,JBoss或HttpClient很难使用线程进行扩展。

而使用 reactor 的应用程序只需要使用一个线程来处理同时发生的事件。


了解反应器模式:基于线程和事件驱动_第3张图片
image.png

基本上,标准的 Reactor 模式允许在事件发生的同时保持简单的单线程。

解复用器是具有输入和多于一个输出的电路,它用来将信号发送到多个设备中的一个。

这个描述听起来与解码器类似,不过解码器用于在多个设备之间进行选择,而解复用器用于在多个设备间发送信号。


了解反应器模式:基于线程和事件驱动_第4张图片
image.png

Reactor 允许使用单个线程有效处理阻塞的多个任务。Reactor 还管理一组事件处理程序,当被用来处理一个任务时,它会连接可用的处理程序并使其处于活动状态。

事件循环

  1. 查找所有处于活动状态和解锁状态的的处理程序,或将其委托给一个调度程序。
  2. 按顺序执行这些调度程序直到完成,或达到某个阻塞点。已完成的调度程序将被置于未激活状态,允许继续执行事件循环。
  3. 重复步骤1

你可能感兴趣的:(了解反应器模式:基于线程和事件驱动)