天天看點

node服務的監控預警系統架構

需求背景

目前node端的服務逐漸成熟,在不少公司内部也開始承擔業務處理或者視圖渲染工作。不同于個人開發的簡單伺服器,企業級的node服務要求更為苛刻:

高穩定性、高可靠性、魯棒性以及直覺的監控和報警

想象下一個存在安全隐患且沒有監控預警系統的node服務在生産環境下運作的場景,當某個node執行個體挂掉的情況下,運維人員或者對應開發維護人員無法立即知曉,直到客戶或者測試人員報告bugs才開始解決問題。在這段無人處理的時間内,損失的訂單數和使用者的忠誠度和信任度将是以後無法彌補的,是以對于node程式的業務開發者而言,這就要求代碼嚴謹、異常處理完備;對于node架構的維護者而言,則需要提供完善的監控預警系統。

功能

當一個服務程序在後端運作時(daemon),作為開發者我們關注的資訊主要有以下幾點:

  • 服務程序是否正在運作,isalive
  • 服務程序的記憶體使用率,是否存在未回收(釋放)的記憶體
  • 服務程序的cpu使用率,在計算量大的情況下是否需要分片處理、延時處理
  • 服務程序的實時響應時間和吞吐量

而作為一個運維人員,關注的不僅僅是node服務程序的相關資訊,還包括實體主機的使用狀況:

  • 實體硬碟所剩存儲空間
  • 記憶體、cpu使用率
  • 網絡接入是否正常

可以看出,不管是針對主機還是程序進行監控,我們的關注點大多數是資源使用率和業務量處理能力,是以我們的監控預警系統也着重實作這些功能。

系統簡易架構

目前生産環境下的node服務大多采用多程序或者cluster模式,而且為了響應突發流量往往采用多機部署,是以監控和預警的目标實體就是多實體(虛拟)機下的多個子程序。

比如,目前node服務在單機上往往采用1+n的程序模型:所謂1,即1個主程序;n,表示n個工作程序,而且這些工作程序是從主程序上fork出來,同時根據經驗,n的值往往等同于主機的cpu核心數,充分利用其并行能力。那麼,采用該種程序模型的node服務部署線上上4台實體機上,我們需要監控的則是4xn個程序,這涉及到了分布式資料同步的問題,需要尋找一種方法實作高效、準确和簡易的資料存和讀,并且盡可能的保證這些資料的可靠性。

在這裡,筆者采用了分布式資料一緻系統ZooKeeper(下文簡寫為ZK)實作資料的存和讀。之是以沒有采用傳統的資料庫是由于讀寫表的性能,如為了防止多個程序同時寫表造成沖突必須進行鎖表等操作,而且讀寫硬碟的性能相對記憶體讀寫較低;之是以沒有采用IPC+事件機制實作多程序通信,主要是由于node提供的IPC通信機制僅限于父子程序,對于不同主機的程序無法進行通信或者實作複雜度較高,是以也并未采用該種方式。

采用ZK來實作多節點下的資料同步,可在保證叢集可靠性的基礎上達到資料的最終一緻性,對于監控系統而言,不需要時刻都精确的資料,是以資料的最終一緻性完全滿足系統的需求。ZK服務叢集通過paxos算法實作選舉,并采用ZK獨特的算法實作資料在各個叢集節點的同步,最終抽象為一個資料層。這樣ZK用戶端就可以通過通路ZK叢集的任意一個服務節點擷取或讀寫相同的資料,用通俗的語言來形容,就是ZK用戶端看到的所有ZK服務節點都有相同的資料。

另外,ZK提供了一種臨時節點,即ephemeral。該節點與用戶端的會話session相綁定,一旦會話逾時或者連接配接斷開,該節點就會消失,并觸發對應事件,是以利用該種特性可以設定node服務的isalive(是否存活)功能。不過,目前node社群針對ZK的用戶端還不是很完善(主要是文檔),筆者采用node-zookeeper-client子產品并且針對所有接口promise化,這樣在進行多級znode開發時更可讀。

node服務的監控預警系統架構

上圖是筆者設計的監控預警系統的架構圖,這裡需要着重關注一下幾點:

  • ZooKeeper部署與znode節點使用
  • 單機内部node程序的程序模型:1+n+1
  • precaution程序的工作内容以及與master和worker的通信方式

下面着重詳述以上幾點。

ZooKeeper部署與編碼細節

