1. 程式人生 > >001_談阿裏核心業務監控平臺SunFire的技術架構

001_談阿裏核心業務監控平臺SunFire的技術架構

resource lin nat 結束 時間段 send 歷史 有一個 路徑

<1>阿裏全球運行指揮中心(GOC)的SunFire出品

<2>在2016年雙11全球購物狂歡節中,天貓全天交易額1207億元,前30分鐘每秒交易峰值17.5萬筆,每秒支付峰值12萬筆。承載這些秒級數據背後的監控產品是如何實現的呢?接下來本文將從阿裏監控體系、監控產品、監控技術架構及實現分別進行詳細講述。

阿裏有眾多監控產品,且各產品分工明確,百花齊放。整個阿裏監控體系如下圖:

技術分享圖片

集團層面的監控,以平臺為主,全部為阿裏自主研發(除引入了第三方基調、博睿等外部檢測系統,用於各地CDN用戶體驗監控),這些監控平臺覆蓋了阿裏集團80%的監控需求。

此外,每個事業群均根據自身特性自主研發了多套監控系統,以滿足自身特定業務場景的監控需求,如廣告的GoldenEye、菜鳥的棱鏡、阿裏雲的天基、螞蟻的金融雲(基於XFlush)、中間件的EagleEye等,這些監控系統均有各自的使用場景。

阿裏的監控規模早已達到了千萬量級的監控項,PB級的監控數據,億級的報警通知,基於數據挖掘、機器學習等技術的智能化監控將會越來越重要。

阿裏全球運行指揮中心(GOC)基於歷史監控數據,通過異常檢測、異常標註、挖掘訓練、機器學習、故障模擬等方式,進行業務故障的自動化定位,並賦能監控中心7*24小時專業監控值班人員,使阿裏集團具備第一時間發現業務指標異常,並快速進行應急響應、故障恢復的能力,將故障對線上業務的影響降到最低。

接下來將詳細講述本文的主角,承載阿裏核心業務監控的SunFire監控平臺。

SunFire簡介

SunFire是一整套海量日誌實時分析解決方案,以日誌、REST 接口、Shell 腳本等作為數據采集來源,提供設備、應用、業務等各種視角的監控能力,從而幫您快速發現問題、定位問題、分析問題、解決問題,為線上系統可用率提供有效保障。

SunFire利用文件傳輸、流式計算、分布式文件存儲、數據可視化、數據建模等技術,提供實時、智能、可定制、多視角、全方位的監控體系。其主要優勢有:

  • 全方位實時監控:提供設備、應用、業務等各種視角的監控能力,關鍵指標秒級、普通指標分鐘級,高可靠、高時效、低延遲。
  • 靈活的報警規則:可根據業務特征、時間段、重要程度等維度設置報警規則,實現不誤報、不漏報。
  • 管理簡單:分鐘級萬臺設備的監控部署能力,故障自動恢復,集群可伸縮
  • 自定義便捷配置:豐富的自定義產品配置功能,便捷、高效的完成產品配置、報警配置。
  • 可視化:豐富的可視化 Dashboard,幫助您定制個性化的監控大盤。
  • 低資源占用:在完成大量監控數據可靠傳輸的同時,保證對宿主機的 CPU、內存等資源極低占用率。

Sunfire技術架構如下:

(點擊放大圖像)

技術分享圖片

主要模塊實現及功能

針對架構圖中的各個組件,其中最關鍵的為采集(Agent)、計算(Map、Reduce)組件,接下來將對這兩個組件進行詳細介紹。

1. 采集

Agent負責所有監控數據的原始采集,它以 Agent 形式部署在應用系統上,負責原始日誌的采集、系統命令的執行。日誌原始數據的采集,按周期查詢日誌的服務,且日誌查詢要低耗、智能。Agent上不執行計算邏輯。主要具有以下特點:

低耗

采集日誌,不可避免要考慮日誌壓縮的問題,通常做日誌壓縮則意味著它必須做兩件事情:一是磁盤日誌文件的內容要讀到應用程序態;二是要執行壓縮算法。

這兩個過程就是CPU消耗的來源。但是它必須做壓縮,因為日誌需要從多個機房傳輸到集中的機房。跨機房傳輸占用的帶寬不容小覷,必須壓縮才能維持運轉。所以低耗的第一個要素,就是避免跨機房傳輸。SunFire達到此目標的方式是運行時組件自包含在機房內部,需要全量數據時才從各機房查詢合並。

