Spark Streaming + Elasticsearch構(gòu)建App異常監(jiān)控平臺(tái)
作者:美團(tuán)
Spark Streaming Elastic Search如果在使用App時(shí)遇到閃退,你可能會(huì)選擇卸載App、到應(yīng)用商店怒斥開發(fā)者等方式來(lái)表達(dá)不滿。但開發(fā)者也同樣感到頭疼,因?yàn)楸罎⒖赡芤馕吨脩袅魇АI(yíng)收下滑。為了降低崩潰率,進(jìn)而提升App質(zhì)量,App開發(fā)團(tuán)隊(duì)需要實(shí)時(shí)地監(jiān)控App異常。一旦發(fā)現(xiàn)嚴(yán)重問(wèn)題,及時(shí)進(jìn)行熱修復(fù),從而把損失降到最低。App異常監(jiān)控平臺(tái),就是將這個(gè)方法服務(wù)化。
低成本
小型創(chuàng)業(yè)團(tuán)隊(duì)一般會(huì)選擇第三方平臺(tái)提供的異常監(jiān)控服務(wù)。但中型以上規(guī)模的團(tuán)隊(duì),往往會(huì)因?yàn)椴幌氚押诵臄?shù)據(jù)共享給第三方平臺(tái),而選擇獨(dú)立開發(fā)。造輪子,首先要考慮的就是成本問(wèn)題。我們選擇了站在開源巨人的肩膀上,如圖1所示。
圖1
Spark Streaming
每天來(lái)自客戶端和服務(wù)器的大量異常信息,會(huì)源源不斷的上報(bào)到異常平臺(tái)的Kafka中,因此我們面臨的是一個(gè)大規(guī)模流式數(shù)據(jù)處理問(wèn)題。美團(tuán)點(diǎn)評(píng)數(shù)據(jù)平臺(tái)提供了Storm和Spark Streaming兩種流式計(jì)算解決方案。我們主要考慮到團(tuán)隊(duì)之前在Spark批處理方面有較多積累,使用Spark Streaming成本較低,就選擇了后者。
Elasticsearch
Elasticsearch(后文簡(jiǎn)稱ES),是一個(gè)開源搜索引擎。不過(guò)在監(jiān)控平臺(tái)中,我們是當(dāng)做“數(shù)據(jù)庫(kù)”來(lái)使用的。為了降低展示層的接入成本,我們還使用了另一個(gè)開源項(xiàng)目ES SQL提供類SQL查詢。ES的運(yùn)維成本,相對(duì) SQL on HBase方案也要低很多。整個(gè)項(xiàng)目開發(fā)只用了不到700行代碼,開發(fā)維護(hù)成本還是非常低的。那如此“簡(jiǎn)單”的系統(tǒng),可用性可以保證嗎?
高可用
Spark Streaming + Kafka的組合,提供了“Exactly Once”保證:異常數(shù)據(jù)經(jīng)過(guò)流式處理后,保證結(jié)果數(shù)據(jù)中(注:并不能保證處理過(guò)程中),每條異常最多出現(xiàn)一次,且最少出現(xiàn)一次。保證Exactly Once是實(shí)現(xiàn)24/7的高可用服務(wù)最困難的地方。在實(shí)際生產(chǎn)中會(huì)出現(xiàn)很多情況,對(duì)Exactly Once的保證提出挑戰(zhàn):
異常重啟
Spark提供了Checkpoint功能,可以讓程序再次啟動(dòng)時(shí),從上一次異常退出的位置,重新開始計(jì)算。這就保證了即使發(fā)生異常情況,也可以實(shí)現(xiàn)每條數(shù)據(jù)至少寫一次HDFS。再覆寫相同的HDFS文件就保證了Exactly Once(注:并不是所有業(yè)務(wù)場(chǎng)景都允許覆寫)。寫ES的結(jié)果也一樣可以保證Exactly Once。你可以把ES的索引,就當(dāng)成HDFS文件一樣來(lái)用:新建、刪除、移動(dòng)、覆寫。
作為一個(gè)24/7運(yùn)行的程序,在實(shí)際生產(chǎn)中,異常是很常見(jiàn)的,需要有這樣的容錯(cuò)機(jī)制。但是否遇到所有異常,都要立刻掛掉再重啟呢?顯然不是,甚至在一些場(chǎng)景下,你即使重啟了,還是會(huì)繼續(xù)掛掉。我們的解決思路是:盡可能把異常包住,讓異常發(fā)生時(shí),暫時(shí)不影響服務(wù)。
圖2
如圖2所示,包住異常,并不意味可以忽略它,必須把異常收集到Spark Driver端,接入監(jiān)控(報(bào)警)系統(tǒng),人工判斷問(wèn)題的嚴(yán)重性,確定修復(fù)的優(yōu)先級(jí)。
為了更好地掌控Spark Streaming服務(wù)的狀態(tài),我們還單獨(dú)開發(fā)了一個(gè)作業(yè)調(diào)度(重啟)工具。美團(tuán)點(diǎn)評(píng)數(shù)據(jù)平臺(tái)安全認(rèn)證的有效期是7天,一般離線的批處理作業(yè)很少會(huì)運(yùn)行超過(guò)這個(gè)時(shí)間,但Spark Streaming作業(yè)就不同了,它需要一直保持運(yùn)行,所以作業(yè)只要超過(guò)7天就會(huì)出現(xiàn)異常。因?yàn)闆](méi)有找到優(yōu)雅的解決方案,只好粗暴地利用調(diào)度工具,每周重啟刷新安全認(rèn)證,來(lái)保證服務(wù)的穩(wěn)定。
升級(jí)重導(dǎo)
Spark提供了2種讀取Kafka的模式:“Receiver-based Approach”和“Direct Approach”。使用Receiver模式,在極端情況下會(huì)出現(xiàn)Receiver OOM問(wèn)題。
使用Direct模式可以避免這個(gè)問(wèn)題。我們使用的就是這種Low-level模式,但在一些情況下需要我們自己維護(hù)Kafka Offset:
升級(jí)代碼:開啟Checkpoint后,如果想改動(dòng)代碼,需要清空之前的Checkpoint目錄后再啟動(dòng),否則改動(dòng)可能不會(huì)生效。但當(dāng)這樣做了之后,就會(huì)發(fā)現(xiàn)另一個(gè)問(wèn)題——程序“忘記”上次讀到了哪個(gè)位置,因?yàn)榇鎯?chǔ)在Checkpoint中的Offset信息也一同被清空了。這種情況下,需要自己用ZooKeeper維護(hù)Kafka的Offset。
重導(dǎo)數(shù)據(jù):重導(dǎo)數(shù)據(jù)的場(chǎng)景也是,當(dāng)希望從之前的某一個(gè)時(shí)間點(diǎn)開始重新開始計(jì)算的時(shí)候,顯然也需要自己維護(hù)時(shí)間和Offset的映射關(guān)系。
自己維護(hù)Offset的成本并不高,所以看起來(lái)Checkpoint功能很雞肋。其實(shí)可以有一些特殊用法的,例如,因?yàn)镻ython不需要編譯,所以如果使用的是PySpark,可以把主要業(yè)務(wù)邏輯寫在提交腳本的外邊,再使用Import調(diào)用。這樣升級(jí)主要業(yè)務(wù)邏輯代碼時(shí),只要重啟一下程序即可。網(wǎng)上有不少團(tuán)隊(duì)分享過(guò)升級(jí)代碼的“黑科技”,這里不再展開。
實(shí)現(xiàn)24/7監(jiān)控服務(wù),我們不僅要解決純穩(wěn)定性問(wèn)題,還要解決延遲問(wèn)題。
低延遲
App異常監(jiān)控,需要保證數(shù)據(jù)延遲在分鐘級(jí)。
雖然Spark Streaming有著強(qiáng)大的分布式計(jì)算能力,但要滿足用戶角度的低延遲,可不是單純的能計(jì)算完這么簡(jiǎn)單。
輸入問(wèn)題
iOS App崩潰時(shí),會(huì)生成Crash Log,但其內(nèi)容是一堆十六進(jìn)制的內(nèi)存地址,對(duì)開發(fā)者來(lái)說(shuō)就是“天書”。只有經(jīng)過(guò)“符號(hào)化”的Crash Log,開發(fā)者才能看懂。因?yàn)榉?hào)化需要在Mac環(huán)境下進(jìn)行,而我們的Mac集群資源有限,不能符號(hào)化全部Crash Log。即使做了去重等優(yōu)化,符號(hào)化后的數(shù)據(jù)流還是有延遲。每條異常信息中,包含N維數(shù)據(jù),如果不做符號(hào)化只能拿到其中的M維。
圖3
如圖3所示,我們將數(shù)據(jù)源分為符號(hào)化數(shù)據(jù)流、未符號(hào)化數(shù)據(jù)流,可以看出兩個(gè)數(shù)據(jù)流的相對(duì)延遲時(shí)間T較穩(wěn)定。如果直接使用符號(hào)化后的數(shù)據(jù)流,那么全部N維數(shù)據(jù)都會(huì)延遲時(shí)間T。為了降低用戶角度的延遲,我們根據(jù)經(jīng)驗(yàn)加大了時(shí)間窗口:先存儲(chǔ)未符號(hào)化的M維數(shù)據(jù),等到拿到對(duì)應(yīng)的符號(hào)化數(shù)據(jù)后,再覆寫全部N維數(shù)據(jù),這樣就只有N-M維數(shù)據(jù)延遲時(shí)間T了。
輸出問(wèn)題
如果Spark Streaming計(jì)算結(jié)果只是寫入HDFS,很難遇到什么性能問(wèn)題。但你如果想寫入ES,問(wèn)題就來(lái)了。因?yàn)镋S的寫入速度大概是每秒1萬(wàn)行,只靠增加Spark Streaming的計(jì)算能力,很難突破這個(gè)瓶頸。
異常數(shù)據(jù)源的特點(diǎn)是數(shù)據(jù)量的波峰波谷相差巨大。由于我們使用了 Direct 模式,不會(huì)因?yàn)閿?shù)據(jù)量暴漲而掛掉,但這樣的“穩(wěn)定”從用戶角度看沒(méi)有任何意義:短時(shí)間內(nèi),數(shù)據(jù)延遲會(huì)越來(lái)越大,暴增后新出現(xiàn)的異常無(wú)法及時(shí)報(bào)出來(lái)。為了解決這個(gè)問(wèn)題,我們制定了一套服務(wù)降級(jí)方案。
圖4
如圖4所示,我們根據(jù)寫ES的實(shí)際瓶頸K,對(duì)每個(gè)周期處理的全部數(shù)據(jù)N使用水塘抽樣(比例K/N),保證始終不超過(guò)瓶頸。并在空閑時(shí)刻使用Spark批處理,將N-K部分從HDFS補(bǔ)寫到ES。既然寫ES這么慢,那我們?yōu)槭裁催€要用ES呢?
高性能
開發(fā)者需要在監(jiān)控平臺(tái)上分析異常。實(shí)際分析場(chǎng)景可以抽象描述為:“實(shí)時(shí) 秒級(jí) 明細(xì) 聚合” 數(shù)據(jù)查詢。
我們團(tuán)隊(duì)在使用的OLAP解決方案可以分為4種,它們各有各的優(yōu)勢(shì):
SQL on HBase方案,例如:Phoenix、Kylin。我們團(tuán)隊(duì)從2015年Q1開始,陸續(xù)在SEM、SEO生產(chǎn)環(huán)境中使用Phoenix、Kylin至今。Phoenix算是一個(gè)“全能選手”,但更適合業(yè)務(wù)模式較固定的場(chǎng)景;Kylin是一個(gè)很不錯(cuò)的OLAP產(chǎn)品,但它的問(wèn)題是不能很好支持實(shí)時(shí)查詢和明細(xì)查詢,因?yàn)樗枰x線預(yù)聚合。另外,基于其他NoSQL的方案,基本大同小異,如果選擇HBase,建議團(tuán)隊(duì)在HBase運(yùn)維方面有一定積累。
SQL on HDFS方案,例如:Presto、Spark SQL。這兩個(gè)產(chǎn)品,因?yàn)橹荒茏龅絹喢爰?jí)查詢,我們平時(shí)多用在數(shù)據(jù)挖掘的場(chǎng)景中。
時(shí)序數(shù)據(jù)庫(kù)方案,例如:Druid、OpenTSDB。OpenTSDB是我們舊版App異常監(jiān)控系統(tǒng)使用過(guò)的方案,更適合做系統(tǒng)指標(biāo)監(jiān)控。
搜索引擎方案,代表項(xiàng)目有ES。相對(duì)上面的3種方案,基于倒排索引的ES非常適合異常分析的場(chǎng)景,可以滿足:實(shí)時(shí)、秒級(jí)、明細(xì)、聚合,全部4種需求。
ES在實(shí)際使用中的表現(xiàn)如何呢?
明細(xì)查詢
支持明顯查詢,算是ES的主要特色,但因?yàn)槭腔诘古潘饕模骷?xì)查詢的結(jié)果最多只能取到10000條。在異常分析中,使用明細(xì)查詢的場(chǎng)景,其實(shí)就是追查異常Case,根據(jù)條件返回前100條就能滿足需求了。例如:已知某設(shè)備出現(xiàn)了Crash,直接搜索這個(gè)設(shè)備的DeviceId就可以看到這個(gè)設(shè)備最近的異常數(shù)據(jù)。我們?cè)谏a(chǎn)環(huán)境中做到了95%的明細(xì)查詢場(chǎng)景1秒內(nèi)返回。
聚合查詢
面對(duì)爆炸的異常信息,一味追求全是不現(xiàn)實(shí),也是沒(méi)必要的。開發(fā)者需要能快速發(fā)現(xiàn)關(guān)鍵問(wèn)題。
因此平臺(tái)需要支持多維度聚合查詢,例如按模塊版本機(jī)型城市等分類聚合,如圖5所示。
圖5
不用做優(yōu)化,ES聚合查詢的性能就已經(jīng)可以滿足需求。因此,我們只做了一些小的使用改進(jìn),例如:很多異常數(shù)據(jù)在各個(gè)維度的值都是相同的,做預(yù)聚合可以提高一些場(chǎng)景下的查詢速度。開發(fā)者更關(guān)心最近48小時(shí)發(fā)生的異常,分離冷熱數(shù)據(jù),自動(dòng)清理歷史數(shù)據(jù)也有助于提升性能。最終在生產(chǎn)環(huán)境中,做到了90%的聚合查詢場(chǎng)景1秒內(nèi)返回。
可擴(kuò)展
異常平臺(tái)不止要監(jiān)控App Crash,還要監(jiān)控服務(wù)端的異常、性能等。不同業(yè)務(wù)的數(shù)據(jù)維度是不同的,相同業(yè)務(wù)的數(shù)據(jù)維度也會(huì)不斷的變化,如果每次新增業(yè)務(wù)或維度都需要修改代碼,那整套系統(tǒng)的升級(jí)維護(hù)成本就會(huì)很高。
維度
為了增強(qiáng)平臺(tái)的可擴(kuò)展性,我們做了全平臺(tái)聯(lián)動(dòng)的動(dòng)態(tài)維度擴(kuò)展:如果App開發(fā)人員在日志中新增了一個(gè)“城市”維度,那么他不需要聯(lián)系監(jiān)控平臺(tái)做項(xiàng)目排期,立刻就可以在平臺(tái)中查詢“城市”維度的聚合數(shù)據(jù)。只需要制定好數(shù)據(jù)收集、數(shù)據(jù)處理、數(shù)據(jù)展示之間的交互協(xié)議,做到動(dòng)態(tài)維度擴(kuò)展就很輕松了。需要注意的是,ES中需要聚合的維度,Index要設(shè)置為“not_analyzed”。
想要支持動(dòng)態(tài)字段擴(kuò)展,還要使用動(dòng)態(tài)模板,樣例如下:
?
資源
美團(tuán)點(diǎn)評(píng)數(shù)據(jù)平臺(tái)提供了Kafka、Spark、ES的集群,整套技術(shù)棧在資源上也是分布式可擴(kuò)展的。
線上集群使用的版本:
- kafka-0.8.2.0
- spark-1.5.2
- elasticsearch-2.1.1
End.