七天学会NodeJS(四)一边读取一边输出响应、守护进程(child process、spawn)、功能-性能-稳定性-代码部署
文章目录
总结:
合并静态文件示例
本章小结
process补充
SIGKILL
是告诉进程要立即终止的信号,理想情况下,其行为类似于process.exit()
。
SIGTERM
是告诉进程要正常终止的信号。它是从进程管理者(如upstart
或supervisord
)等发出的信号。可以从程序内部另一个函数中发送此信号:
process.kill(process.pid, 'SIGTERM')
或从另一个正在运行的 Node.js 程序、或从系统中运行的其他任何的应用程序(能知道要终止的进程的 PID)。
Child Process
- 官方文档: http://nodejs.org/api/child_process.html
- 使用child_process模块可以创建和控制子进程。
- 该模块提供的API中最核心的是.spawn,其余API都是针对特定使用场景对它的进一步封装,算是一种语法糖。
- 使用.spawn(exec, args, options)方法,创建子进程,该方法支持三个参数。
- 第一个参数是执行文件路径,可以是执行文件的相对或绝对路径,也可以是根据PATH环境变量能找到的执行文件名。
- 第二个参数中,数组中的每个成员都按顺序对应一个命令行参数。
- 第三个参数可选,用于配置子进程的执行环境与行为。
第一次迭代(功能)
服务器会首先分析URL,得到请求的文件的路径和类型(MIME)。
- 使用命令行参数传递JSON配置文件路径,入口函数负责读取配置并创建服务器。
- 入口函数完整描述了程序的运行逻辑,其中解析URL和合并文件的具体实现封装在其它两个函数里。
- 解析URL时先将普通URL转换为了文件合并URL,使得两种URL的处理方式可以一致。
然后,服务器会读取请求的文件,并按顺序合并文件内容。
- 合并文件时使用异步API读取文件,避免服务器因等待磁盘IO而发生阻塞。
最后,服务器返回响应,完成对一次请求的处理。
逻辑缺陷:
http://assets.example.com/foo/bar.js,foo/baz.js
经过分析之后我们会发现问题出在
/
被自动替换/??
这个行为上,而这个问题我们可以到第二次迭代时再解决。第二次迭代(性能)
- 第二版代码在检查了请求的所有文件是否有效之后,立即就输出了响应头,并接着一边按顺序读取文件一边输出响应内容。
- 并且,在读取文件时,第二版代码直接使用了只读数据流来简化代码。
- 服务器本身的功能和性能已经得到了初步满足
第三次迭代(稳定性)
- 从工程角度上讲,没有绝对可靠的系统。即使第二次迭代的代码经过反复检查后能确保没有bug,也很难说是否会因为NodeJS本身,或者是操作系统本身,甚至是硬件本身导致我们的服务器程序在某一天挂掉。因此一般生产环境下的服务器程序都配有一个守护进程,在服务挂掉的时候立即重启服务。一般守护进程的代码会远比服务进程的代码简单,从概率上可以保证守护进程更难挂掉。如果再做得严谨一些,甚至守护进程自身可以在自己挂掉时重启自己,从而实现双保险。
- 因此在本次迭代时,我们先利用NodeJS的进程管理机制,将守护进程作为父进程,将服务器程序作为子进程,并让父进程监控子进程的运行状态,在其异常退出时重启子进程。
- 步骤
- 把守护进程的代码保存为
daemon.js
,之后我们可以通过node daemon.js config.json
启动服务,而守护进程会进一步启动和监控服务器进程- 让守护进程在接收到
SIGTERM
信号时终止服务器进程- 而在服务器进程这一端,同样在收到
SIGTERM
信号时先停掉HTTP服务再正常退出。第四次迭代(代码部署及服务器控制)
1. 大示例
学习讲究的是学以致用和融会贯通。至此我们已经分别介绍了NodeJS的很多知识点,本章作为最后一章,将完整地介绍一个使用NodeJS开发Web服务器的示例。
需求
我们要开发的是一个简单的静态文件合并服务器,该服务器需要支持类似以下格式的JS或CSS文件合并请求。
http://assets.example.com/foo/??bar.js,baz.js
在以上URL中,??
是一个分隔符,之前是需要合并的多个文件的URL的公共部分,之后是使用,
分隔的差异部分。因此服务器处理这个URL时,返回的是以下两个文件按顺序合并后的内容。
/foo/bar.js
/foo/baz.js
另外,服务器也需要能支持类似以下格式的普通的JS或CSS文件请求。
http://assets.example.com/foo/bar.js
以上就是整个需求。
第一次迭代
快速迭代是一种不错的开发方式,因此我们在第一次迭代时先实现服务器的基本功能。
设计
简单分析了需求之后,我们大致会得到以下的设计方案。
+---------+ +-----------+ +----------+
request -->| parse |-->| combine |-->| output |--> response
+---------+ +-----------+ +----------+
也就是说,服务器会首先分析URL,得到请求的文件的路径和类型(MIME)。然后,服务器会读取请求的文件,并按顺序合并文件内容。最后,服务器返回响应,完成对一次请求的处理。
另外,服务器在读取文件时需要有个根目录,并且服务器监听的HTTP端口最好也不要写死在代码里,因此服务器需要是可配置的。
实现
根据以上设计,我们写出了第一版代码如下。
var fs = require('fs'),
path = require('path'),
http = require('http');
var MIME = {
'.css': 'text/css',
'.js': 'application/javascript'
};
//合并函数,服务器会读取请求的文件,并按顺序合并文件内容
function combineFiles(pathnames, callback) {
var output = [];
(function next(i, len) {
if (i < len) {
fs.readFile(pathnames[i], function (err, data) {
if (err) {