【译】1995年经典论文"Reactor模式"翻译

476 阅读7分钟

原文pdf

     【金山文档】 reactor-siemens(1)   kdocs.cn/l/cbp2G02Eu…  可下载.

        这篇论文的早期版本是在 《程序设计的模式语言》ISBN 0 - 201-6073-4,由Jim Coplien和Douglas C. Schmidt编辑 由Addison-Wesley于1995年出版。

一、意图

         Reactor设计模式处理以下的服务请求: 由一个或多个并发请求发送给应用程序。应用程序中的每个服务可能包括 几个方法并用一个独立的事件handler表示,该事件负责分派特定于服务的re任务。事件处理程序的分派由一个initiation调度程序执行,它管理注册的事件handlers。业务请求的解复用由一个 同步事件信号分离器。

二、2个重要角色

事件派发器,  通知器

三、举例分析

          为了说明Reactor模式,请考虑事件驱动的模式 分布式日志服务的服务器,如图1所示。 客户端应用程序使用日志服务来记录关于它们在分布式环境中的状态的信息。该status信息通常包括错误通知、de错误跟踪和性能报告。日志记录 发送到一个中央日志服务器,它可以写 记录到各种输出设备,如控制台、打印机、 文件或网管数据库。 

          图1中所示的日志服务器处理日志 记录客户端发送的连接请求。日志记录 记录和连接请求可以同时到达 多个句柄。句柄标识操作系统内管理的网络通信资源。 

         日志服务器与客户端的交互必须使用面向连接的协议,如TCP[1]。要记录数据,必须首先向 服务器发送一个请求。服务器采用"监听向客户端暴露一个地址+端口的请求处理工厂"来等待大量请求的到来。 当一个连接请求到达时,"请求处理工厂"采用"创建一个表示端点的句柄"来实现客户端与服务端的链接建立。这个句柄被返回给服务器,然后 等待客户端服务请求到达句柄。一次 客户端连接后,可以将日志记录"并发发送"给服务器。服务器通过这些"socket句柄"处理日志数据。 

          也许是开发并发日志服务器的最直观的方法 是使用多个线程来处理的 多个客户端并发程序,如图2所示。这 方法同步接受网络连接和 生成一个“每个连接一个线程”来处理客户端日志 记录。

     然而多线程方案的实现无法解决以下问题:

  • 性能最大化(Effificiency):
    多线程可能导致频繁的线程上下文切换造成性能表现不理想、同步、数据共享. 而且在部分线程处于空闲时造成io资源浪费.
  • 开发难度(Programming simplicity):
    多线程会让并发控制方案变得非常复杂.
  • 可移植性(Portability):
    线程相关特性在每个操作系统的实现机制不一样.(这一段感觉翻译得不到位, 有更好的建议的话欢迎读者评论, 原文:Threading is not available on all OS platforms.).

综上所述不足, 多线程通常不是最有效且最完整的技术方案.

四、概述

 在分布式系统中, 应用通常会从不止一个客户端并发的接收请求

五、核心问题

        分布式系统中的服务器应用程序一般要处理大量客户端发送的各种请求。然而,在调用一个特定的服务之前,服务器应用程序必须 将每个传入请求分派到其对应的答服务提供者。开发高质量的多路复用机制需要满足以下几点: 

  • **高可用(Availability)
    **服务器(BossGroup主线程)必须保证请求到来时马上处理,即使它正在等待其他请求到达。特别是,服务器不能无限期地阻塞处理任何单一的事件源而排除其他事件事件源,因为这可能会显著延迟响应的其他客户端。

  • **高效率(Effificiency)
    **服务端最好最小化延迟、最大化吞吐量、避免不必要的cpu时间片

  • **易用性(Programming simplicity)
    **在设计上对并发策略的使用保证有的放矢、简单。

  • 灵活性(Adaptability)
    集成新的或改进的服务,例如更改消息格式或添加服务器端缓存,带来的修改和维护现有代码的成本应该降到最低。例如,实现新的应用程序服务不应该需要修改对一般事件解复用和调度机制。

  • **可移植性(Portability)
    **将应用移植到新的操作系统平台不能太费劲。  

六、落地方案

        "同步事件多路复用"被分派他们到对应的事件处理程序来处理事件。 此外,将应用程序中特定的"分发调度"和"服务实现"从通用的多路复用分离解耦出来。  

        对于应用提供的每个服务,引入了一个单独的"事件处理器"来处理某些类型的事件。 所有事件处理程序实现相同的接口。 事件处理程序注册并初始化Dispatcher,它使用同步事件多路复用器等待事件发生。 当事件发生,多路复用器通知初始化调度程序,就回调事件所关联的Handler。 事件处理程序然后分派事件发送到实现请求服务的方法。  

七、Reactor骨架

Reactor模式中的关键参与者如下(待补充):

  • Handles(句柄/描述符)操作系统提供的一种资源. 对于os的socket描述符. 事件产生的发源地. 监听事件等于监听handle.

  • Synchronous Event Demultiplexer(同步事件分离器) 本身是一种系统调用 , 用于等待事件的发生. 事件可以有多个. 没有事件时会阻塞, 一直到事件产生为止. 在netty中具体中就是I/O多路复用机制. 和Java NIO中select()职能一致.

  • Event Handler(事件处理器): 回调函数. Netty相比Java NIO多出来的特色内容.

  • Concrete Event Handler (具体事件处理器): 本身实现了事件处理器提供的各个回调.

  • Initiation Dispatcher(初始分发器): 分发事件总处理器, 不处理真正的具体业务逻辑, 来提供性能. 在最初reactor模型中充当MainReactor角色. 通过"同步事件分离器"等待事件发生. 有一堆事件发生时, selectKeySet会被遍历, 调用事件处理器"处理事件". 

    下面这种图描述了5中参与者的关系

-------------------------------------------继续啃---------------------------------------------

八、动态(这个感觉不太到位)

8.1  模块间的协作关系.

  • 当初始分发器中注册了一个"具体事件回调处理器",  根据句柄原理在事件发生时事件处理器会收到相应事件的通知.

  • 初始分发器要求每个事件处理器回传内部句柄. 这个句柄标识了事件在操作系统中的唯一性.

  • 所有事件处理器注册完毕后, 应用就会启动事件循环, 此时统管所有句柄, 而且利用_多路复用机制阻塞等待_事件在句柄上冒泡.   
    例如,  在TCP协议层利用"多路复用器" 阻塞等待客户端把日志 到达 socket描述符.

  • 当某个事件源变为"可读", 多路复用器会通知主循环(也是初始分发器). 
    例如, 某个TCP连接现在是"读就绪".

  • 就绪状态句柄上的钩子函数(hook本质上也是回调函数)被主循环触发.   
    当事件出现时,  主循环利用被事件源(在netty中就是SelectKey的set集合)激活的句柄来定位并转发具体事件处理器的钩子函数.

  • 初始分发器事件处理器的钩子方法handle_event 钩子方法,来执行对应事件的应用相关功能。发生事件的事件类型可以作为参数传递,以及被内部方法用来执行额外的服务特定的多路分离和分发操作。另一个分发方式请参阅9.4章节.

----------------------------

翻译花了一天半 ,  结果发现简书已经有一篇译文了..  先用着吧以后有时间继续翻译.

友情链接地址: 

www.jianshu.com/p/36b58ef2f…