设计模式 in real world - 反应器模式(Reactor Pattern)

3,048 阅读5分钟

前言

从最早看的《Head First 设计模式》到后面看的一些书,里面对于设计模式的讲解大部分都是从抽象概念介绍一个设计模式是什么和一些例子。这些例子大多都是买咖啡(head first真的喜欢以咖啡举例),玩具鸭子之类的。我也看过类似《Javascript设计模式与开发实践》,里面举例的时候会以一些作者实际开发中碰到的例子。这个系列是我日常学习中遇到设计模式的一个总结,希望能帮助到大家。

文章应该会在我的学习过程中不断更新。

正文

今天在研究《Node.js design patterns 2nd》的时候提到了反应器模式,我总结下自己所学和研究并写篇博客总结一下。

反应器模式的介绍

反应器设计模式(Reactor pattern)是一种为处理服务请求并发 提交到一个或者多个服务处理程序的事件设计模式。当请求抵达后,服务处理程序使用解多路分配策略,然后同步地派发这些请求至相关的请求处理程序。

The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers. --GoF

反正我们不玩定义。我们先以一个餐饮店为例,假设每一个人来就餐就是一个I/O操作,他会先看一下菜单,然后点餐。就像一个网站会有很多的请求,要求服务器做一些事情。处理这些就餐事件的就是我们需要解决的问题了。

多线程的并发解决方案

在多线程处理的方式会是这样的:

一个人来就餐,一个服务员去服务,然后客人点菜。 服务员将菜单给后厨。

十个人来就餐,十个服务员去服务……

这个就是多线程的处理方式,一个请求到来,就会有一个线程服务。很显然这种方式在人少的情况下会有很好的用户体验,每个客人都感觉自己是VIP,专人服务的。如果餐厅一直这样同一时间最多来10个客人,这家餐厅是可以很好的服务下去的。

那么问题来了,如果这家餐厅生意非常好,同时就餐人数(网站并发访问量)达到100人呢,给每个分配个服务员那餐馆怕不是要破产。

尽管可以考虑使用类似线程池的方法,组成一个10个服务员的线程池,一个服务员服务完一个人后继续去服务下一个。 但是这样有一个比较严重的缺点就是,如果某个客人点菜很慢(读写文件,网路请求等操作和内存读写比起来非常的慢),其他人可能就要等好长时间了。

事件驱动的解决方案

其实当客人在点菜的时候,大部服务员服务的时候都在等着客人的菜上好后才去服务下一个(阻塞),其实干的活不是太多。如果客人需要点餐的时候直接告诉服务员,我需要宫保鸡丁,做好之后直接放到14桌,然后服务员就可以去服务下一个人了,这样是不是能够有效利用服务员的资源?

其实这就是基于事件的解决方案,“做好之后放到14桌”其实就是回调函数。

反应器模式

先直接贴一下Douglas C. Schmidt 大佬的论文 An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events.pdf

Node.js中的反应器模式(Reactor pattern)

Node.js 这几年非常火,借助 js 天生的事件驱动机制和 V8高性能引擎,让编写高并发的web应用门槛降低了许多,当然这背后还要得益于基于事件驱动的 Reactor 模式,让本身只支持单线程执行的 js 能够胜任如今高并发环境下的服务端应用。

下面这张图就是一个应用里面使用Reactor模式的图示

当应用使用Reactor模式的时候发生了如下过程:

  1. 应用通过向Event Demultiplexer提交一个请求生成一个新的I/O操作。应用同时指定一个handler,这个handler会在操作完成时被调用,其实就是回调函数或者叫事件处理函数。向Event Demultiplexer提交一个请求是非阻塞(non-blocking)的调用,会立即返回。
  2. 当一系列I/O操作完成后,Event Demultiplexer会把对应触发的event的项推进Event Queue
  3. 与此同时,Event Loop会遍历所有Event Queue里面的每一项。
  4. 对于每个事件event,相应的处理程序handler会被执行。
  5. 当处理程序handler执行完后会把控制流交还给Event Loop(5a),然而有可能处理程序中会产生新的异步操作(5b),导致在控制流回到Event Loop之前会向Event Demultiplexer中插入新的项。
  6. Event Queue中的所有项都执行完后,这个循环会在Event Demultiplexer中被关闭,当再次有新事件被加入Event Queue时再触发另一个循环。

前端的同学对于Event Loop应该比较熟吧。

现在再来重新看一下定义是不是清晰了一点?

反应器设计模式(Reactor pattern)是一种为处理服务请求并发 提交到一个或者多个服务处理程序的事件设计模式。当请求抵达后,服务处理程序使用解多路分配策略,然后同步地派发这些请求至相关的请求处理程序。

随便扯扯

其实Event Demultiplexer之前我就有过一些了解不过没有深入,当时是在做计算机网络大作业的时候,当时选题是做一个Web Server,类似Apache、Nginx那种。当时研究高并发模型的时候接触到了I/O多路复用(multiplexing),其实就是事件驱动,当时用的libevent的库,好像是基于的linux的epoll系统调用实现的。