Spark Streaming + Elasticsearch構建App異常監控平台

本文已發表在《程序員》雜誌2016年10月期。

如果在使用App時遇到閃退,你可能會選擇卸載App、到應用商店怒斥開發者等方式來表達不滿。但開發者也同樣感到頭疼,因為崩潰可能意味著用戶流失、營收下滑。為了降低崩潰率,進而提升App質量,App開發團隊需要實時地監控App異常。一旦發現嚴重問題,及時進行熱修復,從而把損失降到最低。App異常監控平台,就是將這個方法服務化。

低成本

小型創業團隊一般會選擇第三方平台提供的異常監控服務。但中型以上規模的團隊,往往會因為不想把核心數據共享給第三方平台,而選擇獨立開發。造輪子,首先要考慮的就是成本問題。我們選擇了站在開源巨人的肩膀上,如圖1所示。

Spark Streaming

每天來自客戶端和伺服器的大量異常信息,會源源不斷的上報到異常平台的Kafka中,因此我們面臨的是一個大規模流式數據處理問題。美團點評數據平台提供了Storm和Spark Streaming兩種流式計算解決方案。我們主要考慮到團隊之前在Spark批處理方面有較多積累,使用Spark Streaming成本較低,就選擇了後者。

Elasticsearch

Elasticsearch(後文簡稱ES),是一個開源搜索引擎。不過在監控平台中,我們是當做「資料庫」來使用的。為了降低展示層的接入成本,我們還使用了另一個開源項目ES SQL提供類SQL查詢。ES的運維成本,相對 SQL on HBase方案也要低很多。整個項目開發只用了不到700行代碼,開發維護成本還是非常低的。那如此「簡單」的系統,可用性可以保證嗎?

高可用

Spark Streaming + Kafka的組合,提供了「Exactly Once」保證:異常數據經過流式處理後,保證結果數據中(註:並不能保證處理過程中),每條異常最多出現一次,且最少出現一次。保證Exactly Once是實現24/7的高可用服務最困難的地方。在實際生產中會出現很多情況,對Exactly Once的保證提出挑戰:

異常重啟

Spark提供了Checkpoint功能,可以讓程序再次啟動時,從上一次異常退出的位置,重新開始計算。這就保證了即使發生異常情況,也可以實現每條數據至少寫一次HDFS。再覆寫相同的HDFS文件就保證了Exactly Once(註:並不是所有業務場景都允許覆寫)。寫ES的結果也一樣可以保證Exactly Once。你可以把ES的索引,就當成HDFS文件一樣來用:新建、刪除、移動、覆寫。

作為一個24/7運行的程序,在實際生產中,異常是很常見的,需要有這樣的容錯機制。但是否遇到所有異常,都要立刻掛掉再重啟呢?顯然不是,甚至在一些場景下,你即使重啟了,還是會繼續掛掉。我們的解決思路是:儘可能把異常包住,讓異常發生時,暫時不影響服務。

如圖2所示,包住異常,並不意味可以忽略它,必須把異常收集到Spark Driver端,接入監控(報警)系統,人工判斷問題的嚴重性,確定修復的優先順序。

為了更好地掌控Spark Streaming服務的狀態,我們還單獨開發了一個作業調度(重啟)工具。美團點評數據平台安全認證的有效期是7天,一般離線的批處理作業很少會運行超過這個時間,但Spark Streaming作業就不同了,它需要一直保持運行,所以作業只要超過7天就會出現異常。因為沒有找到優雅的解決方案,只好粗暴地利用調度工具,每周重啟刷新安全認證,來保證服務的穩定。

升級重導

Spark提供了2種讀取Kafka的模式:「Receiver-based Approach」和「Direct Approach」。使用Receiver模式,在極端情況下會出現Receiver OOM問題。

使用Direct模式可以避免這個問題。我們使用的就是這種Low-level模式,但在一些情況下需要我們自己維護Kafka Offset:

升級代碼:開啟Checkpoint後,如果想改動代碼,需要清空之前的Checkpoint目錄後再啟動,否則改動可能不會生效。但當這樣做了之後,就會發現另一個問題——程序「忘記」上次讀到了哪個位置,因為存儲在Checkpoint中的Offset信息也一同被清空了。這種情況下,需要自己用ZooKeeper維護Kafka的Offset。

重導數據:重導數據的場景也是,當希望從之前的某一個時間點開始重新開始計算的時候,顯然也需要自己維護時間和Offset的映射關係。

自己維護Offset的成本並不高,所以看起來Checkpoint功能很雞肋。其實可以有一些特殊用法的,例如,因為Python不需要編譯,所以如果使用的是PySpark,可以把主要業務邏輯寫在提交腳本的外邊,再使用Import調用。這樣升級主要業務邏輯代碼時,只要重啟一下程序即可。網上有不少團隊分享過升級代碼的「黑科技」,這裡不再展開。

實現24/7監控服務,我們不僅要解決純穩定性問題,還要解決延遲問題。

低延遲

App異常監控,需要保證數據延遲在分鐘級。

雖然Spark Streaming有著強大的分散式計算能力,但要滿足用戶角度的低延遲,可不是單純的能計算完這麼簡單。

輸入問題

iOS App崩潰時,會生成Crash Log,但其內容是一堆十六進位的內存地址,對開發者來說就是「天書」。只有經過「符號化」的Crash Log,開發者才能看懂。因為符號化需要在Mac環境下進行,而我們的Mac集群資源有限,不能符號化全部Crash Log。即使做了去重等優化,符號化後的數據流還是有延遲。每條異常信息中,包含N維數據,如果不做符號化只能拿到其中的M維。

