目前node端的服務逐漸成熟,在很多公司內部也開始承擔業務處理或者視圖渲染工做。不一樣於我的開發的簡單服務器,企業級的node服務要求更爲苛刻:node
高穩定性、高可靠性、魯棒性以及直觀的監控和報警linux
想象下一個存在安全隱患且沒有監控預警系統的node服務在生產環境下運行的場景,當某個node實例掛掉的狀況下,運維人員或者對應開發維護人員沒法當即知曉,直到客戶或者測試人員報告bugs纔開始解決問題。在這段無人處理的時間內,損失的訂單數和用戶的忠誠度和信任度將是之後沒法彌補的,所以對於node程序的業務開發者而言,這就要求代碼嚴謹、異常處理完備;對於node框架的維護者而言,則須要提供完善的監控預警系統。算法
當一個服務進程在後端運行時(daemon),做爲開發者咱們關注的信息主要有如下幾點:數據庫
服務進程是否正在運行,isalivejson
服務進程的內存使用率,是否存在未回收(釋放)的內存後端
服務進程的cpu使用率,在計算量大的狀況下是否須要分片處理、延時處理promise
服務進程的實時響應時間和吞吐量安全
而做爲一個運維人員,關注的不只僅是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開發時更可讀。
上圖是筆者設計的監控預警系統的架構圖,這裏須要着重關注一下幾點:
ZooKeeper部署與znode節點使用
單機內部node進程的進程模型:1+n+1
precaution進程的工做內容以及與master和worker的通訊方式
下面着重詳述以上幾點。
上節已提到,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界面上直觀體現,如