Node.js 效能提升

在什麼樣的情況下需要效能提升?

如果我們的程式需要進行重度的計算,而且這個任務無法像 timer, http request 等,可以非同步運行時,我們的 Request 就會被卡住

ex:

const express = require('express');

const app = express();

function doWork(duration) {
const start = Date.now();
while(Date.now() - start < duration) {}
}

app.get('/', (req, res) => {
doWork(5000)
res.send('<h1>Hi There!</h1>');
});

app.listen(3000, () => {});

我們寫一個 doWork function,讓它在既定秒數內不斷跑 while 迴圈

這個時如果開兩個 tab 載入頁面,你就會發現第二個載入的頁面會慢非常多,因為 server 還在處理第一個頁面,必須等第一個頁面處理完才會處理第二個頁面

利用 Node.js “Cluster” Mode 多開幾個 process 來幫忙吧!

因為每個 Process 都只能用單核心 CPU 來運行, 透過 Cluster 模組就可以開更多的 Process 來幫忙處理,
而這個Cluster的模組強大的地方在於讓我們只監聽一個Port,加上Master/Worker的概念,當收到請求後就分配給底下的Worker進行處理。

Image.png

Cluster Manager 負責監控底下 Node Server 的狀態,Cluster Manager 可以開 Node Instance, 關閉 Instance, 送資訊給 Instance

Cluster 如何運作?

他主要是使用 child_process.fork() 產生新的 proccess 用此來與 master_process 溝通。

如圖所示

XaovjoA.png

圖片來源: 【我可以你也可以的Node.js】第二十篇 - Node Cluster 讓你的 Thread 不再孤軍奮戰

預設配發方式

當一個請求進來的時候進到 master process 然後由他去輪流指派 一個 process

使用 Cluster

使用 Cluster 時,我們可以使用 cluster.isMaster 屬性去判斷現在是否為 Master Process

const cluster = require('cluster');
const express = require('express');

if (cluster.isMaster) {
// Create child processes
// Cause index.js to be executed again but in child mod
cluster.fork();
cluster.fork();
} else {
const app = express();

function doWork(duration) {
const start = Date.now();
while(Date.now() - start < duration) {}
}

app.get('/', (req, res) => {
doWork(5000)
res.send('<h1>Hi There!</h1>');
});

app.get('/fast', (req, res) => {
res.send('fast');
})

app.listen(3000, () => {
console.log('server listening at port 3000')
});
}

我們新增一個不會阻塞的 route /fast ,由於我們現在有兩個 process 了,所以當我們開兩個 tab,一個發到 / ,另一個再發到 /fast ,我們可以發現 /fast 直接就返回了,沒有被上個 Request 卡死

既然開 Process 就可以變快,那開好開滿不就超快?

我們這邊可以用 ab (apache-benchmark) 指令來測試開越多 child process 是否就越好

ab -c 50 -n 500 localhost:3000/fast
  • -c : concurrency 的意思, -c 50 同時保持有 50 Request 還處 pending 的狀態
  • -n : request 的總數, -n 500 代表總共發 500 個

測試方式:逐步增加 Child Process 和 Request

測試程式:

if (cluster.isMaster) {
// 逐步增加
cluster.fork();
} else {
const app = express();
app.get('/', (req, res) => {
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
res.send('<h1>Hi There!</h1>');
})
});

app.listen(3000, () => {
console.log('server listening at port 3000')
});
}

測試 1 個 Child Process

測試指令

ab -c -1 -n -1 localhost:3000/

測試結果
Image.png

測試 2 個 Child Process

測試指令

ab -c -2 -n -2 localhost:3000/

測試結果

Image.png

測試 10 個 Child Process

測試指令

ab -c -10 -n -10 localhost:3000/

測試結果

Image.png

測試 2 個 Child Process 同時處理 10 個 Request

測試指令

ab -c -10 -n -10 localhost:3000/

測試結果

Image.png

我們可以看到,新增一個 Child Process,確實是讓 2 個請求維持在同一時間返回,但是新增到10 個 Child Process 時,面對同時 10 個請求速度卻下降了,然會後最驚人的事,只開兩個 Child Process 卻比開 10 個的快???

Stack Overflow 也有人提出相似問題: Node.js clustering is slower than single threaded mode, 但看起也有沒有結論

PM2

相較與直接用 cluster 模組,在管理 process 方面,社群大多數人會使用 pm2 開源專案,pm2 除了管裡 process 以外還有許多實用的功能,可以參考 Ray Leel 大的 好 pm2, 不用嗎?

安裝:

npm i -g pm2

使用方法:

開啟 instance:

-i 後面接希望啟動 instance 的數量, 0 或 max 默認自動偵測 CPU 啟動最大值

pm2 start app.js -i 0

顯示管理程序狀態:

pm2 [list|ls|status]

Image.png

停止服務:

pm2 stop app_name

停止並刪除服務:

pm2 delete app_name

Reference:

使用 stream 上傳檔案到 Server 你真的懂 Node.js 裡 Libuv 如何使用 Thread Pool 嗎?
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×