上節已提到,ZooKeeper抽象為一個資料一緻層,它是由多個節點組成的存儲叢集,是以在具體的線上環境下,ZK叢集是由多個線上主機搭建而成,所有的資料都是存儲在記憶體中,每當對應工作程序的資料發生變化時則修改對應znode節點的資料,在具體實作中每個znode節點存儲的是json資料,便于node端直接解析。

在具體的代碼中,我們需要注意的是ZK用戶端會話逾時和網絡斷開重連的問題。預設,ZK用戶端會幫助我們完成網絡斷開後重連過程的建立,而且在重新連接配接的過程中會攜帶上次斷開連接配接的session id,這樣在session未逾時的前提下仍會綁定之前的資料;但是當session逾時的情況下,對應session id的資料将會被清空,這就需要我們的自己處理這種情況,又稱作現場恢複。其實,在監控系統中,由于需要實時查詢對應節點資料,需要始終保持session,在設定session expire時間的情況下終究會出現ZK用戶端會話逾時的情況,是以需要我們實作現場恢複,需要注意。

程序模型

大多數開發者為了提高node程式的并行處理能力,往往采用一個主程序+多個工作程序的方式處理請求,這在不需要監控預警系統的前提下是可以滿足要求的。但是,随着監控預警功能的加入,有很多人估計會把這些功能加入到主程序,這首先不說主程序工作職能的混亂,最主要的是額外增加了風險性(預警系統的職能之一就是打點堆快照,并提醒開發者。是以主程序内執行查詢、打點系統資源、發送郵件等工作存在可能的風險)。是以為了主程序的功能單一性和可靠性,建立了一個precaution程序,該程序與主程序同級。

采用1+n+1模型并不會影響請求處理效率,工作程序的職能仍是處理請求,是以新的程序模型完全相容之前的代碼,需要做的就是在主程序和precaution程序執行的代碼中添加業務部分代碼。

通信方式

在監控預警系統中,需要實作precaution程序<-->master程序、master程序<-->worker程序、precaution程序<-->worker程序的雙向通信,如打點記憶體,需要由precaution程序通知對應worker程序,worker進行打點完成後發送消息給precaution程序,precaution進行處理後發送郵件通知。

首先,worker與master的通信走的是node提供的IPC通道,需要注意的是IPC通道隻能傳輸字元串和可結構化的對象。可結構化的對象可以用一個公式簡易表述:

o = JSON.parse(JSON.stringify(o))
           

如RegExp的執行個體就不是可結構化對象。

其次,worker和precaution的通信是通過master作為橋梁實作的,是以其中的關節點就在于precaution與master的通信。

最後,precaution與master的通信采用domain socket機制實作,這兩個程序是隻是兩個node執行個體而已,是以無法采用node提供的IPC機制,而程序間通信可以采用其他方法如:命名管道、共享記憶體、信号量和消息隊列等,采用這些方法實作固然簡單,但是缺點在于兩個程序耦合度相對較高,如命名管道需要建立具體的管道檔案并且對管道檔案大小有限制。使用domain socket,最大的好處就是靈活制定通信協定,且易于擴充。

node的net子產品提供了domain socket的通信方式,與網絡伺服器類似,采用domain通信的伺服器偵聽的不是端口而是sock檔案,采用這種方式實作全雙工通信。

業務量計算和資料打點

這裡提到的業務量,指的是監控預警系統所關注的資料業務,如記憶體和cpu使用率、吞吐量(request per minute)和響應時間。其中,記憶體和cpu使用率可以通過linux下的相關指令如top來查詢,響應時間和吞吐量則通過koa中間件實作粗略統計。不過為了友善開發者把精力集中到業務上去而非相容底層作業系統,建議使用pidusage子產品完成資源使用率的測量,而針對吞吐量筆者并未找到相關的工具進行測量,僅在中間件中粗略計算得出。

在precaution程序中,設定了兩個門檻值。一個是warning值,當使用記憶體大小超過了該值則進行日志打點,并開始周期性的node堆記憶體打點;另一個是danger值,超過該值則進行記憶體打點并發送郵件提醒,根據附件中的近三個快照分析記憶體。

總結

采用上述監控預警架構,可以有效的實作多節點下多程序的監控,在確定程序可靠性的基礎上完成侵入性較小的、安全性較高的、可擴充性強的實作。以後不管是臨時擴張主機節點還是更改子程序數量,都可以瞬時在UI界面上直覺展現,如

node服務的監控預警系統架構