Node.js 中的進程管理

Ror5975 7年前發布 | 7K 次閱讀 Node.js Node.js 開發

本文主要對 Node.js 中進程管理相關的東西做一個簡單介紹,包括 process 對象 、 child_process 模塊 和 cluster 模塊 ,詳細的 API 可以查看官方文檔。

Process 對象

process 是 Node.js 的一個全局對象,可以在任何地方直接使用而不需要 require 命令加載。 process 對象提供了 當前 node 進程 的命令行參數、標準輸入輸出、運行環境和運行狀態等信息。

常用屬性

argv

process.argv 屬性返回一個數組,第一個元素是 node,第二個元素是腳本文件名稱,其余成員是腳本文件的參數。

$ node process-2.js one two=three four

0: /usr/local/bin/node
1: /Users/mjr/work/node/process-2.js
2: one
3: two=three
4: four

env

process.env 返回一個對象,包含了當前 Shell 的所有環境變量,比如:

{
  TERM: 'xterm-256color',
  SHELL: '/bin/zsh',
  USER: 'huangtengfei',
  PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin',
  PWD: '/Users/huangtengfei',
  HOME: '/Users/huangtengfei'
}

這個屬性通常的使用場景是,新建一個 NODE_ENV 變量,用來確定當前所處的開發階段,生成階段設為 production ,開發階段設為 develop ,然后在腳本中讀取 process.env.NODE_ENV 再做相應處理即可。

運行腳本時可以這樣改變環境變量:

$ export NODE_ENV=production && node app.js
# 或者
$ NODE_ENV=production node app.js

stdin/stdout

process.stdin 指向標準輸入(鍵盤到緩沖區里的東西),返回一個可讀的流:

process.stdin.setEncoding('utf8');

process.stdin.on('readable', () => {
  var chunk = process.stdin.read();
  if (chunk !== null) {
    process.stdout.write(`data: ${chunk}`);
  }
});

process.stdin.on('end', () => {
  process.stdout.write('end');
});

process.stdout 指向標準輸出(向用戶顯示內容),返回一個可寫的流:

const fs = require('fs');

fs.createReadStream('wow.txt')
  .pipe(process.stdout);

常用方法

cwd()

process.cwd() 返回運行 Node 的工作目錄(絕對路徑),比如在目錄 /Users/huangtengfei/abc 下執行 node server.js ,那么 process.cwd() 返回的就是 /Users/huangtengfei/abc 。

另一個常用的獲取路徑的方法是 __dirname ,它返回的是執行文件時該文件在文件系統中所在的目錄。注意 process.cwd() 和 __dirname 的不同,前者是進程發起時的位置,后者是腳本的位置,兩者可能不一致。

on()

process 對象部署了 EventEmitter 接口,可以使用 process.on() 方法監聽各種事件,并指定回調函數。比如監聽到系統發出進程終止信號時關閉服務器然后退出進程:

process.on('SIGTERM', function () {
  server.close(function () {
    process.exit(0);
  });
});

exit()

process.exit() 會讓 Node 立即終止當前進程(同步),參數為一個退出狀態碼, 0 表示成功,大于 0 的任意整數表示失敗。

kill()

process.kill() 用來對特定 id 的進程(process.pid)發送信號,默認為 SIGINT 信號。比如殺死當前進程:

process.kill(process.pid, 'SIGTERM');

雖然名字叫 kill ,但其實 process.kill() 只是負責發送信號,具體發送完信號之后這個怎么處理這個指定進程,取決于信號種類和接收到這個信號之后做了什么操作(比如 process.exit() 或者只是 console.log('Ignored this single') )。

Child Process 模塊

child_process 模塊用于創建和控制子進程,其中最核心的是 .spawn() ,其他 API 算是針對特定場景對它的封裝。使用前要先 require 進來:

const cp = require('child_process');

exec(command[, options][, callback])

exec() 方法用于執行 shell 命令,它的第一個參數是字符串形式的命令,第二個參數(可選)用來指定子進程運行時的定制化操作,第三個參數(可選)用來設置執行完命令的回調函數。比如在一個特定目錄 /Users/huangtengfei/abc 下執行 ls -l 命令:

cp.exec('ls -l', {
  cwd: '/Users/huangtengfei/abc'
}, (error, stdout, stderr) => {
  if (error) {
    console.error(`exec error: ${error}`);
    return;
  }
  console.log(`stdout: ${stdout}`);
  console.log(`stderr: ${stderr}`);
})

spawn(command[, args][, options])

