【译】reactor-siemens

原文地址: http://www.dre.vanderbilt.edu/~schmidt/PDF/reactor-siemens.pdf

论文名称
Reactor
An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events

作者信息
Douglas C. Schmidt
[email protected]
Department of Computer Science
Washington University, St. Louis, MO1

译者水平有限, 如有错误和疏漏, 欢迎指正

这篇论文的早期版本出现再书籍 “Pattern Languages of Program Design”(ISBN 0- 201-6073-4)中, 这本书由 Jim Coplien 和 Douglas C. Schmidt 编辑, 并由Addison-Wesley在1995年发布.

1. 意向

Reactor模式处理从一个或者多个客户端并发地发送来的服务请求. 应用中的每个服务由一些方法构成, 并且服务由负责分发特定于服务的请求独立事件处理器表示. 分发事件由一个 "启动调度器" 负责, 启动调度器管理注册的事件处理器. 分解服务请求由一个同步事件复用器负责.

2. 别称

Dispatcher(分发程序, 调度程序), Notifier(通知程序)

3. 示例

图1中为分布式日志服务设计的事件驱动服务说明了Reactor模式. 客户端程序使用日志服务来记录分布式环境下的状态信息. 状态信息一般包括 错误通知, debug跟踪信息以及性能报告. 日志记录发送到中央日志服务, 日志服务可以将日志信息记录到多种终端设备, 比如 控制台, 打印机, 文件或者网络数据库.

图1中的日志服务处理由客户端发送的日志记录和连接请求. 日志记录和连接请求可能通过多个句柄并发地发送到服务器. 句柄代表由操作系统管理的网络通信资源.

日志服务使用面向连接的协议与客户端通信, 如 TCP. 客户端必须向服务器发送连接请求, 之后才能记录日志. 服务器使用一个监听指定地址的句柄工厂等待客户端的请求连接, 监听的地址事先告知客户端. 当连接请求达到, 句柄工厂创建一个代表连接端点的新句柄, 从而建立客户端和服务的连接. 句柄返回给日志服务, 日志服务在返回的句柄上等待客户端请求的达到. 一旦客户端连接建立, 客户端可以并发地向服务发送日志记录请求. 日志服务通过连接上的socket句柄接收请求.

图1 分布式日志服务

开发一个并发地日志服务最直观的方式也许是使用多线程, 可以并发地处理多个客户端, 如图2所示. 这种方式同步地接收网络连接, 使用为每个连接分配一个线程的方式处理客户端日志记录请求.

图2多线程日志服务

但是, 使用多线程实现的日志服务不能解决以下问题:

  • 效率: 由于上下文切换, 同步以及数据迁移等因素, 多线程可能导致低性能.
  • 编程简洁性: 多线程会需要复杂的并发控制
  • 可移植性: 多线程并不是在所有的操作系统平台上都可用

由于这些缺点, 开发并发日志服务时, 多线程通常不是最有效的, 也不是最简洁的解决方案.

4. 上下文

分布式系统中从一个或多个客户端并发地接收事件的服务应用.

5. 问题

分布式系统中的服务应用必须处理发送请求的多个客户端. 然而在调用具体服务之前, 服务器应用必须多路分离以及分发每一个连接进来的请求到对应的服务提供者. 为了多路分离和分发客户端请求开发的高效服务机制需要解决以下问题:

  • 可用性: 即使服务器在等待其他的请求到达, 也要能够已经处理连接进来的请求. 特别地,服务器不能在排除其他事件源的情况下无限期地阻塞处理任何单一事件源,因为这可能会显著延迟对其他客户端的响应.
  • 效率: 服务必须最小化延迟, 最大化吞吐量并且避免不必要的CPU使用.
  • 编程简洁性: 服务的设计应该简化合适的并发策略的使用
  • 可扩展性: 集成新的服务或者改进服务, 比如改变消息格式或者增加服务端缓存, 应该最小化对原有代码的更改以及维护成本. 比如, 实现新的应用服务不应该需要更改原有的多路复用和请求分发机制.
  • 可移植性: 将服务器移植到新的OS平台无需太多的工作

6. 解决方案

将事件的同步多路复用程序, 和处理事件对应的事件处理器分发程序, 整合起来. 另外, 将特定于应用程序的调度和服务实现与通用事件解复用和调度机制解耦.

为应用提供的每个服务, 引入单独 Event Handler (事件处理器) 来处理特定类型的事件. 所有的Event Handler 实现同一个接口. 将Event Handler 注册到 Initiation Dispatcher(初始分发器), Initiation Dispatcher使用Synchronous Event Demultiplexer(同步事件多路复用器)等待事件的发生. 事件发生后, Synchronous Event Demultiplexer通知Initiation Dispatcher, Initiation Dispatcher同步地回调关联了事件的Event Handler. 然后Event Handler将事件处理分发给实现了请求的服务的方法.

7. 结构

Reactor模式主要的组成部件如下:

Handles

  • Handle(句柄)标识OS管理的资源. 这些资源一般包括网络连接, 打开的文件, 定时器, 同步对象等. 日志服务中的句柄标识socket端点, 同步事件多路复用器在句柄上阻塞, 等待事件的发生. 日志服务关注的两种类型的事件是连接事件以及读事件,分别代表达到的客户端连接以及记录日志请求。日志服务为每一个客户端维护一个独立的连接。每个连接由socket句柄表示。

