在 Node 服務中發生 OOM 時,如何監控記憶體?

shanyue發表於2020-07-03
本文章已備份在 github 上 山月的部落格 歡迎 star

剛開始,先丟擲一個問題:

你知道你們生產環境的 Node 服務平時佔用記憶體多少嗎?或者說是多少量級?

山月在面試 Node 候選人時,這個問題足夠篩掉一半的自稱Node精通者,不過沒有回答上來,我往往會再補充一個問題,以免漏掉優秀的無線上經驗的候選人:

如何知道某個程式消耗多少記憶體?

當使用 Node 在生產環境作為伺服器語言時,併發量過大或者程式碼問題造成 OOM (out of memory) 或者 CPU 滿載這些都是伺服器中常見的問題,此時通過監控 CPU 及記憶體,再結合日誌及 Release 就很容易發現問題。

本章將介紹如何監控本地環境及生產環境的記憶體變化

一個 Node 應用例項

所以,如何動態監控一個 Node 程式的記憶體變化呢?

以下是一個 Node Server 的示例,並且是一個有記憶體洩漏問題的示例,並且是山月在生產環境定位了很久的問題的精簡版。

那次記憶體洩漏問題中,導致單個容器中的記憶體從原先的 400M 暴漲到 700M,在 800M 的容器資源限制下偶爾會發生 OOM,導致重啟。一時沒有定位到問題 (發現問題過遲,半個月前的時序資料已被吞沒,於是未定位到 Release),於是把資源限制上調到 1000M。後發現是由 ctx.request 掛載了資料庫某個大欄位而致
const Koa = require('koa')
const app = new Koa()

function getData () {
  return Array.from(Array(1000)).map(x => 10086)
}

app.use(async (ctx, next) => {
  ctx.data = getData()
  await next()
})

app.use(ctx => {
  ctx.body = 'hello, world'
})

app.listen(3200, () => console.log('Port: 3200'))

程式記憶體監控

一些問題需要在本地及測試環境得到及時扼殺,來避免在生產環境造成更大的影響。那麼瞭解在本地如何監控記憶體就至關重要。

pidstatsysstat 系列 linux 效能除錯工具的一個包,竟然用它來除錯 linux 的效能問題,包括記憶體,網路,IO,CPU 等。

這不僅試用與 node,而且適用於一切程式,包括 pythonjava 以及 go

# -r: 指輸出記憶體指標
# -p: 指定 pid
# 1: 每一秒輸出一次
# 100: 輸出100次
$ pidstat -r -p pid 1 100

而在使用 pidstat 之前,需要先找到程式的 pid

如何找到 Node 程式的 pid

node 中可以通過 process.pid 來找到程式的 pid

> process.pid
16425

雖然通過寫程式碼可以找到 pid,但是具有侵入性,不太實用。那如何通過非侵入的手段找到 pid 呢?有兩種辦法

  1. 通過多餘的引數結合 ps 定位程式
  2. 通過埠號結合 lsof 定位程式
$ node index.js shanyue

# 第一種方法:通過多餘的引數快速定位 pid
$ ps -ef | grep shanyue
root     31796 23839  1 16:38 pts/5    00:00:00 node index.js shanyue

# 第二種方法:通過埠號定位 pid
lsof -i:3200
COMMAND   PID USER   FD   TYPE    DEVICE SIZE/OFF NODE NAME
node    31796 root   20u  IPv6 235987334      0t0  TCP *:tick-port (LISTEN)

使用 pidstat 監控記憶體

從以上程式碼中可以知道,node 服務的 pid 為 31796,為了可以觀察到記憶體的動態變化,再施加一個壓力測試

$ ab -c 10000 -n 1000000 http://localhost:3200/
# -r: 指輸出記憶體指標
# -p: 指定 pid
# 1: 每一秒輸出一次
# 100: 輸出100次
$ pidstat -r -p 31796 1 100
Linux 3.10.0-957.21.3.el7.x86_64 (shuifeng)     2020年07月02日  _x86_64_        (2 CPU)

             UID       PID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
19時20分39秒     0     11401      0.00      0.00  566768  19800   0.12  node
19時20分40秒     0     11401      0.00      0.00  566768  19800   0.12  node
19時20分41秒     0     11401   9667.00      0.00  579024  37792   0.23  node
19時20分42秒     0     11401  11311.00      0.00  600716  59988   0.37  node
19時20分43秒     0     11401   5417.82      0.00  611420  70900   0.44  node
19時20分44秒     0     11401   3901.00      0.00  627292  85928   0.53  node
19時20分45秒     0     11401   1560.00      0.00  621660  81208   0.50  node
19時20分46秒     0     11401   2390.00      0.00  623964  83696   0.51  node
19時20分47秒     0     11401   1764.00      0.00  625500  85204   0.52  node

對於輸出指標的含義如下

  • RSS: Resident Set Size,常駐記憶體集,可理解為記憶體,這就是我們需要監控的記憶體指標
  • VSZ: virtual size,虛擬記憶體

從輸出可以看出,當施加了壓力測試後,記憶體由 19M 漲到了 85M。

使用 top 監控記憶體

pidstat 是屬於 sysstat 下的 linux 效能工具,但在 mac 中,如何定位記憶體的變化?

此時可以使用 top/htop

$ htop -p 31796

使用 htop 監控記憶體

生產環境記憶體監控

由於目前生產環境大都部署在 k8s因此生產環境對於某個應用的記憶體監控本質上是 k8s 對於某個 workload/deployment 的記憶體監控,關於記憶體監控 metric 的資料流向大致如下:

k8s -> metric server -> prometheus -> grafana

架構圖如下:

以上圖片取自以下文章

最終能夠在 grafana 中收集到某一應用的記憶體監控實時圖:

由於本部分設計內容過多,我將在以下的章節中進行介紹

這不僅僅適用於 node 服務,而且適用於一切 k8s 上的 workload

總結

本章介紹了關於 Node 服務的記憶體在本地環境及生產環境的監控

  1. 本地使用 htop/top 或者 pidstat 監控程式記憶體
  2. 生產環境使用 k8s/metric-server/prometheus/grafana 監控 node 整個應用的記憶體

當監控到某一服務發生記憶體洩漏後,如何解決問題?因此接下來的文章將會講到

  1. 生產環境是如何監控整個應用的記憶體的
  2. 當生產環境發生 OOM 後,如何快速定位
  3. 真實生產環境若干 OOM 的示例定位
本文由部落格一文多發平臺 OpenWrite 釋出!

相關文章