spawn() 用來創建一個子進程執行特定命令,與 exec() 的區別是它沒有回調函數,只能通過監聽事件來獲取運行結果,它適用于子進程長時間運行的情況,可以實時輸出結果。

const ls = cp.spawn('ls', ['-l']);

ls.stdout.on('data', (data) => {
  console.log(`stdout: ${data}`);
});

ls.stderr.on('data', (data) => {
  console.log(`stderr: ${data}`);
});

ls.on('close', (code) => {
  console.log(`child process exited with code ${code}`);
});

使用 spawn 可以實現一個簡單的守護進程,在工作進程不正常退出時重啟工作進程:

/* daemon.js */
function spawn(mainModule) {
    const worker = cp.spawn('node', [ mainModule ]);

    worker.on('exit', function (code) {
        if (code !== 0) {
            spawn(mainModule);
        }
    });
}

spawn('worker.js');

fork(modulePath[, args][, options])

fork() 用來創建一個子進程執行 node 腳本, fork('./child.js') 相當于 spawn('node', ['./child.js']) ,區別在于 fork 會在父子進程之間建立一個通信管道( fork() 的返回值),用于進程間通信。對該通信管道對象可以監聽 message 事件,用來獲取子進程返回的信息,也可以向子進程發送信息。

/* main.js */
const proc = cp.fork('./child.js');
proc.on('message', function(msg) {
  console.log(`parent got message: ${msg}`);
});
proc.send({ hello: 'world' });

/* child.js */
process.on('message', function(msg) {
  console.log(`child got message: ${msg}`);
});
process.send({ foo: 'bar' });

Cluster 模塊

Node.js 默認單進程執行,但這樣就無法利用多核計算機的資源,cluster 模塊的出現就是為了解決這個問題的。在開發服務器程序時,可以通過 cluster 創建一個主進程和多個 worker 進程,讓每個 worker 進程運行在一個核上,統一通過主進程監聽端口和分發請求。

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  console.log(`Master ${process.pid} is running`);

  // Fork workers.
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`worker ${worker.process.pid} died`);
  });
} else {
  // Workers can share any TCP connection
  // In this case it is an HTTP server
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world\n');
  }).listen(8000);

  console.log(`Worker ${process.pid} started`);
}

常用屬性和方法

isMaster/isWorker

cluster.isMaster 用來判斷當前進程是否是主進程, cluster.isWorker 用來判斷當前進程是否是工作進程,兩者返回的都是布爾值。

workers

cluster.workers 是一個包含所有 worker 進程的對象,key 為 worker.id ,value 為 worker 進程對象。

// 遍歷所有 workers
function eachWorker(callback) {
  for (const id in cluster.workers) {
    callback(cluster.workers[id]);
  }
}
eachWorker((worker) => {
  worker.send('big announcement to all workers');
});

fork([env])

cluster.fork() 方法用來新建一個 worker 進程,默認上下文復制主進程,只有主進程可調用。

常用事件

listening

在工作進程調用 listen 方法后,會觸發一個 listening 事件,這個事件可以被 cluster.on('listening') 監聽。

比如每當一個 worker 進程連進來時,輸出一條 log 信息:

cluster.on('listening', (worker, address) => {
  console.log(
    `A worker is now connected to ${address.address}:${address.port}`);
});

exit

在工作進程掛掉時,會觸發一個 exit 事件,這個事件可以被 cluster.on('exit') 監聽。

比如自動重啟 worker:

cluster.on('exit', (worker, code, signal) => {
  console.log('worker %d died (%s). restarting...',
    worker.process.pid, signal || code);
  cluster.fork();
});

worker 對象

worker 對象是 cluster.fork() 的返回值,代表一個 worker 進程。

worker.id

worker.id 是當前 worker 的唯一標識,也是保存在 cluster.workers 中的 key 值。

worker.process

所有的 worker 進程都是通過 child_process.fork() 生成的,這個進程對象保存在 worker.process 中。

worker.send()

worker.send() 用在主進程給子進程發送消息,在子進程中,使用 process.on() 監聽消息并使用 process.send() 發送消息。

if (cluster.isMaster) {
  const worker = cluster.fork();
  worker.send('hi there');
} else if (cluster.isWorker) {
  process.on('message', (msg) => {
    process.send(msg);
  });
}

參考

  1. Node.js 官方文檔
  2. JavaScript 標準教程 - Node.js

 

來自:http://huangtengfei.com/2017/03/process/

 

 本文由用戶 Ror5975 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!