Synchronous Event Demultiplexer

  • Synchronous Event Demultiplexer(同步事件多路复用器)在句柄集合上阻塞等待事件的发生。当其中一个句柄可以无阻塞地发起操作时,同步事件多路复用器从阻塞方法返回。一般的IO多路复用器是select, 一个由UNIX和WIN32提供的事件多路复用的系统调用。 select 调用指示哪些句柄可以在不阻塞应用程序进程的情况下对其同步调用操作。

Initiation Dispatcher

  • Initiation Dispatcher(初始分发器)定义用于注册,取消注册,分发 Event Handler(事件处理器)的接口。根本上,同步事件多路复用器负责等待新事件的发生。当多路复用器检测到新事件时,通知初始分发器回调事件处理器。通常事件包括连接接受事件,数据输入输出事件,以及超时事件。

Event Handler

  • Event Handler(事件处理程序)指定一个由钩子方法组成的接口,该钩子方法抽象地表示服务特定事件的调度操作。钩子方法必须由应用特定的服务实现。

Concrete Event Handler

  • Concrete Event Handler(具体事件处理器)实现钩子方法,同样实现应用中处理这些事件的方法。应用将具体事件处理器注册到初始分发器,用以处理特定类型的事件。每当事件到达,初始分发器回调合适的具体事件处理器的钩子方法。日志服务中有两个具体事件处理器: LoggingHandlerLoggingAcceptorLoggingHandler 负责接收和处理日志记录请求。 LoggingAcceptor 创建和连接LoggingHandlerLoggingHandler 处理后续来自客户端的日志记录请求。

Reactor模式的部件结构如下OMT类图:

OMT class diagram

8. Dynamics

8.1 模块间的协作关系

Reactor模式中模块的协作关系如下:

  • 应用注册具体事件处理器到初始分发器, 来向初始分发器声明具体事件处理器感兴趣的事件类型, 当在事件处理器关联的句柄上有事件发生时, 初始分发器通知到具体事件处理器
  • 初始分发器请求每个事件处理器返回其内部句柄。此句柄 标识操作系统的事件处理程序.
  • 所有事件处理器注册完毕之后, 应用调用handle_events 方法开启初始分发器的事件循环. 此时, 初始分发器结合已注册事件处理器的句柄,使用同步事件多路复用器在这些句柄上等待事件的发生。比如,TCP协议层使用同步事件多路复用操作在已连接的socket句柄上等待客户端的日志记录请求。
  • 当对应事件源的句柄变得“可用”时,如TCP socket连接做好了“读”准备,同步事件多路复用器通知初始分发器。
  • 初始分发器触发在准备好的句柄上,触发对应事件的事件处理器钩子方法。事件发生时,事件源作为“key“激活句柄,初始分发器使用激活的句柄定位和分发到合适的事件处理器钩子方法。
  • 初始分发器事件处理器的钩子方法handle_event 钩子方法,来执行对应事件的应用相关功能。发生事件的事件类型可以作为参数传递,以及被内部方法用来执行额外的服务特定的多路分离和分发操作。另一个可选的分发方法在9.4节有描述

下面的交互图说明了Reactor模式中应用代码和模块之间的协作关系:

sequence diagram
8.2 协作场景

日志服务中的Reactor模式协作场景有两部分。这两个场景展示了日志服务是如何使用响应式事件来分发来自多个客户端的句柄连接请求以及日志数据记录请求的。

8.2.1 客户端连接到响应式日志服务

第一个场景展示客户端连接到日志服务的步骤:

连接到日志服务步骤

步骤总结如下:

  1. 日志服务注册Logging Acceptor 到初始分发器来处理连接请求(1);
  2. 日志服务调用初始分发器的handle_events 方法(2);
  3. 初始分发器调用同步事件多路复用器的select 方法,等待连接请求或者日志数据的达到(3);
  4. 客户端连接到日志服务器(4);
  5. 初始分发器通知Logging Acceptor 有新连接达到(5);
  6. Logging Acceptor 接受新连接(6);
  7. Logging Acceptor 创建一个Logging Handler 来为新客户端服务(7);
  8. Logging Handler 将它的socket句柄注册到初始分发器,并且要求,当socket可读时,初始分发器通知它(8);
8.2.2 客户端发送日志记录请求到响应式日志服务

第二个场景展示响应式日志服务处理日志记录请求的步骤:

日志服务处理请求步骤

步骤总结如下:

  1. 客户端发送日志记录请求(1);
  2. 当客户端日志记录请求在socket上列队时,初始分发器通知关联的Logging Handler(2);
  3. 以非阻塞的方式接收日志记录(重复步骤2和3直到日志记录被完全接收);
  4. Logging Handler 处理日志记录并写到标准输出(4);
  5. Logging Handler 返回到初始分发器的事件循环(5)。

自此reactor模式该篇论文对reactor模式的核心已经翻译完毕,后面关于C++实现reactor模式,使用reactor的框架,以及reactor模式的优缺点不想翻译 哈哈哈

你可能感兴趣的:(【译】reactor-siemens)