如圖3所示,我們將數據源分為符號化數據流、未符號化數據流,可以看出兩個數據流的相對延遲時間T較穩定。如果直接使用符號化後的數據流,那麼全部N維數據都會延遲時間T。為了降低用戶角度的延遲,我們根據經驗加大了時間窗口:先存儲未符號化的M維數據,等到拿到對應的符號化數據後,再覆寫全部N維數據,這樣就只有N-M維數據延遲時間T了。

輸出問題

如果Spark Streaming計算結果只是寫入HDFS,很難遇到什麼性能問題。但你如果想寫入ES,問題就來了。因為ES的寫入速度大概是每秒1萬行,只靠增加Spark Streaming的計算能力,很難突破這個瓶頸。

異常數據源的特點是數據量的波峰波谷相差巨大。由於我們使用了 Direct 模式,不會因為數據量暴漲而掛掉,但這樣的「穩定」從用戶角度看沒有任何意義:短時間內,數據延遲會越來越大,暴增後新出現的異常無法及時報出來。為了解決這個問題,我們制定了一套服務降級方案。

如圖4所示,我們根據寫ES的實際瓶頸K,對每個周期處理的全部數據N使用水塘抽樣(比例K/N),保證始終不超過瓶頸。並在空閑時刻使用Spark批處理,將N-K部分從HDFS補寫到ES。既然寫ES這麼慢,那我們為什麼還要用ES呢?

高性能

開發者需要在監控平台上分析異常。實際分析場景可以抽象描述為:「實時 秒級 明細 聚合」 數據查詢。

我們團隊在使用的OLAP解決方案可以分為4種,它們各有各的優勢:

  • SQL on HBase方案,例如:Phoenix、Kylin。我們團隊從2015年Q1開始,陸續在SEM、SEO生產環境中使用Phoenix、Kylin至今。Phoenix算是一個「全能選手」,但更適合業務模式較固定的場景;Kylin是一個很不錯的OLAP產品,但它的問題是不能很好支持實時查詢和明細查詢,因為它需要離線預聚合。另外,基於其他NoSQL的方案,基本大同小異,如果選擇HBase,建議團隊在HBase運維方面有一定積累。
  • SQL on HDFS方案,例如:Presto、Spark SQL。這兩個產品,因為只能做到亞秒級查詢,我們平時多用在數據挖掘的場景中。
  • 時序資料庫方案,例如:Druid、OpenTSDB。OpenTSDB是我們舊版App異常監控系統使用過的方案,更適合做系統指標監控。
  • 搜索引擎方案,代表項目有ES。相對上面的3種方案,基於倒排索引的ES非常適合異常分析的場景,可以滿足:實時、秒級、明細、聚合,全部4種需求。

ES在實際使用中的表現如何呢?

明細查詢

支持明顯查詢,算是ES的主要特色,但因為是基於倒排索引的,明細查詢的結果最多只能取到10000條。在異常分析中,使用明細查詢的場景,其實就是追查異常Case,根據條件返回前100條就能滿足需求了。例如:已知某設備出現了Crash,直接搜索這個設備的DeviceId就可以看到這個設備最近的異常數據。我們在生產環境中做到了95%的明細查詢場景1秒內返回。

聚合查詢

面對爆炸的異常信息,一味追求全是不現實,也是沒必要的。開發者需要能快速發現關鍵問題。

因此平台需要支持多維度聚合查詢,例如按模塊版本機型城市等分類聚合,如圖5所示。

不用做優化,ES聚合查詢的性能就已經可以滿足需求。因此,我們只做了一些小的使用改進,例如:很多異常數據在各個維度的值都是相同的,做預聚合可以提高一些場景下的查詢速度。開發者更關心最近48小時發生的異常,分離冷熱數據,自動清理歷史數據也有助於提升性能。最終在生產環境中,做到了90%的聚合查詢場景1秒內返回。

可擴展

異常平台不止要監控App Crash,還要監控服務端的異常、性能等。不同業務的數據維度是不同的,相同業務的數據維度也會不斷的變化,如果每次新增業務或維度都需要修改代碼,那整套系統的升級維護成本就會很高。

維度

為了增強平台的可擴展性,我們做了全平台聯動的動態維度擴展:如果App開發人員在日誌中新增了一個「城市」維度,那麼他不需要聯繫監控平台做項目排期,立刻就可以在平台中查詢「城市」維度的聚合數據。只需要制定好數據收集、數據處理、數據展示之間的交互協議,做到動態維度擴展就很輕鬆了。需要注意的是,ES中需要聚合的維度,Index要設置為「not_analyzed」。

想要支持動態欄位擴展,還要使用動態模板,樣例如下:

{ "mappings": { "es_type_name": { "dynamic_templates": [ { "template_1": { "match": "*log*", "match_mapping_type": "string", "mapping": { "type": "string" } } }, { "template_2": { "match": "*", "match_mapping_type": "string", "mapping": { "type": "string", "index": "not_analyzed" } } } ] } }}

資源

美團點評數據平台提供了Kafka、Spark、ES的集群,整套技術棧在資源上也是分散式可擴展的。

線上集群使用的版本:

  • kafka-0.8.2.0
  • spark-1.5.2
  • elasticsearch-2.1.1

不想錯過技術博客更新?想給文章評論、和作者互動?第一時間獲取技術沙龍信息?

請關注我們的官方微信公眾號「美團點評技術團隊」。

推薦閱讀:

Elastic Stack 5.0升級踩坑記
日誌分析的模式發現功能實現(1)
多個ElasticSearch Cluster的一致性問題
從Elasticsearch來看分散式系統架構設計

TAG:Spark | Elasticsearch | 编程 |