怎麼獲取Node效能監控指標?獲取方法分享

2022-04-19 22:00:13
怎麼獲取效能監控指標?本篇文章來和大家聊聊Node效能監控指標獲取方法,希望對大家有所幫助!

最近在學習監控的知識,雖然沒有精力去學習寫一個簡易版監控,但還是忍不住瞭解一下如何獲取這些指標(查閱了很多資料,覺得國內網上對於這塊內容介紹是在太少了,自己也在整理伺服器端node知識點,就彙總為此文章,與君共用)。

本文有些指標可能存在問題,歡迎交流,其實這些資料你都可以整理一下寫成一個監控的庫,用在自己的中小專案上了。然後前端react有bizcharts,g2這些工具,前端自己繪製資料大屏。我看esay monitor 收集的資料維度還沒有我們這個全呢。

伺服器的效能瓶頸通常為以下幾個:

  • CPU 使用率
  • CPU 負載(load)
  • 記憶體
  • 磁碟
  • I/O
  • 吞吐量 (Throughput)
  • 每秒查詢率 QPS(Query Per Second)
  • 紀錄檔監控/真實QPS
  • 響應時間
  • 程序監控

獲取 CPU 指標

CPU 使用率與CPU 負載,這兩個從一定程度上都可以反映一臺機器的繁忙程度。

CPU 使用率

CPU 使用率是執行的程式佔用的 CPU 資源,表示機器在某個時間點的執行程式的情況。使用率越高,說明機器在這個時間上執行了很多程式,反之較少。使用率的高低與 CPU 強弱有直接關係。我們先了解一下相關的API和一些名詞解釋,幫助我們理解獲取CPU使用率的程式碼。

os.cpus()

返回包含有關每個邏輯 CPU 核心的資訊的物件陣列。

  • model: 一個字串,指定CPU核心的型號。

  • speed: 一個數位,指定CPU核心的速度(以MHz為單位)。

  • times: 包含以下屬性的物件:

    • user CPU 在使用者模式下花費的毫秒數。
    • nice CPU 在良好模式下花費的毫秒數。
    • sys CPU 在系統模式下花費的毫秒數。
    • idle CPU 在空閒模式下花費的毫秒數。
    • irq CPU 在中斷請求模式下花費的毫秒數。

注意:nice值僅用於POSIX。在Windows作業系統上,nice所有處理器的值始終為0。

大家看到user,nice欄位,有些同學就優點懵逼了,我也是,所以仔細查詢了一下其意義,請接著。

user

user 表示 CPU 執行在 使用者態 的時間佔比。

應用程序執行分為 使用者態 以及 核心態 : CPU 在使用者態執行應用程序自身的程式碼邏輯,通常是一些 邏輯數值計算 ; CPU 在核心態執行程序發起的 系統呼叫 ,通常是響應程序對資源的請求。

使用者空間程式是任何不屬於核心的程序。 Shell、編譯器、資料庫、Web 伺服器以及與桌面相關的程式都是使用者空間程序。 如果處理器沒有空閒,那麼大部分 CPU 時間應該花在執行使用者空間程序上是很正常的。

nice

nice 表示 CPU 執行在 低優先順序使用者態 的時間佔比,低優先順序意味著程序 nice 值小於 0 。

system

user 表示 CPU 執行在 核心態 的時間佔比。

一般而言, 核心態 CPU 使用率不應過高,除非應用程序發起大量系統呼叫。如果太高,表示系統呼叫時間長,例如是IO操作頻繁。

idle

idle 表示 CPU 在空閒狀態的時間佔比,該狀態下 CPU 沒有任何任務可執行。

irq

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 負載

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,要麼你看不懂我們獲取記憶體指標的程式碼

process.memoryUsage():

該函數返回4個引數,含義及差別如下:

  • rss: (Resident Set Size)作業系統分配給程序的總的記憶體大小。包括所有 C++ 和 JavaScript 物件和程式碼。(比如,堆疊和程式碼段)
  • heapTotal:堆的總大小,包括3個部分,
    • 已分配的記憶體,用於物件的建立和儲存,對應於heapUsed
    • 未分配的但可用於分配的記憶體
    • 未分配的但不能分配的記憶體,例如在垃圾收集(GC)之前物件之間的記憶體碎片
  • heapUsed: 已分配的記憶體,即堆中所有物件的總大小,是heapTotal的子集。
  • external: 程序使用到的系統連結庫所佔用的記憶體, 比如buffer就是屬於external裡的資料。buffer資料不同於其他物件,它不經過V8的記憶體分配機制,所以也不會有堆記憶體大小限制。

