写点什么

深入浅出 Node.js(八):Connect 模块解析(之二)静态文件中间件

2012 年 7 月 26 日

上一篇专栏简单介绍了Connect 模块的基本架构,它的执行模型十分简单,中间件机制也使得它十分易于扩展,具备良好的可伸缩性。在Connect 的良好机制下,我们本章开始将逐步解开Connect 生态圈中中间件部分,这部分给予Connect 良好的功能扩展。

静态文件中间件

也许你还记得我曾经写过的 Node.js 静态文件服务器实战,那篇文章中我叙述了如何利用 Node.js 实现一个静态文件服务器的许多技术细节,包括路由实现,MIME,缓存控制,传输压缩,安全、欢迎页、断点续传等。但是这里我们不需要去亲自处理细节,Connectstatic中间件为我们提供上述所有功能。代码只需寥寥 3 行即可:

复制代码
var connect = require('connect');
var app = connect();
app.use(connect.static(__dirname + '/public'));

在项目中需要临时搭建静态服务器,也无需安装 apache 之类的服务器,通过 NPM 安装 Connect 之后,三行代码即可解决需求。
这里需要提及的是在使用该模块的一点性能相关的细节。

动静分离

前一章提及,app.use()方法在没有指定路由信息时,相当于app.use("/", middleware)。这意味着静态文件中间件将会在处理所有路径的请求。在动静态请求混杂的场景下,静态中间件会在动态请求时也调用fs.stat来检测文件系统是否存在静态文件。这造成了不必要的系统调用,使得性能降低。
解决影响性能的方法既是动静分离。利用路由检测,避免不必要的系统调用,可以有效降低对动态请求的性能影响。

复制代码
app.use('/public', connect.static(__dirname + '/public'));

在大型的应用中,动静分离通常无需到一个 Node.js 实例中进行,CDN 的方式直接在域名上将请求分离。小型应用中,适当的进行动静分离即可避免不必要的性能损耗。

缓存策略

缓存策略包含客户端和服务端两个部分。
客户端的缓存,主要是利用浏览器对 HTTP 协议响应头中cache-controlexpires字段的支持。浏览器在得到明确的相应头后,会将文件缓存在本地,依据cache-controlexpires的值进行相应的过期策略。这使得重复访问的过程中,浏览器可以从本地缓存中读取文件,而无需从网络读取文件,提升加载速度,也可以降低对服务器的压力。
默认情况下静态中间件的最大缓存时设置为 0,意味着它在浏览器关闭后就被清除。这显然不是我们所期望的结果。除非是在开发环境可以无视maxAge的设置外,生产环境请务必设置缓存,因为它能有效节省网络带宽。

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

maxAge选项的单位为毫秒。YUI3 的 CDN 服务器设置过期时间为 10 年,是一个值得参考的值。
静态文件如果在客户端被缓存,在需要清除缓存的时候,又该如何清除呢?这里的实现方法较多,一种较为推荐的做法是为文件进行 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}));

这是一个提供上层缓存功能的中间件,能够将磁盘中的文件加载到内存中,以提高响应速度和提高性能。
它的官方测试数据如下:

复制代码
static(): 2700 rps
node-static: 5300 rps
static() + staticCache(): 7500 rps

另一个专门用于静态文件托管的模块叫node-static,其性能是 Connect 静态文件中间件的效率的两倍。但是在缓存中间件的协助下,可以弥补性能损失。
事实上,这个中间件在生产环境下并不推荐被使用,而且它将在 Connect 3.0 版本中被移除。但是它的实现中有值得玩味的地方,这有助于我们认识 Node.js 模型的优缺点。
staticCache中间件有两个主要的选项:maxObjectsmaxLength。代表的是能存储多少个文件和单个文件的最大尺寸,其默认值为 128 和 256kb。为何会有这两个选项的设定,原因在于 V8 有内存限制的原因,作为缓存,如果没有良好的过期策略,缓存将会无限增加,直到内存溢出。设置存储数量和单个文件大小后,可以有效抑制缓存区的大小。
事实上,该缓存还存在的缺陷是单机情况下,通常为了有效利用 CPU,Node.js 实例并不只有一个,多个实例进程之间将会存在冗余的缓存占用,这对于内存使用而言是浪费的。
除此之外,V8 的垃圾回收机制是暂停 JavaScript 线程执行,通过扫描的方式决定是否回收对象。如果缓存对象过大,键太多,则扫描的时间会增加,会引起 JavaScript 响应业务逻辑的速度变慢。
但是这个模块并非没有存在的意义,上述提及的缺陷大多都是 V8 内存限制和 Node.js 单线程的原因。解决该问题的方式则变得明了。
**风险转移** 是 Node.js 中常用于解决资源不足问题的方式,尤其是内存方面的问题。将缓存点,从 Node.js 实例进程中转移到第三方成熟的缓存中去即可。这可以保证:

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

