Node 进程

Posted on 2019-08-31 15:47  lc_博客  阅读(400)  评论(0编辑  收藏  举报

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 来判断主进程还是工作进程