用如下程式碼,列印一個子程序的記憶體使用情況,可以看出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

  • 先用df -P獲得所有磁碟情況,這個-P是為了防止有換行
  • startsWith('/')保證是真實磁碟,不是虛擬的
  • line.match(/(\d+)%\s+(/.*$)/) => 匹配磁碟情況和掛載的磁碟,比如'1% /System/Volumes/Preboot'
  • match[1]是字串,表示使用率, match[2]表示掛載的磁碟名稱
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負載指的主要是磁碟I/O。反應的是磁碟上的讀寫情況,對於Node編寫的應用,主要是面向網路服務,是不太可能出現I/O負載過高的情況,多讀書的I/O的壓力來源於資料庫。

獲取I/O指標,我們要了解一個linux命令,叫iostat,如果沒有安裝,需要安裝一下,我們看一下這個命令為啥能反應I/O指標

iostat -dx

image.png

屬性說明

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。

響應時間RT監控

監控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

名詞解釋:

一、QPS,每秒查詢

QPS:Queries Per Second意思是「每秒查詢率」,是一臺伺服器每秒能夠響應的查詢次數,是對一個特定的查詢伺服器在規定時間內所處理流量多少的衡量標準。

網際網路中,作為域名系統伺服器的機器的效能經常用每秒查詢率來衡量。

二、TPS,每秒事務

TPS:是TransactionsPerSecond的縮寫,也就是事務數/秒。它是軟體測試結果的測量單位。一個事務是指一個客戶機向伺服器傳送請求然後伺服器做出反應的過程。客戶機在傳送請求時開始計時,收到伺服器響應後結束計時,以此來計算使用的時間和完成的事務個數。

QPS vs TPS:QPS基本類似於TPS,但是不同的是,對於一個頁面的一次存取,形成一個TPS;但一次頁面請求,可能產生多次對伺服器的請求,伺服器對這些請求,就可計入「QPS」之中。如,存取一個頁面會請求伺服器2次,一次存取,產生一個「T」,產生2個「Q」。

三、RT,響應時間

響應時間:執行一個請求從開始到最後收到響應資料所花費的總體時間,即從使用者端發起請求到收到伺服器響應結果的時間。

響應時間RT(Response-time),是一個系統最重要的指標之一,它的數值大小直接反應了系統的快慢。

四、並行數

並行數是指系統同時能處理的請求數量,這個也是反應了系統的負載能力。

五、吞吐量

系統的吞吐量(承壓能力)與request對CPU的消耗、外部介面、IO等等緊密關聯。單個request 對CPU消耗越高,外部系統介面、IO速度越慢,系統吞吐能力越低,反之越高。

系統吞吐量幾個重要引數:QPS(TPS)、並行數、響應時間。

  • QPS(TPS):(Query Per Second)每秒鐘request/事務 數量

  • 並行數: 系統同時處理的request/事務數

  • 響應時間: 一般取平均響應時間

理解了上面三個要素的意義之後,就能推算出它們之間的關係:

  • QPS(TPS)= 並行數/平均響應時間
  • 並行數 = QPS*平均響應時間

六、實際舉例

我們通過一個範例來把上面幾個概念串起來理解。按二八定律來看,如果每天 80% 的存取集中在 20% 的時間裡,這 20% 時間就叫做峰值時間。

  • 公式:( 總PV數 * 80% ) / ( 每天秒數 * 20% ) = 峰值時間每秒請求數(QPS)
  • 機器:峰值時間每秒QPS / 單臺機器的QPS = 需要的機器

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頭資訊複製程式碼

更多引數請檢視官方檔案。

http://httpd.apache.org/docs/2.2/programs/ab.html

例如測試某個GET請求介面:

ab -n 10000 -c 100 -t 10 "http://127.0.0.1:8080/api/v1/posts?size=10"

得到一下資料:

image.png