Connect 推荐服务器端缓存采用varnish这样的成熟缓存代理。而笔者目前的项目则是通过Redis来完成后端缓存的任务。

关于作者

田永强,新浪微博 @朴灵,前端工程师,曾就职于 SAP,现就职于淘宝,花名朴灵,致力于 NodeJS 和 Mobile Web App 方面的研发工作。双修前后端 JavaScript,寄望将 NodeJS 引荐给更多的工程师。兴趣:读万卷书,行万里路。个人 Github 地址: http://github.com/JacksonTian


给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2012 年 7 月 26 日 00:0014356

评论

发布
暂无评论
发现更多内容

李艺:建立订阅者意识,当好一名知识服务生,做好知识课程

李艺

知识付费

就餐卡系统架构设计文档

极客大学架构师训练营

来了!8M/S+速度,Pdown复活!

程序员生活志

神器工具:新一代多系统启动 U 盘装机解决方案

JackTian

工具软件 U盘启动盘 安装操作系统 ventoy ISO 镜像文件

每日一题-翻转字符串里的单词

王传义

LeetCode

MySQL InnoDB存储引擎 - 事务

Arthur

【极客大学】【架构师训练营】【第二周】总结:设计原则

NieXY

极客大学架构师训练营

Redis系列(三):缓存过期该如何剔除?RDB和AOF又是什么?

z小赵

Java redis 高并发 高并发系统设计

别兜售你自己不会购买的东西

Neco.W

创业 销售管理 销售

网络性能篇 (13讲)

王传义

ARTS Week5

时之虫

ARTS 打卡计划

架构师训练营 - 第三周学习总结

清风徐徐

计算机操作系统基础(一)---操作系统概览

书旅

php laravel 多线程 操作系统 进程

计算机操作系统基础(四)---进程管理之进程同步

书旅

php laravel 多线程 操作系统 进程

设计原则与设计模式

dapaul

极客大学架构师训练营

wee1作业总结

极客大学架构师训练营

架构师训练营:第四周第一节,互联网架构系统架构的演化

zcj

极客大学架构师训练营

食堂就餐卡系统设计

John

极客大学架构师训练营

ARTS Week5

丽子

第四周 学习总结

冯凯

LeetCode 655. Print Binary Tree

liu_liu

算法 LeetCode

计算机操作系统基础(二)---进程管理之进程实体

书旅

php laravel 多线程 操作系统 进程

计算机操作系统基础(三)---进程管理之五状态模型

书旅

php laravel 多线程 操作系统 进程

Week2:作业一

车小勺的男神

如何让你的大脑更健康

兆熊

设计模式之单例模式和组合模式

dapaul

极客大学架构师训练营

查找算法系列文(一)一文入门二叉树

淡蓝色

Java 数据结构 算法 二叉树

SpringBean的生命周期

编号94530

Java spring Spring Boot 生命周期

多个maven项目启动顺序

冬月末

maven

iOS & Android 去马赛克处理

liu_liu

ios android 去马赛克

微信支付的软件架构究竟有多牛逼...

程序员生活志

微信 架构

InfoQ 极客传媒开发者生态共创计划线上发布会

InfoQ 极客传媒开发者生态共创计划线上发布会

深入浅出Node.js(八):Connect模块解析(之二)静态文件中间件-InfoQ