文章目录
  1. 1. 介绍
  2. 2. 优点
  3. 3. 中间件
  4. 4. 流式处理
  5. 5. 路由
  6. 6. 静态文件中间件和动静分离
  7. 7. 缓存策略

“中间件”在软件领域是一个非常广的概念,除操作系统的软件都可以称为中间件,比如,消息中间件,ESB中间件,日志中间件,数据库中间件等等。

Connect被定义为Node平台的中间件框架,从定位上看Connect一定是出众的,广泛兼容的,稳定的,基础的平台性框架。如果攻克Connect,会有助于我们更了解Node的世界。Express就是基于Connect开发的。

介绍

Connect是一个node中间件(middleware)框架。如果把一个http处理过程比作是污水处理,中间件就像是一层层的过滤网。每个中间件在http处理过程中通过改写request或(和)response的数据、状态,实现了特定的功能。完整中间件列表

优点

  1. 模型简单
  2. 中间件易于组合和插拔:流式处理
  3. 中间件易于定制和优化
  4. 丰富的中间件

中间件

一个最朴素的原型:

function (req, res, next) {
  // 中间件
}

流式处理

中间件使用形式:

var app = connect();
// Middleware
app.use(connect.staticCache());
app.use(connect.static(__dirname + '/public'));
app.use(connect.cookieParser());
app.use(connect.session());
app.use(connect.query());
app.use(connect.bodyParser());
app.use(connect.csrf());
app.use(function (req, res, next) {
  // 中间件
});
app.listen(3001);

Conncet提供use方法用于注册中间件到一个Connect对象的队列中,我们称该队列叫做中间件队列。

Conncet的部分核心代码如下,它通过use方法来维护一个中间件队列。然后在请求来临的时候,依次调用队列中的中间件,直到某个中间件不再调用下一个中间件为止。

app.stack = [];
app.use = function(route, fn){
  // …

  // add the middleware
  debug('use %s %s', route || '/', fn.name || 'anonymous');
  this.stack.push({ route: route, handle: fn });

  return this;
};

值得注意的是,必须要有一个中间件调用res.end()方法来告知客户端请求已被处理完成,否则客户端将一直处于等待状态。

流式处理也是Node.js中用于流程控制的经典模式,Connect模块是典型的应用了它。流式处理的好处在于,每一个中间层的职责都是单一的,开发者通过这个模式可以将复杂的业务逻辑进行分解。

路由

从前文可以看到其实app.use()方法接受两个参数,route和fn,既路由信息和中间件函数,一个完整的中间件,其实包含路由信息和中间件函数。路由信息的作用是过滤不匹配的URL。请求在遇见路由信息不匹配时,直接传递给下一个中间件处理。

通常在调用app.use()注册中间件时,只需要传递一个中间件函数即可。实际上这个过程中,Connect会将/作为该中间件的默认路由,它表示所有的请求都会被该中间件处理。

中间件的优势类似于Java中的过滤器,能够全局性地处理一些事务,使得业务逻辑保持简单。

任何事物均有两面性,当你调用app.use()添加中间件的时候,需要考虑的是中间件队列是否太长,因为每一层中间件的调用都是会降低性能的。为了提高性能,在添加中间件的时候,如非全局需求的,尽量附带上精确的路由信息。

以multipart中间件为例,它用于处理表单提交的文件信息,相对而言较为耗费资源。它存在潜在的问题,那就是有可能被人在客户端恶意提交文件,造成服务器资源的浪费。如果不采用路由信息加以限制,那么任何URL都可以被攻击。

app.use("/upload", connect.multipart({ uploadDir: path }));

加上精确的路由信息后,可以将问题减小。

静态文件中间件和动静分离

一个静态文件服务器包括路由实现,MIME,缓存控制,传输压缩,安全、欢迎页、断点续传等多个技术细节,Connect的static中间件为我们提供上述所有功能。

