首頁  >  文章  >  web前端  >  怎麼取得Node效能監控指標?獲取方法分享

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

青灯夜游
青灯夜游轉載
2022-04-19 21:25:134413瀏覽

怎麼取得Node效能監控指標?這篇文章來和大家聊聊Node效能監控指標取得方法,希望對大家有幫助!

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

最近在學習nodejs監控的知識,雖然沒有精力去學習寫一個簡易版監控,但還是忍不住了解一下如何獲取這些指標(查閱了很多資料,覺得國內網上對於這塊內容介紹是在太少了,自己也在整理服務端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(&#39;cpuUsage: &#39;, 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(&#39;os&#39;);
// 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) + &#39; MB&#39;;
   };
   console.log(&#39;Process: heapTotal &#39; + format(mem.heapTotal) + &#39; heapUsed &#39; + format(mem.heapUsed) + &#39; rss &#39; + format(mem.rss) + &#39; external:&#39; + format(mem.external));
   console.log(&#39;-----------------------------------------------------------&#39;);
};

對於Node而言,一旦出現記憶體洩漏,就不是那麼容易排查。如果監控到記憶體只升不降,那麼鐵定有記憶體外洩問題。健康的記憶體使用應該有升有降。存取大的時候上升,存取回落下降

取得記憶體指標的程式碼

const os = require(&#39;os&#39;);
// 查看当前 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(&#39;child_process&#39;);
const result = execSync(&#39;df -P&#39;, { encoding: &#39;utf8&#39;})
const lines = result.split(&#39;\n&#39;);
const metric = {};
lines.forEach(line => {
  if (line.startsWith(&#39;/&#39;)) {
    const match = line.match(/(\d+)%\s+(\/.*$)/);
    if (match) {
      const rate = parseInt(match[1] || 0);
      const mounted = match[2];
      if (!mounted.startsWith(&#39;/Volumes/&#39;) && !mounted.startsWith(&#39;/private/&#39;)) {
        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

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

屬性說明

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(&#39;X-Execution-Time&#39;, 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"

得到一下数据:

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

我们从中获取几个关键指标:

  • 吞吐率(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(&#39;http&#39;)\
http.createServer(function(req, res) {\
res.writeHead(200, { &#39;Content-Type&#39;: &#39;text/plain&#39; })\
res.end(&#39;Hello World\n&#39;)\
}).listen(Math.round((1 + Math.random()) * 1000), &#39;127.0.0.1&#39;)\

通过 node worker.js 启动它,会监听 1000 到 2000 之间的一个随机端口。

master.js 代码如下:

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

查看进程数的 命令如下:

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相关知识,请访问:nodejs 教程

以上是怎麼取得Node效能監控指標?獲取方法分享的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除