背景
由于 ons(阿里云 RocketMQ 包)基于 C艹 封装而来,不支持单一进程内实例化多个生产者与消费者,为了解决这一问题,使用了 Node.js 子进程。
在使用的过程中碰到的坑
- 发布:进程管理关闭主进程后,子进程变为操作系统进程(pid 为 1)
几种解决方案
- 将子进程看做独立运行的进程,记录 pid,发布时进程管理关闭主进程同时关闭子进程
- 主进程监听关闭事件,主动关闭从属于自己的子进程
子进程种类
- spawn:执行命令
- exec:执行命令(新建 shell)
- execFile:执行文件
- fork:执行文件
子进程常用事件
- exit
- close
- error
- message
close 与 exit 是有区别的,close 是在数据流关闭时触发的事件,exit 是在子进程退出时触发的事件。因为多个子进程可以共享同一个数据流,所以当某个子进程 exit 时不一定会触发 close 事件,因为这个时候还存在其他子进程在使用数据流。
子进程数据流
- stdin
- stdout
- stderr
因为是以主进程为出发点,所以子进程的数据流与常规理解的数据流方向相反,stdin:写入流,stdout、stderr:读取流。
spawn
spawn(command[, args][, options])
执行一条命令,通过 data 数据流返回各种执行结果。
基础使用
const { spawn } = require('child_process');
const child = spawn('find', [ '.', '-type', 'f' ]);
child.stdout.on('data', (data) => {
console.log(`child stdout:\n${data}`);
});
child.stderr.on('data', (data) => {
console.error(`child stderr:\n${data}`);
});
child.on('exit', (code, signal) => {
console.log(`child process exit with: code ${code}, signal: ${signal}`);
});
常用参数
{
cwd: String,
env: Object,
stdio: Array | String,
detached: Boolean,
shell: Boolean,
uid: Number,
gid: Number
}
重点说明下 detached 属性,detached 设置为 true
是为子进程独立运行做准备。子进程的具体行为与操作系统相关,不同系统表现不同,Windows 系统子进程会拥有自己的控制台窗口,POSIX 系统子进程会成为新进程组与会话负责人。
这个时候子进程还没有完全独立,子进程的运行结果会展示在主进程设置的数据流上,并且主进程退出会影响子进程运行。当 stdio 设置为 ignore
并调用 child.unref();
子进程开始真正独立运行,主进程可独立退出。
exec
exec(command[, options][, callback])
执行一条命令,通过回调参数返回结果,指令未执行完时会缓存部分结果到系统内存。
const { exec } = require('child_process');
exec('find . -type f | wc -l', (err, stdout, stderr) => {
if (err) {
console.error(`exec error: ${err}`);
return;
}
console.log(`Number of files ${stdout}`);
});
两全其美 —— spawn 代替 exec
由于 exec 的结果是一次性返回,在返回前是缓存在内存中的,所以在执行的 shell 命令输出过大时,使用 exec 执行命令的方式就无法按期望完成我们的工作,这个时候可以使用 spawn 代替 exec 执行 shell 命令。
const { spawn } = require('child_process');
const child = spawn('find . -type f | wc -l', {
stdio: 'inherit',
shell: true
});
child.stdout.on('data', (data) => {
console.log(`child stdout:\n${data}`);
});
child.stderr.on('data', (data) => {
console.error(`child stderr:\n${data}`);
});
child.on('exit', (code, signal) => {
console.log(`child process exit with: code ${code}, signal: ${signal}`);
});
execFile
child_process.execFile(file[, args][, options][, callback])
执行一个文件
与 exec 功能基本相同,不同之处在于执行给定路径的一个脚本文件,并且是直接创建一个新的进程,而不是创建一个 shell 环境再去运行脚本,相对更轻量级更高效。但是在 Windows 系统中如 .cmd
、.bat
等文件无法直接运行,这是 execFile 就无法工作,可以使用 spawn、exec 代替。
fork
child_process.fork(modulePath[, args][, options])
执行一个 Node.js 文件
// parent.js
const { fork } = require('child_process');
const child = fork('child.js');
child.on('message', (msg) => {
console.log('Message from child', msg);
});
child.send({ hello: 'world' });
// child.js
process.on('message', (msg) => {
console.log('Message from parent:', msg);
});
let counter = 0;
setInterval(() => {
process.send({ counter: counter++ });
}, 3000);
fork 实际是 spawn 的一种特殊形式,固定 spawn Node.js 进程,并且在主子进程间建立了通信通道,让主子进程可以使用 process 模块基于事件进行通信。
子进程使用场景
- 计算密集型系统
- 前端构建工具利用多核 CPU 并行计算,提升构建效率
- 进程管理工具,如:PM2 中部分功能
实践:Akyuu PM 启动项目
- 进程 ①:用来解析用户输入,调用启动命令
const commander = require('commander'); const path = require('path'); const start = require('./lib/start'); commander .command('start <entry>') .description('start process') .option('--dev', 'set dev environment') .action(function(entry, options) { start({ entry: path.resolve(__dirname, `../entry/${entry}`), isDaemon: !options.dev }); });
- 进程 ①:
- 利用 spawn 启动指定入口文件进程 ②, 设置 detached 为
true
,调用child.unref();
使子进程独立运行const { spawn } = require('child_process'); const child = spawn(process.execPath, [ path.resolve(__dirname, 'cluster') ], { cwd: path.resolve(__dirname, '../../'), env: Object.assign({}, process.env, { izayoiCoffee: JSON.stringify({ configDir: config.akyuuConfigDir, entry: options.entry }) }), detached: true, stdio: 'ignore' }); child.on('exit', function(code, signal) { console.error(`start process \`${path.basename(options.entry)}\` failed, ` + `code: ${code}, signal: ${signal}`); process.exit(1); }); child.unref();
- 记录子进程 pid 到文件
child .on('fork', function(worker) { try { fs.writeFileSync( 'pid file path', worker.process.pid, { encoding: 'utf8' } ); } catch(err) { console.error( '[%s] [uncaughtException] [master: %d] \n%s', moment().utcOffset(8).format('YYYY-MM-DD HH:mm:ss.SSS'), process.pid, err.stack ); } }) .on('exit', function(worker, code, signal) { try { fs.unlinkSync('pid file path'); } catch(err) { console.error( '[%s] [uncaughtException] [master: %d] \n%s', moment().utcOffset(8).format('YYYY-MM-DD HH:mm:ss.SSS'), process.pid, err.stack ); } });
- 利用 spawn 启动指定入口文件进程 ②, 设置 detached 为
- 进程 ②:如果进程 ② 中还需要启动自己的子进程,在启动子进程后,监听自己的退出事件,并主动关闭子进程,防止子进程变为操作系统进程而不受控
const { fork } = require('child_process); const child = fork('some child process file'); // 程序停止信号 process.on('SIGHUP', function() { child.kill('SIGHUP'); process.exit(0); }); // kill 默认参数信号 process.on('SIGTERM', function() { child.kill('SIGHUP'); process.exit(0); }); // Ctrl + c 信号 process.on('SIGINT', function() { child.kill('SIGHUP'); process.exit(0); }); // 退出事件 process.on('exit', function() { child.kill('SIGHUP'); process.exit(0); }); // 未捕获异常 process.on('uncaughtException', function() { child.kill('SIGHUP'); process.exit(0); });
总结
在使用 Node.js 做开发中,尤其是 API 开发过程中很少涉及到子进程,但是子进程还是比较重要的一个组成部分。Node.js 可以利用子进程做些计算密集型任务,虽然没有 C艹 等其他语言高效、方便,但是也不失为一种方案,在没有掌握其他语言时可以用 Node.js 支撑起业务场景。对于子进程的采坑与使用在本文中记录,以供未来的自己参考。
今天的文章Node.js 子进程与应用场景分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/15502.html