在动静态请求混杂的场景下,静态中间件会在动态请求时也调用fs.stat来检测文件系统是否存在静态文件。这造成了不必要的系统调用,使得性能降低。

解决影响性能的方法既是动静分离。利用路由检测,避免不必要的系统调用,可以有效降低对动态请求的性能影响。

app.use('/public', connect.static(__dirname + '/public'));

缓存策略

缓存策略包含客户端和服务端两个部分。

客户端的缓存,主要是利用浏览器对HTTP协议响应头中cache-control和expires字段的支持。浏览器在得到明确的相应头后,会将文件缓存在本地,依据cache-control和expires的值进行相应的过期策略。这使得重复访问的过程中,浏览器可以从本地缓存中读取文件,而无需从网络读取文件,提升加载速度,也可以降低对服务器的压力。

默认情况下静态中间件的最大缓存时设置为0,意味着它在浏览器关闭后就被清除。这显然不是我们所期望的结果。除非是在开发环境可以无视maxAge的设置外,生产环境请务必设置缓存,因为它能有效节省网络带宽。

app.use('/public', connect.static(__dirname + '/public', {maxAge: 86400000}));

静态文件如果在客户端被缓存,在需要清除缓存的时候,又该如何清除呢?这里的实现方法较多,一种较为推荐的做法是为文件进行md5处理。

http://some.url/some.js?md5 

当文件内容产生改变时,md5值也将发生改变,浏览器根据URL的不同会重新获取静态文件。md5的方式可以避免不必要的缓存清除,也能精确清除缓存。

由于浏览器本身缓存容量的限制,尽管我们可能设置了10年的过期时间,但是也许两天之后就被新的静态文件挤出了本地缓存。这将持续引起静态服务器的响应,也即意味着,客户端缓存并不能完全解决降低服务器压力的问题。

为了解决静态服务器重复读取磁盘造成的压力,这里需要引出第二个相关的中间件:staticCache。

app.use(connect.staticCache());  
app.use(“/public”, connect.static(__dirname + '/public', {maxAge: 86400000}));

这是一个提供上层缓存功能的中间件,能够将磁盘中的文件加载到内存中,以提高响应速度和提高性能。

staticCache中间件有两个主要的选项:maxObjects和maxLength。代表的是能存储多少个文件和单个文件的最大尺寸,其默认值为128和256kb。为何会有这两个选项的设定,原因在于V8有内存限制的原因,作为缓存,如果没有良好的过期策略,缓存将会无限增加,直到内存溢出。设置存储数量和单个文件大小后,可以有效抑制缓存区的大小。

事实上,该缓存还存在的缺陷是单机情况下,通常为了有效利用CPU,Node.js实例并不只有一个,多个实例进程之间将会存在冗余的缓存占用,这对于内存使用而言是浪费的。

除此之外,V8的垃圾回收机制是暂停JavaScript线程执行,通过扫描的方式决定是否回收对象。如果缓存对象过大,键太多,则扫描的时间会增加,会引起JavaScript响应业务逻辑的速度变慢。
但是这个模块并非没有存在的意义,上述提及的缺陷大多都是V8内存限制和Node.js单线程的原因。解决该问题的方式则变得明了。

风险转移是Node.js中常用于解决资源不足问题的方式,尤其是内存方面的问题。将缓存点,从Node.js实例进程中转移到第三方成熟的缓存中去即可。这可以保证:

  • 缓存内容不冗余。
  • 集中式缓存,减少不一致性的发生。
  • 缓存的算法更优秀以保持较高的命中率。
  • 让Node.js保持轻量,以解决它更擅长的问题。

Connect推荐服务器端缓存采用varnish这样的成熟缓存代理。而原文作者是使用Redis来完成后端缓存的任务。

文章目录
  1. 1. 介绍
  2. 2. 优点
  3. 3. 中间件
  4. 4. 流式处理
  5. 5. 路由
  6. 6. 静态文件中间件和动静分离
  7. 7. 缓存策略