本文由尚妝前端開(kāi)發(fā)工程師欲休撰寫
本文發(fā)表于尚妝博客
需求背景
目前node端的服務(wù)逐漸成熟寓涨,在不少公司內(nèi)部也開(kāi)始承擔(dān)業(yè)務(wù)處理或者視圖渲染工作。不同于個(gè)人開(kāi)發(fā)的簡(jiǎn)單服務(wù)器砾省,企業(yè)級(jí)的node服務(wù)要求更為苛刻:
高穩(wěn)定性客燕、高可靠性、魯棒性以及直觀的監(jiān)控和報(bào)警
想象下一個(gè)存在安全隱患且沒(méi)有監(jiān)控預(yù)警系統(tǒng)的node服務(wù)在生產(chǎn)環(huán)境下運(yùn)行的場(chǎng)景婚瓜,當(dāng)某個(gè)node實(shí)例掛掉的情況下宝鼓,運(yùn)維人員或者對(duì)應(yīng)開(kāi)發(fā)維護(hù)人員無(wú)法立即知曉,直到客戶或者測(cè)試人員報(bào)告bugs才開(kāi)始解決問(wèn)題巴刻。在這段無(wú)人處理的時(shí)間內(nèi)愚铡,損失的訂單數(shù)和用戶的忠誠(chéng)度和信任度將是以后無(wú)法彌補(bǔ)的,因此對(duì)于node程序的業(yè)務(wù)開(kāi)發(fā)者而言胡陪,這就要求代碼嚴(yán)謹(jǐn)沥寥、異常處理完備;對(duì)于node框架的維護(hù)者而言柠座,則需要提供完善的監(jiān)控預(yù)警系統(tǒng)邑雅。
功能
當(dāng)一個(gè)服務(wù)進(jìn)程在后端運(yùn)行時(shí)(daemon),作為開(kāi)發(fā)者我們關(guān)注的信息主要有以下幾點(diǎn):
- 服務(wù)進(jìn)程是否正在運(yùn)行妈经,isalive
- 服務(wù)進(jìn)程的內(nèi)存使用率淮野,是否存在未回收(釋放)的內(nèi)存
- 服務(wù)進(jìn)程的cpu使用率,在計(jì)算量大的情況下是否需要分片處理吹泡、延時(shí)處理
- 服務(wù)進(jìn)程的實(shí)時(shí)響應(yīng)時(shí)間和吞吐量
而作為一個(gè)運(yùn)維人員骤星,關(guān)注的不僅僅是node服務(wù)進(jìn)程的相關(guān)信息,還包括物理主機(jī)的使用狀況:
- 物理硬盤所剩存儲(chǔ)空間
- 內(nèi)存荞胡、cpu使用率
- 網(wǎng)絡(luò)接入是否正常
可以看出妈踊,不管是針對(duì)主機(jī)還是進(jìn)程進(jìn)行監(jiān)控,我們的關(guān)注點(diǎn)大多數(shù)是資源使用率和業(yè)務(wù)量處理能力泪漂,因此我們的監(jiān)控預(yù)警系統(tǒng)也著重實(shí)現(xiàn)這些功能廊营。
系統(tǒng)簡(jiǎn)易架構(gòu)
目前生產(chǎn)環(huán)境下的node服務(wù)大多采用多進(jìn)程或者cluster模式歪泳,而且為了響應(yīng)突發(fā)流量往往采用多機(jī)部署,因此監(jiān)控和預(yù)警的目標(biāo)實(shí)體就是多物理(虛擬)機(jī)下的多個(gè)子進(jìn)程露筒。
比如呐伞,目前node服務(wù)在單機(jī)上往往采用1+n的進(jìn)程模型:所謂1,即1個(gè)主進(jìn)程慎式;n伶氢,表示n個(gè)工作進(jìn)程,而且這些工作進(jìn)程是從主進(jìn)程上fork出來(lái)瘪吏,同時(shí)根據(jù)經(jīng)驗(yàn)癣防,n的值往往等同于主機(jī)的cpu核心數(shù),充分利用其并行能力掌眠。那么蕾盯,采用該種進(jìn)程模型的node服務(wù)部署在線上4臺(tái)物理機(jī)上,我們需要監(jiān)控的則是4xn個(gè)進(jìn)程蓝丙,這涉及到了分布式數(shù)據(jù)同步的問(wèn)題级遭,需要尋找一種方法實(shí)現(xiàn)高效、準(zhǔn)確和簡(jiǎn)易的數(shù)據(jù)存和讀渺尘,并且盡可能的保證這些數(shù)據(jù)的可靠性挫鸽。
在這里,筆者采用了分布式數(shù)據(jù)一致系統(tǒng)ZooKeeper(下文簡(jiǎn)寫為ZK)實(shí)現(xiàn)數(shù)據(jù)的存和讀鸥跟。之所以沒(méi)有采用傳統(tǒng)的數(shù)據(jù)庫(kù)是由于讀寫表的性能丢郊,如為了防止多個(gè)進(jìn)程同時(shí)寫表造成沖突必須進(jìn)行鎖表等操作,而且讀寫硬盤的性能相對(duì)內(nèi)存讀寫較低锌雀;之所以沒(méi)有采用IPC+事件機(jī)制實(shí)現(xiàn)多進(jìn)程通信蚂夕,主要是由于node提供的IPC通信機(jī)制僅限于父子進(jìn)程,對(duì)于不同主機(jī)的進(jìn)程無(wú)法進(jìn)行通信或者實(shí)現(xiàn)復(fù)雜度較高腋逆,因此也并未采用該種方式婿牍。
采用ZK來(lái)實(shí)現(xiàn)多節(jié)點(diǎn)下的數(shù)據(jù)同步,可在保證集群可靠性的基礎(chǔ)上達(dá)到數(shù)據(jù)的最終一致性惩歉,對(duì)于監(jiān)控系統(tǒng)而言等脂,不需要時(shí)刻都精確的數(shù)據(jù),因此數(shù)據(jù)的最終一致性完全滿足系統(tǒng)的需求撑蚌。ZK服務(wù)集群通過(guò)paxos算法實(shí)現(xiàn)選舉上遥,并采用ZK獨(dú)特的算法實(shí)現(xiàn)數(shù)據(jù)在各個(gè)集群節(jié)點(diǎn)的同步,最終抽象為一個(gè)數(shù)據(jù)層争涌。這樣ZK客戶端就可以通過(guò)訪問(wèn)ZK集群的任意一個(gè)服務(wù)節(jié)點(diǎn)獲取或讀寫相同的數(shù)據(jù)粉楚,用通俗的語(yǔ)言來(lái)形容,就是ZK客戶端看到的所有ZK服務(wù)節(jié)點(diǎn)都有相同的數(shù)據(jù)。
另外模软,ZK提供了一種臨時(shí)節(jié)點(diǎn)伟骨,即ephemeral。該節(jié)點(diǎn)與客戶端的會(huì)話session相綁定燃异,一旦會(huì)話超時(shí)或者連接斷開(kāi)携狭,該節(jié)點(diǎn)就會(huì)消失,并觸發(fā)對(duì)應(yīng)事件回俐,因此利用該種特性可以設(shè)置node服務(wù)的isalive(是否存活)功能逛腿。不過(guò),目前node社區(qū)針對(duì)ZK的客戶端還不是很完善(主要是文檔)仅颇,筆者采用node-zookeeper-client模塊并且針對(duì)所有接口promise化单默,這樣在進(jìn)行多級(jí)znode開(kāi)發(fā)時(shí)更可讀。
上圖是筆者設(shè)計(jì)的監(jiān)控預(yù)警系統(tǒng)的架構(gòu)圖灵莲,這里需要著重關(guān)注一下幾點(diǎn):
- ZooKeeper部署與znode節(jié)點(diǎn)使用
- 單機(jī)內(nèi)部node進(jìn)程的進(jìn)程模型:1+n+1
- precaution進(jìn)程的工作內(nèi)容以及與master和worker的通信方式
下面著重詳述以上幾點(diǎn)雕凹。
ZooKeeper部署與編碼細(xì)節(jié)
上節(jié)已提到殴俱,ZooKeeper抽象為一個(gè)數(shù)據(jù)一致層政冻,它是由多個(gè)節(jié)點(diǎn)組成的存儲(chǔ)集群,因此在具體的線上環(huán)境下线欲,ZK集群是由多個(gè)線上主機(jī)搭建而成明场,所有的數(shù)據(jù)都是存儲(chǔ)在內(nèi)存中,每當(dāng)對(duì)應(yīng)工作進(jìn)程的數(shù)據(jù)發(fā)生變化時(shí)則修改對(duì)應(yīng)znode節(jié)點(diǎn)的數(shù)據(jù)李丰,在具體實(shí)現(xiàn)中每個(gè)znode節(jié)點(diǎn)存儲(chǔ)的是json數(shù)據(jù)苦锨,便于node端直接解析。
在具體的代碼中趴泌,我們需要注意的是ZK客戶端會(huì)話超時(shí)和網(wǎng)絡(luò)斷開(kāi)重連的問(wèn)題舟舒。默認(rèn),ZK客戶端會(huì)幫助我們完成網(wǎng)絡(luò)斷開(kāi)后重連過(guò)程的簡(jiǎn)歷嗜憔,而且在重新連接的過(guò)程中會(huì)攜帶上次斷開(kāi)連接的session id秃励,這樣在session未超時(shí)的前提下仍會(huì)綁定之前的數(shù)據(jù);但是當(dāng)session超時(shí)的情況下吉捶,對(duì)應(yīng)session id的數(shù)據(jù)將會(huì)被清空夺鲜,這就需要我們的自己處理這種情況,又稱作現(xiàn)場(chǎng)恢復(fù)呐舔。其實(shí)币励,在監(jiān)控系統(tǒng)中,由于需要實(shí)時(shí)查詢對(duì)應(yīng)節(jié)點(diǎn)數(shù)據(jù)珊拼,需要始終保持session食呻,在設(shè)定session expire時(shí)間的情況下終究會(huì)出現(xiàn)ZK客戶端會(huì)話超時(shí)的情況,因此需要我們實(shí)現(xiàn)現(xiàn)場(chǎng)恢復(fù),需要注意仅胞。
進(jìn)程模型
大多數(shù)開(kāi)發(fā)者為了提高node程序的并行處理能力浪感,往往采用一個(gè)主進(jìn)程+多個(gè)工作進(jìn)程的方式處理請(qǐng)求,這在不需要監(jiān)控預(yù)警系統(tǒng)的前提下是可以滿足要求的饼问。但是影兽,隨著監(jiān)控預(yù)警功能的加入,有很多人估計(jì)會(huì)把這些功能加入到主進(jìn)程莱革,這首先不說(shuō)主進(jìn)程工作職能的混亂峻堰,最主要的是額外增加了風(fēng)險(xiǎn)性(預(yù)警系統(tǒng)的職能之一就是打點(diǎn)堆快照,并提醒開(kāi)發(fā)者盅视。因此主進(jìn)程內(nèi)執(zhí)行查詢捐名、打點(diǎn)系統(tǒng)資源、發(fā)送郵件等工作存在可能的風(fēng)險(xiǎn))闹击。因此為了主進(jìn)程的功能單一性和可靠性镶蹋,創(chuàng)建了一個(gè)precaution進(jìn)程,該進(jìn)程與主進(jìn)程同級(jí)赏半。
采用1+n+1模型并不會(huì)影響請(qǐng)求處理效率贺归,工作進(jìn)程的職能仍是處理請(qǐng)求,因此新的進(jìn)程模型完全兼容之前的代碼断箫,需要做的就是在主進(jìn)程和precaution進(jìn)程執(zhí)行的代碼中添加業(yè)務(wù)部分代碼拂酣。
通信方式
在監(jiān)控預(yù)警系統(tǒng)中,需要實(shí)現(xiàn)precaution進(jìn)程<-->master進(jìn)程仲义、master進(jìn)程<-->worker進(jìn)程婶熬、precaution進(jìn)程<-->worker進(jìn)程的雙向通信,如打點(diǎn)內(nèi)存埃撵,需要由precaution進(jìn)程通知對(duì)應(yīng)worker進(jìn)程赵颅,worker進(jìn)行打點(diǎn)完成后發(fā)送消息給precaution進(jìn)程,precaution進(jìn)行處理后發(fā)送郵件通知暂刘。
首先饺谬,worker與master的通信走的是node提供的IPC通道,需要注意的是IPC通道只能傳輸字符串和可結(jié)構(gòu)化的對(duì)象鸳惯∩淘蹋可結(jié)構(gòu)化的對(duì)象可以用一個(gè)公式簡(jiǎn)易表述:
o = JSON.parse(JSON.stringify(o))
如RegExp的實(shí)例就不是可結(jié)構(gòu)化對(duì)象。
其次芝发,worker和precaution的通信是通過(guò)master作為橋梁實(shí)現(xiàn)的绪商,因此其中的關(guān)節(jié)點(diǎn)就在于precaution與master的通信。
最后辅鲸,precaution與master的通信采用domain socket機(jī)制實(shí)現(xiàn)格郁,這兩個(gè)進(jìn)程是只是兩個(gè)node實(shí)例而已,因此無(wú)法采用node提供的IPC機(jī)制,而進(jìn)程間通信可以采用其他方法如:命名管道例书、共享內(nèi)存锣尉、信號(hào)量和消息隊(duì)列等,采用這些方法實(shí)現(xiàn)固然簡(jiǎn)單决采,但是缺點(diǎn)在于兩個(gè)進(jìn)程耦合度相對(duì)較高自沧,如命名管道需要?jiǎng)?chuàng)建具體的管道文件并且對(duì)管道文件大小有限制。使用domain socket树瞭,最大的好處就是靈活制定通信協(xié)議拇厢,且易于擴(kuò)展。
node的net模塊提供了domain socket的通信方式晒喷,與網(wǎng)絡(luò)服務(wù)器類似孝偎,采用domain通信的服務(wù)器偵聽(tīng)的不是端口而是sock文件,采用這種方式實(shí)現(xiàn)全雙工通信凉敲。
業(yè)務(wù)量計(jì)算和數(shù)據(jù)打點(diǎn)
這里提到的業(yè)務(wù)量衣盾,指的是監(jiān)控預(yù)警系統(tǒng)所關(guān)注的數(shù)據(jù)業(yè)務(wù),如內(nèi)存和cpu利用率爷抓、吞吐量(request per minute)和響應(yīng)時(shí)間势决。其中,內(nèi)存和cpu利用率可以通過(guò)linux下的相關(guān)命令如top來(lái)查詢废赞,響應(yīng)時(shí)間和吞吐量則通過(guò)koa中間件實(shí)現(xiàn)粗略統(tǒng)計(jì)徽龟。不過(guò)為了方便開(kāi)發(fā)者把精力集中到業(yè)務(wù)上去而非兼容底層操作系統(tǒng),建議使用pidusage模塊完成資源利用率的測(cè)量唉地,而針對(duì)吞吐量筆者并未找到相關(guān)的工具進(jìn)行測(cè)量,僅在中間件中粗略計(jì)算得出传透。
在precaution進(jìn)程中耘沼,設(shè)置了兩個(gè)閾值。一個(gè)是warning值朱盐,當(dāng)使用內(nèi)存大小超過(guò)了該值則進(jìn)行日志打點(diǎn)群嗤,并開(kāi)始周期性的node堆內(nèi)存打點(diǎn);另一個(gè)是danger值兵琳,超過(guò)該值則進(jìn)行內(nèi)存打點(diǎn)并發(fā)送郵件提醒狂秘,根據(jù)附件中的近三個(gè)快照分析內(nèi)存。