我們從中獲取幾個關鍵指標:

  • 吞吐率(Requests per second)在圖上有顯示

伺服器並行處理能力的量化描述,單位是reqs/s,指的是在某個並行使用者數下單位時間內處理的請求數。某個並行使用者數下單位時間內能處理的最大請求數,稱之為最大吞吐率。

記住:吞吐率是基於並行使用者數的。這句話代表了兩個含義:

  • a、吞吐率和並行使用者數相關
  • b、不同的並行使用者數下,吞吐率一般是不同的

計算公式:

總請求數/處理完成這些請求數所花費的時間

必須要說明的是,這個數值表示當前機器的整體效能,值越大越好。

2、QPS每秒查詢率(Query Per Second)

  每秒查詢率QPS是對一個特定的查詢伺服器在規定時間內所處理流量多少的衡量標準,在因特網上,作為域名系統伺服器的機器的效能經常用每秒查詢率來衡量,即每秒的響應請求數,也即是最大吞吐能力。

計算公式

QPS(TPS)= 並行數/平均響應時間(Time per request)

在上圖裡有Time per request的值,然後我們也有並行數資料,就可以計算出QPS。

這個QPS是壓測資料,真實的qps,可使用紀錄檔監控來獲取。

紀錄檔監控

通常情況下,隨著系統的執行,我們的後臺服務會產生各種紀錄檔,應用程式會產生應用程式的存取紀錄檔、錯誤紀錄檔,執行紀錄檔,網路紀錄檔,我們需要一個展示平臺去展示這些紀錄檔。

後端一般都用比如ELk去展示,我們前端都是ui老手了,自己可以畫客製化的UI介面,不多說了,主要是紀錄檔本身要列印符合一定的規範,這樣格式化的資料更利於分析和展示。

並且業務邏輯型的監控主要體現在紀錄檔上。通過監控異常紀錄檔檔案的變動,將新增的異常按異常型別和數量反映出來。某些異常與具體的某個子系統相關,監控出現的某個異常也能反映出子系統的狀態。

在體制監控裡也能體現出實際業務的QPS值。觀察QPS的表現能夠檢查業務在時間上的分部。

此外,從存取紀錄檔中也能實現PV和UV的監控。並且可以從中分析出使用者的習慣,預知存取高峰。

響應時間

這個也可以通過存取紀錄檔來獲取,並且真實響應時間是需要在controller上打log的。

程序監控

監控程序一般是檢查作業系統中執行的應用程序數,比如對於採用多程序架構的node應用,就需要檢查工作程序的數量,如果低於預期值,就當發出報警。

檢視程序數在linux下很簡單,

假如我們通過Node 提供 child_process 模組來實現多核 CPU 的利用。child_process.fork() 函數來實現程序的複製。

worker.js 程式碼如下:

var http = require('http')\
http.createServer(function(req, res) {\
res.writeHead(200, { 'Content-Type': 'text/plain' })\
res.end('Hello World\n')\
}).listen(Math.round((1 + Math.random()) * 1000), '127.0.0.1')\

通過 node worker.js 啟動它,會監聽 1000 到 2000 之間的一個隨機埠。

master.js 程式碼如下:

var fork = require('child_process').fork
var cpus = require('os').cpus()
for (var i = 0; i < cpus.length; i++) {
  fork('./worker.js')
}

檢視程序數的 命令如下:

ps aux | grep worker.js
$ ps aux | grep worker.js
lizhen 1475 0.0 0.0 2432768 600 s003 S+ 3:27AM 0:00.00 grep worker.js\
lizhen 1440 0.0 0.2 3022452 12680 s003 S 3:25AM 0:00.14 /usr/local/bin/node ./worker.js\
lizhen 1439 0.0 0.2 3023476 12716 s003 S 3:25AM 0:00.14 /usr/local/bin/node ./worker.js\
lizhen 1438 0.0 0.2 3022452 12704 s003 S 3:25AM 0:00.14 /usr/local/bin/node ./worker.js\
lizhen 1437 0.0 0.2 3031668 12696 s003 S 3:25AM 0:00.15 /usr/local/bin/node ./worker.js\

更多node相關知識,請存取:!

以上就是怎麼獲取Node效能監控指標?獲取方法分享的詳細內容,更多請關注TW511.COM其它相關文章!