Node 是一个 Node
是基于Chrome V8
引擎开发的能使JavaScript
在服务器端运行的运行时环境。由于 js 的特性,所以 Node 默认也是单线程的,无法充分利用多核CPU。适用于IO密集型,但对于 CPU 密集型的应用,因为需要长时间占用 CPU,会导致其他的请求处于阻塞状态,所以对于 CPU 密集型的应用,需要特殊考虑。当然,Node 也给我们提供了一些 API,用于衍生子进程来进行其他的一些操作。常用的有 child_process 跟 cluster。
1. child_process: 提供了四个 api 来衍生异步子进程
1). child_process.spawn(command[, args][, options]): 默认不会衍生一个 shell, 可通过 options.shell = true 衍生一个 shell 并在该 shell 上执行对于的 command,但是需要注意的是如果启动了 shell,则不要将未经过处理的用户输入传给 command, 因为这可能会触发一些命令的执行, 比如 ls hello.txt; rm -rf *
const { spawn } = require(‘child_process‘) const spawnProcess = spawn(‘cmd.exe‘, [‘/c‘, ‘node test.js‘], {cwd: path}) // 监听方法只有在子进程执行完成之后才会触发 spawnProcess.stdout.on(‘data‘, (data) => { console.log(data.toString()); }); spawnProcess.stderr.on(‘data‘, (data) => { console.error(data.toString()); });
默认情况下,stdin、 stdout 和 stderr 的管道会在父 Node.js 进程和衍生的子进程之间建立。父子进程间可通过 stdin 跟 stdout 进行通信。但是这些通信是有容量限制的。
// parent.js const childSpawn = spawn(‘node‘, [‘child‘]) // 父进程发送信息给子进程 childSpawn.stdin.write(JSON.stringify({ type: ‘handshake‘, payload: ‘你好啊‘ })) // 父进程接收子进程信息 childSpawn.stdout.on(‘data‘, (chunk) => { try { let data = JSON.parse(chunk.toString()) if (data.type === ‘success‘) { console.log(‘接收子进程发送的消息: ‘, data.payload) } } catch (e) { console.log(chunk.toString()) } }) // child.js process.stdin.on(‘data‘, (chunk) => { let data = JSON.parse(chunk.toString()) if (data.type === ‘handshake‘) { console.log(‘接收父进程发送的消息: ‘, data.payload) process.stdout.write(JSON.stringify({ type: ‘success‘, payload: ‘你也好‘ })) } })
2). child_process.exec(command[, options][, callback]): 默认会衍生一个 shell , 并在这个shell上面执行对于的 command,在子进程未执行完成的情况下,会缓冲输出(包括 console/Error ),并在子进程执行完成之后将缓冲的输出 stdout, stderr 做为回调函数的参数传递。需要注意的是不要将未经过处理的用户输入传给 command, 因为这可能会触发一些命令的执行, 比如 ls hello.txt; rm -rf *
a. command: 必填,需要执行的命令,如果需要传递参数,可使用空格隔开
b. options: 选填,子进程的一些参数配置。需要注意的是如果 stdout 或 stderr 输出的数据量过大,子进程会被终止,可以通过设置 maxBuffer 来修改最大字节数,默认是 1024*1024 (Node 12.9.0)。但是 spawn 衍生的子进程没有这个限制,所以如果需要传输大数据量,建议使用 spawn
c. callback: 选填,子进程结束之后的回调函数,(err, stdout, stderr) => {} 也就是说子进程的输出数据只有在进程关闭之后才会被捕获
err: 当衍生子进程的时候出错,比如执行的路径出错,就会抛出 Error: spawn C:\Windows\system32\cmd.exe ENOENT, errCode: ENOENT 并退出
stdout: 子进程的标准输出,比如正常的console 等
stderr: 子进程的错误,比如在子进程执行代码的时候抛出的 Error
const { exec } = require(‘child_process‘) const execProcess = exec(‘node test.js‘, {cwd: ‘path‘}, (err, stdout, stderr) => { if (err) { // 当path不存在的时候 err 不为空 console.log(`执行的错误: ${err}, errCode: ${err.code}`) return } // 子进程的正常输出/错误输出 console.log(`stdout: ${stdout}`) console.log(`stderr: ${stderr}`) })
3). child_process.execFile(file[, args][, options][, callback]): 类似于 exec(), 但是默认不会衍生 shell,所以对于 window ,因为 .bat/.cmd 文件都需要一个终端来运行,所以默认是不适合的。
1 const { execFile } = require(‘child_process‘) 2 const execFileProcess = execFile(‘node‘, [‘test.js‘], (error, stdout, stderr) => { 3 if (err) { 4 console.log(`执行的错误: ${err}, errCode: ${err.code}`) 5 return 6 } 7 console.log(`stdout: ${stdout}`) 8 console.log(`stderr: ${stderr}`) 9 });
4). child_process.fork(modulePath[, args][, options]): 专门用于衍生新的 Node.js 进程,跟 spawn 一样返回一个 ChildProcess 对象。允许衍生的子进程跟父进程已建立了 IPC 通信通道,父进程与子进程之间通过 send / on 来 发送 / 接收 消息。每个进程都有自己的内存,带有自己的 V8 实例。 由于需要额外的资源分配,因此不建议衍生大量的 Node.js 子进程。可以根据 CPU 数量衍生相应的子进程。Node 提供的 cluster 集群模块也是基于 fork 衍生子进程的,只是衍生的子进程跟父进程共享一个 TCP 链接。
// parent.js const { fork } = require(‘child_process‘) const child = fork(‘./child.js‘) child.send(‘hi child fork‘) child.on(‘message‘, (data) => { console.log(‘message from child: ‘, data) }) // child.js process.on(‘message‘, (data)=> { console.log(‘message from parent: ‘, data) process.send(‘hello parent process‘) })
exec, execFile, fork 底层都是基于 spawn
exec, execFile 提供了回调方法,当子进程结束的时候调用
2. cluster 模块:
cluster 使用 fork() 来衍生子进程,采用的是主从模式。cluster 会创建一个 master 进程,然后在master进程里面根据需要可以创建出多个子进程(但是不建议衍生太多的子进程,一般是通过 os 模块提供的 require(‘os‘).length 来获取CPU核数并创建相对数量的子进程)。
由于是通过 child_process 的 fork() 方法创建的子进程,所以父子进程间可以通过 IPC 通信。
master 进程不负责业务处理,主要是负责监听端口,管理跟分配任务给子进程。可通过 cluster.isMaster / isWorker 来判断主进程还是工作进程