網上搜索zero-copy,會知道文件傳輸其實是可以不經過用戶態的,可以在Linux的核心態用類似DMA的思想,實現極低CPU占用的文件傳輸。SunFire的Agent當然不能放過這個利好,對它的充分利用是Agent低耗的根本原因。以前這部分傳輸代碼是用c語言編寫的sendfile邏輯,集成到Java工程裏,後來被直接改造為了Java實現。

最後,在下方的計算平臺中會提到,要求Agent的日誌查詢服務具備“按周期查詢日誌”的能力。這是目前Agent工程裏最大的難題,我們都用過RAF(RandomAccessFile),你給它一個遊標,指定offset,再給它一個長度,指定讀取的文件size,它可以很低耗的扒出文件裏的這部分內容。然而問題在於:周期≠offset。從周期轉換為offset是一個痛苦的過程。

在流式計算裏一般不會遇到這個問題,因為在流式架構裏,Agent是水龍頭,主動權掌握在Agent手裏,它可以從0開始push文件內容,push到哪裏就做一個標記,下次從標記繼續往後push,不斷重復。這個標記就是offset,所以流式不會有任何問題。

而計算平臺周期任務驅動架構裏,pull的方式就無法提供offset,只能提供Term(周期,比如2015-11-11 00:00分)。Agent解決此問題的方式算是簡單粗暴,那就是二分查找法。而且日誌還有一個天然優勢,它是連續性的。所以按照對二分查找法稍加優化,就能達到“越猜越準”的效果(因為區間在縮小,區間越小,它裏面的日誌分布就越平均)。

於是,Agent代碼裏的LogFinder組件撐起了這個職責,利用上述兩個利好,實現了一個把CPU控制在5%以下的算法,目前能夠維持運轉。其中CPU的消耗不用多說,肯定是來自於猜的過程,因為每一次猜測,都意味著要從日誌的某個offset拉出一小段內容來核實,會導致文件內容進入用戶態並解析。這部分算法依然有很大的提升空間。

日誌滾動

做過Agent的同學肯定都被日誌滾動困擾過,各種各樣的滾動姿勢都需要支持。SunFire的pull方式當然也會遇到這個問題,於是我們簡單粗暴的窮舉出了某次pull可能會遇到的所有場景,比如:

  • 正常猜到了offset

  • 整個日誌都猜不到offset

  • 上次猜到了offset,但是下次再來的時候發現不對勁(比如滾動了)

這段邏輯代碼窮舉的分支之多,在一開始誰都沒有想到。不過仔細分析了很多次,發現每個分支都必不可少。

查詢接口

Agent提供的查詢服務分為first query和ordinary query兩種。做這個區分的原因是:一個周期的查詢請求只有第一次需要猜offset,之後只需要順序下移即可。而且計算組件裏有大量的和Agent查詢接口互相配合的邏輯,比如一個周期拉到什麽位置上算是確定結束?一次ordinary query得到的日誌裏如果末尾是截斷的(只有一半)該如何處理…… 這些邏輯雖然縝密,但十分繁瑣,甚至讓人望而卻步。但現狀如此,這些實現邏輯保障了SunFire的高一致性,不用擔心數據不全、報警不準,隨便怎麽重啟計算組件,隨便怎麽重啟Agent。但這些優勢的背後,是值得深思的代碼復雜度。

路徑掃描

為了讓用戶配置簡單便捷,SunFire提供給用戶選擇日誌的方式不是手寫,而是像windows的文件夾一樣可以瀏覽線上系統的日誌目錄和文件,讓他雙擊一個心儀的文件來完成配置。但這種便捷帶來的問題就是路徑裏若有變量就會出問題。所以Agent做了一個簡單的dir掃描功能。Agent能從應用目錄往下掃描,找到同深度文件夾下“合適”的目標日誌。

2. 計算

由Map、Reduce組成計算平臺,負責所有采集內容的加工計算,具備故障自動恢復能力及彈性伸縮能力。計算平臺一直以來都是發展最快、改造最多的領域,因為它是很多需求的直接生產者,也是性能壓力的直接承擔者。因此,在經過多年的反思後,最終走向了一條插件化、周期驅動、自協調、異步化的道路。主要具有以下幾個特點:

純異步

原來的SunFire計算系統裏,線程池繁復,從一個線程池處理完還會丟到下一個線程池裏;為了避免並發bug,加鎖也很多。這其中最大的問題有兩個:CPU密集型的邏輯和I/O密集型混合。

對於第一點,只要發生混合,無論你怎麽調整線程池參數,都會導致各式各樣的問題。線程調的多,會導致某些時刻多線程搶占CPU,load飆高;線程調的少,會導致某些時刻所有線程都進入阻塞等待,堆積如山的活兒沒人幹。

對於第二點,最典型的例子就是日誌包合並。比如一臺Map上的一個日誌計算任務,它要收集10個Agent的日誌,那肯定是並發去收集的,10個日誌包陸續(同時)到達,到達之後各自解析,解析完了data要進行merge。這個merge過程如果涉及到互斥區(比如嵌套Map的填充),就必須加鎖,否則bug滿天飛。

但其實我們重新編排一下任務就能杜絕所有的鎖。比如上面的例子,我們能否讓這個日誌計算任務的10個Agent的子任務,全部在同一個線程裏做?這當然是可行的,只要回答兩個問題就行:

  • 如果串行,那10個I/O動作(拉日誌包)怎麽辦?串行不就浪費cpu浪費時間嗎?
  • 把它們都放到一個線程裏,那我怎麽發揮多核機器的性能?

第一個問題,答案就是異步I/O。只要肯花時間,所有的I/O都可以用NIO來實現,無鎖,事件監聽,不會涉及阻塞等待。即使串行也不會浪費CPU。第二個問題,就是一個大局觀問題了。現實中我們面臨的場景往往是用戶配置了100個產品,每個產品都會拆解為子任務發送到每臺Map,而一臺Map只有4個核。所以,你讓一個核負責25個任務已經足夠榨幹機器性能了,沒必要追求更細粒度子任務並發。因此,計算平臺付出了很大的精力,做了協程框架。

我們用Akka作為協程框架,有了協程框架後再也不用關註線程池調度等問題了,於是我們可以輕松的設計協程角色,實現CPU密集型和I/O密集型的分離、或者為了無鎖而做任務編排。接下來,盡量用NIO覆蓋所有的I/O場景,杜絕I/O密集型邏輯,讓所有的協程都是純跑CPU。按照這種方式,計算平臺已經基本能夠榨幹機器的性能。

周期驅動

所謂周期驅動型任務調度,說白了就是Map/Reduce。Brain被選舉出來之後,定時撈出用戶的配置,轉換為計算作業模型,生成一個周期(比如某分鐘的)的任務, 我們稱之為拓撲(Topology), 拓撲也很形象的表現出Map/Reduce多層計算結構的特征。所有任務所需的信息,都保存在topology對象中,包括計算插件、輸入輸出插件邏輯、Map有幾個、每個Map負責哪些輸入等等。這些信息雖然很多,但其實來源可以簡單理解為兩個:一是用戶的配置;二是運維元數據。拓撲被安裝到一臺Reduce機器(A)上。

A上的Reduce任務判斷當前集群裏有多少臺Map機器,就產生多少個任務(每個任務被平均分配一批Agent),這些任務被安裝到每臺機器上Map。被安裝的Map任務其實就是一個協程,它負責了一批Agent,於是它就將每個Agent的拉取任務再安裝為一個協程。至此,安裝過程結束。Agent拉取任務協程(也稱之為input輸入協程,因為它是數據輸入源)在周期到點後,開始執行,拉取日誌,解析日誌,將結果交予Map協程;Map協程在得到了所有Agent的輸入結果並merge完成後,將merge結果回報到Reduce協程(這是一個遠程協程消息,跨機器);Reduce協程得到了所有Map協程的匯報結果後,數據到齊,寫入到Hbase存儲,結束。

上述過程非常簡單,不高不大也不上,但經過多年大促的考驗,其實非常的務實。能解決問題的架構,就是好的架構,能簡單,為何要把事情做得復雜呢?

