最近在學習nodejs監控的知識,雖然沒有精力去學習寫一個簡易版監控,但還是忍不住了解一下如何獲取這些指標(查閱了很多資料,覺得國內網上對於這塊內容介紹是在太少了,自己也在整理服務端node知識點,就總結為此文章,與君分享)。
本文有些指標可能有問題,歡迎交流,其實這些數據你都可以整理一下寫成一個監控的庫,用在自己的中小項目上了。然後前端react有bizcharts,g2這些工具,前端自己繪製資料大螢幕。我看esay monitor 收集的資料維度還沒有我們這個全呢。
伺服器的效能瓶頸通常為以下幾個:
CPU 使用率與CPU 負載,這兩個從一定程度上都可以反映一台機器的繁忙程度。
CPU 使用率是執行的程式所佔用的CPU 資源,表示機器在某個時間點的運作程序的情況。使用率越高,表示機器在這個時間上運行了很多程序,反之較少。使用率的高低與CPU 強弱有直接關係。我們先來了解相關的API和一些名詞解釋,幫助我們理解取得CPU使用率的程式碼。
os.cpus()
傳回包含每個邏輯CPU 核心的資訊的物件陣列。
model:一個字串,指定CPU核心的型號。
speed:一個數字,指定CPU核心的速度(以MHz為單位)。
times:包含下列屬性的物件:
注意:的nice
值僅用於POSIX。在Windows作業系統上, nice
所有處理器的值始終為0。
大家看到user,nice字段,有些同學就優點懵逼了,我也是,所以仔細查詢了一下其意義,請接著。
user 表示CPU 運作在使用者態的時間佔比。
應用程式執行分為使用者態以及核心態: CPU 在使用者態執行應用程式本身的程式碼邏輯,通常是一些邏輯或數值計算; CPU 在核心態執行行程發起的系統調用,通常是回應行程對資源的請求。
使用者空間程式是任何不屬於核心的進程。 Shell、編譯器、資料庫、Web 伺服器以及與桌面相關的程式都是使用者空間進程。 如果處理器沒有空閒,那麼大部分CPU 時間應該花在執行用戶空間進程上是很正常的。
nice 表示CPU 運行在低優先權用戶態的時間佔比,低優先權表示進程nice 值小於0 。
user 表示CPU 運作在內核態的時間佔比。
一般而言,核心態CPU 使用率不應過高,除非應用程式發起大量系統呼叫。如果太高,表示系統呼叫時間長,例如是IO操作頻繁。
idle 表示CPU 在空閒狀態的時間佔比,CPU 沒有任何任務可執行。
irq 表示CPU 處理硬體中斷的時間佔比。
網路卡中斷是一個典型的例子:網路卡接到資料包後,透過硬體中斷通知CPU 處理。 如果系統網路流量非常大,則可觀察到irq 使用率明顯升高。
使用者態小於70%,內核態小於35%且整體小於70%,可算為健康狀態。
以下範例說明了Node.js中os.cpus()方法的使用:
範例1:
// Node.js program to demonstrate the // os.cpus() method // Allocating os module const os = require('os'); // Printing os.cpus() values console.log(os.cpus());
輸出:
[ { model:'Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz', speed:2712, times: { user:900000, nice:0, sys:940265, idle:11928546, irq:147046 } }, { model:'Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz', speed:2712, times: { user:860875, nice:0, sys:507093, idle:12400500, irq:27062 } }, { model:'Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz', speed:2712, times: { user:1273421, nice:0, sys:618765, idle:11876281, irq:13125 } }, { model:'Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz', speed:2712, times: { user:943921, nice:0, sys:460109, idle:12364453, irq:12437 } } ]
以下是如何取得cpu利用率的程式碼
const os = require('os'); const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); class OSUtils { constructor() { this.cpuUsageMSDefault = 1000; // CPU 使用率預設時段} /** * 取得某時間段CPU 使用率* @param { Number } Options.ms [時間段,預設是1000ms,即1 秒鐘] * @param { Boolean } Options.percentage [true(以百分比結果傳回)|false] * @returns { Promise } */ async getCPUUsage(options={}) { const that = this; let { cpuUsageMS, percentage } = options; cpuUsageMS = cpuUsageMS || that.cpuUsageMSDefault; const t1 = that._getCPUInfo(); // t1 時間點CPU 訊息await sleep(cpuUsageMS); const t2 = that._getCPUInfo(); // t2 時間點CPU 訊息const idle = t2.idle - t1.idle; const total = t2.total - t1.total; let usage = 1 - idle / total; if (percentage) usage = (usage * 100.0).toFixed(2) + "%"; return usage; } /** * 取得CPU 瞬時時間資訊* @returns { Object } CPU 資訊* user <number> CPU 在使用者模式下花費的毫秒數。 * nice <number> CPU 在良好模式下花費的毫秒數。 * sys <number> CPU 在系統模式下花費的毫秒數。 * idle <number> CPU 在閒置模式下花費的毫秒數。 * irq <number> CPU 在中斷請求模式下花費的毫秒數。 */ _getCPUInfo() { const cpus = os.cpus(); let user = 0, nice = 0, sys = 0, idle = 0, irq = 0, total = 0; for (let cpu in cpus) { const times = cpus[cpu].times; user += times.user; nice += times.nice; sys += times.sys; idle += times.idle; irq += times.irq; } total += user + nice + sys + idle + irq; return { user, sys, idle, total, } } } const cpuUsage = new OSUtils().getCPUUsage({ percentage: true }); console.log('cpuUsage: ', cpuUsage.then(data=>console.log(data))); // 我的電腦是6.15%
CPU的負載(loadavg)很好理解,指某段時間內佔用CPU 時間的進程和等待CPU 時間的進程數為平均負載(load average),這裡等待CPU 時間的進程是指等待被喚醒的進程,不包括處於wait狀態進程。
在此之前我們需要學習一個node的API
os.loadavg()
傳回包含1、5 和15 分鐘平均負載的陣列。
平均負載是作業系統計算的系統活動量度,並表示為小數。
平均負載是Unix 特有的概念。 在Windows 上,回傳值始終為[0, 0, 0]
它用來描述作業系統目前的繁忙程度,可以簡單地理解為CPU在單位時間內正在使用和等待使用CPU的平均任務數。 CPU load過高,表示進程數量過多,在Node中可能反映在用紫禁城模組重複啟動新的進程。
const os = require('os'); // CPU執行緒數const length = os.cpus().length; // 單核心CPU的平均負載,傳回一個包含1、5、15 分鐘平均負載的陣列os.loadavg().map(load => load / length);
我們先解釋一個API,要嘛你看不懂我們取得記憶體指標的程式碼
函數傳回4個參數,意義及差異如下:
用如下程式碼,印一個子程序的記憶體使用情況,可以看出rss大致等於top指令的RES。另外,主進程的記憶體只有33M比子進程的記憶體還小,可見它們的記憶體佔用情況是獨立統計的。
var showMem = function(){ var mem = process.memoryUsage(); var format = function(bytes){ return (bytes / 1024 / 1024).toFixed(2) + ' MB'; }; console.log('Process: heapTotal ' + format(mem.heapTotal) + ' heapUsed ' + format(mem.heapUsed) + ' rss ' + format(mem.rss) + ' external:' + format(mem.external) ); console.log('--------------------------------------------- --------------'); };
對於Node而言,一旦出現記憶體洩漏,就沒那麼容易排查。如果監控到記憶體只升不降,那麼鐵定有記憶體外洩問題。健康的記憶體使用應該有升有降。訪問大的時候上升,訪問回落下降
const os = require('os'); // 查看目前Node 程序記憶體使用情況const { rss, heapUsed, heapTotal } = process.memoryUsage(); // 取得系統空閒記憶體const systemFree = os.freemem(); // 取得系統總記憶體const systemTotal = os.totalmem(); module.exports = { memory: () => { return { system: 1 - systemFree / systemTotal, // 系統記憶體佔用率heap: heapUsed / headTotal, // 目前Node 進程記憶體佔用率node: rss / systemTotal, // 目前Node 進程記憶體佔用系統記憶體的比例} } }
磁碟監控主要是監控磁碟的用量。由於日誌頻繁寫的緣故,磁碟空間被漸漸用光。一旦磁碟不夠用,將會引發系統的各種問題。將磁碟的使用量設定上限,一旦磁碟用量超過警戒值,伺服器的管理者就應該整理日誌或清理磁碟。
以下程式碼參考easy monitor3.0
const { execSync } = require('child_process'); const result = execSync('df -P', { encoding: 'utf8'}) const lines = result.split('n'); const metric = {}; lines.forEach(line => { if (line.startsWith('/')) { const match = line.match(/(d+)%s+(/.*$)/); if (match) { const rate = parseInt(match[1] || 0); const mounted = match[2]; if (!mounted.startsWith('/Volumes/') && !mounted.startsWith('/private/')) { metric[mounted] = rate; } } } }); console.log(metric)
I/O負載指的主要是磁碟I/O。反應的是磁碟上的讀寫情況,對於Node編寫的應用,主要是面向網路服務,是不太可能出現I/O負載過高的情況,多讀書的I/O的壓力來自資料庫。
取得I/O指標,我們要了解一個linux指令,叫iostat,如果沒有安裝,需要安裝一下,我們看一下這個指令為啥能反應I/O指標
iostat -dx
屬性說明
rrqm/s: 每秒進行merge 的讀取操作數目。即rmerge/s(每秒對該裝置的讀取請求被合併次數,檔案系統會對讀取同區塊(block)的請求進行合併) wrqm/s: 每秒進行merge 的寫入操作數目。即wmerge/s(每秒對該裝置的寫入請求被合併次數) r/s: 每秒完成的讀取I/O 裝置次數。即rio/s w/s: 每秒完成的寫入I/O 裝置次數。即wio/s rsec/s: 每秒讀扇區數。即rsect/s wsec/s: 每秒寫扇區數。即wsect/s rkB/s: 每秒讀K位元組數。是rsect/s 的一半,因為每扇區大小為512位元組。 wkB/s: 每秒寫入K位元組數。是wsect/s 的一半。 avgrq-sz: 平均每次設備I/O操作的資料大小(扇區)。 avgqu-sz: 平均I/O佇列長度。 await: 平均每次設備I/O操作的等待時間(毫秒)。 svctm: 平均每次設備I/O操作的處理時間(毫秒)。 %util: 一秒中有百分之多少的時間用於I/O 操作,即被io消耗的cpu百分比
我們只監控%util就行
如果%util 接近100% ,說明產生的I/O請求太多, I/O系統已經滿負荷,磁碟可能存在瓶頸。
如果await 遠大於svctm,表示I/O 佇列太長,應用得到的回應時間變慢,如果回應時間超過了使用者可以容許的範圍,這時可以考慮更換更快的磁碟,調整核心elevator 演算法,最佳化應用,或者升級CPU。
監控Nodejs的頁面回應時間, 方案選自廖雪峰老師的部落格文章。
最近想監控一下Nodejs的效能。記錄分析Log太麻煩,最簡單的方式是記錄每個HTTP請求的處理時間,直接在HTTP Response Header中回傳。
記錄HTTP請求的時間很簡單,就是收到請求記一個時間戳,回應請求的時候再記一個時間戳,兩個時間戳之差就是處理時間。
但是,res.send()程式碼遍布各個js文件,總不能把每個URL處理函數都改一遍。
正確的思路是用middleware實作。但是Nodejs沒有任何攔截res.send()的方法,怎麼破?
其實只要稍微轉換一下思路,放棄傳統的OOP方式,以函數物件看待res.send(),我們就可以先保存原始的處理函數res.send,再用自己的處理函數取代res.send:
app.use (function (req, res, next) { // 記錄start time: var exec_start_at = Date.now(); // 儲存原始處理函數: var _send = res.send; // 綁定我們自己的處理函數: res.send = function () { // 發送Header: res.set('X-Execution-Time', String(Date.now() - exec_start_at)); // 呼叫原始處理函數: return _send.apply(res, arguments); }; next(); });
只用了幾行程式碼,就把時間戳搞定了。
對於res.render()方法不需要處理,因為res.render()內部呼叫了res.send()。
呼叫apply()函數時,傳入res物件很重要,否則原始的處理函數的this指向undefined直接導致出錯。
實測首頁回應時間9毫秒
名詞解釋:
QPS:Queries Per Second意思是“每秒查詢率”,是一台伺服器每秒能夠回應的查詢次數,是對一個特定的查詢伺服器在規定時間內所處理流量多少的衡量標準。
在互聯網中,作為網域名稱系統伺服器的機器的效能經常以每秒查詢率來衡量。
TPS:是TransactionsPerSecond的縮寫,也就是事務數/秒。它是軟體測試結果的測量單位。一個事務是指一個客戶機向伺服器發送請求然後伺服器做出反應的過程。客戶機在發送請求時開始計時,收到伺服器回應後結束計時,以此計算使用的時間和完成的交易數量。
QPS vs TPS:QPS基本上類似於TPS,但是不同的是,對於一個頁面的一次訪問,形成一個TPS;但一次頁面請求,可能產生多次對伺服器的請求,伺服器對這些請求,就可計入“ QPS」之中。如,訪問一個頁面會請求伺服器2次,一次訪問,產生一個“T”,產生2個“Q”。
回應時間:執行一個請求從開始到最後收到回應資料所花費的總體時間,即從客戶端發起請求到收到伺服器回應結果的時間。
反應時間RT(Response-time),是一個系統最重要的指標之一,它的數值大小直接反應了系統的快慢。
並發數是指系統同時能處理的請求數量,這個也是反應了系統的負載能力。
系統的吞吐量(承壓能力)與request對CPU的消耗、外部介面、IO等等緊密關聯。單一request 對CPU消耗越高,外部系統介面、IO速度越慢,系統吞吐能力越低,反之越高。
系統吞吐量幾個重要參數:QPS(TPS)、併發數、回應時間。
QPS(TPS):(Query Per Second)每秒鐘request/事務數量
並發數: 系統同時處理的request/事務數
回應時間: 一般取平均回應時間
理解了上面三個要素的意義之後,就能推導出它們之間的關係:
我們透過一個實例來把上面幾個概念串起來理解。以二八定律來看,如果每天80% 的訪問集中在20% 的時間裡,這20% 時間就叫做峰值時間。
1、每天300w PV 的在單一機器上,這台機器需要多少QPS?
( 3000000 * 0.8 ) / (86400 * 0.2 ) = 139 (QPS)
2、如果一台機器的QPS是58,需要幾台機器來支援?
139 / 58 = 3
到這裡,以後如果你做一般中小專案的前端架構,在部署自己的node服務,就知道需要多少機器組成集群來報告ppt了吧,哈哈,有pv就能推算一個初略值。
我們需要了解壓力測試(我們要靠壓測取得qps),以ab指令為例:
指令格式:
ab [options] [http://]hostname[:port]/path
常用參數如下:
-n requests 總請求數-c concurrency 並發數-t timelimit 測試所進行的最大秒數, 可以當做請求的超時時間-p postfile 包含了需要POST的資料的檔案-T content-type POST資料所使用的Content-type頭資訊複製程式碼