這種架構裏,有一個非常重要的特性:任務是按周期隔離的。也就是說,同一個配置,它的2015-11-11 00:00分的任務和2015-11-11 00:01分的任務,是兩個任務,沒有任何關系,各自驅動,各自執行。理想情況下,我們可以做出結論:一旦某個周期的任務結束了,它得到的數據必然是準確的(只要每個Agent都正常響應了)。所以采用了這種架構之後,SunFire很少再遇到數據不準的問題,當出現業務故障的時候我們都可以斷定監控數據是準確的,甚至秒級都可以斷定是準確的,因為秒級也就是5秒為周期的任務,和分鐘級沒有本質區別,只是周期範圍不同而已。能獲得這個能力當然也要歸功於Agent的“按周期查詢日誌”的能力。

任務重試

在上節描述的Brain-&gt;Reduce-&gt;Map的任務安裝流程裏,我們對每一個上遊賦予一個職責:監督下遊。當機器發生重啟或宕機,會丟失一大批協程角色。每一種角色的丟失,都需要重試恢復。監督主要通過監聽Terminated事件實現,Terminated事件會在下遊掛掉(不論是該協程掛掉還是所在的機器掛掉或是斷網等)的時候發送給上遊。由於拓撲是提前生成好且具備完備的描述信息,因此每個角色都可以根據拓撲的信息來重新生成下遊任務完成重試。

  • 若Brain丟失,則Brain會再次選主, Brain讀取最後生成到的任務周期, 再繼續生成任務。

  • 若Reduce丟失,每個任務在Brain上都有一個TopologySupervisor角色, 來監聽Reduce協程的Terminated事件來執行重試動作。

  • 若Map丟失,Reduce本身也監聽了所有Map的Terminated事件來執行重試動作。

  • 為確保萬無一失,若Reduce沒有在規定時間內返回完成事件給Brain,Brain同樣會根據一定規則重試這個任務。

過程依然非常簡單,而且從理論上是可證的,無論怎麽重啟宕機,都可以確保數據不丟,只不過可能會稍有延遲(因為部分任務要重新做)。

輸入共享:在用戶實際使用SunFire的過程中,常常會有這樣的情況:用戶配了幾個不同的配置,其計算邏輯可能是不同的,比如有的是單純計算行數,有的計算平均值,有的需要正則匹配出日誌中的內容,但這幾個配置可能都用的同一份日誌,那麽一定希望幾個配置共享同一份拉取的日誌內容。否則重復拉取日誌會造成極大的資源消耗。那麽我們就必須實現輸入共享,輸入共享的實現比較復雜,主要依賴兩點:

  • 其一是依賴安裝流,因為拓撲是提前安裝的,因此在安裝到真正開始拉取日誌的這段時間內,我們希望能夠通過拓撲信息判斷出需要共享的輸入源,構建出輸入源和對應Map的映射關系。

  • 其二是依賴Map節點和Agent之間的一致性哈希,保證Brain在生成任務時,同一個機器上的日誌,永遠是分配相同的一個Map節點去拉取的(除非它對應的Map掛了)。

站在Map節點的視角來看:在各個任務的Reduce拿著拓撲來註冊的時候,我拿出輸入源(對日誌監控來說通常可以用一個IP地址和一個日誌路徑來描述)和Map之間的關系,暫存下來,每當一個新的Reduce來註冊Map,我都判斷這個Map所需的輸入源是否存在了,如果有,那就給這個輸入源增加一個上遊,等到這個輸入源的周期到了,那就觸發拉取,不再共享了。

其他組件

存儲:負責所有計算結果的持久化存儲,可以無限伸縮,且查詢歷史數據保持和查詢實時數據相同的低延遲。Sunfire原始數據存儲使用的是阿裏集團的Hbase產品(HBase :Hadoop Database,是一個高可靠性、高性能、面向列、可伸縮的分布式存儲系統),用戶配置存儲使用的是MongoDb。

展示:負責提供用戶交互,讓用戶通過簡潔的建模過程來打造個性化的監控產品。基於插件化、組件化的構建方式,用戶可以快速增加新類型的監控產品。

自我管控:即OPS-Agent、Ops-web組件,負責海量Agent的自動化安裝監測,並且承擔了整個監控平臺各個角色的狀態檢測、一鍵安裝、故障恢復、容量監測等職責。

001_談阿裏核心業務監控平臺SunFire的技術架構