Airbnb 的核心日誌系統架構及主要系統模組的設計之道

天府雲創發表於2017-07-03

隨著業務的快速增長,傳統的基於批處理模式和無格式的日誌處理已經逐漸不能滿足應用需求。因此,Airbnb 資料基礎構架組開發了新一代的日誌資料儲存和查詢平臺,著重於保證日誌資料的質量,解決資料的實時性,提高查詢的靈活性,方便多維度統計分析,和提供異常檢測。演講主要分享 Airbnb 核心日誌平臺的系統構架,以及主要系統模組的設計和實現。

背景介紹

什麼是日誌平臺系統?首先我們知道大資料對於網際網路公司有非常重要的價值,對 Airbnb 也不例外。我們通過資料可以為客戶提供最佳的旅行體驗,同時我們也可以通過資料發掘使用者的需求、市場需求,對我們的產品做出正確的決策。

這裡是一些很簡單、很具體的應用。首先通過資料可以進行反欺詐,讓我們的平臺變得更加可靠、值得信賴,同時通過資料可以找到我們所需要的房源,通過分析使用者的相關行為,我們可以做到使用者的拉新、促或、匹配,我們也可以做 AB 測試,可以通過資料決定新的產品或者功能是不是對業務起到一定的幫助,同時資料可以用來做監測。這是產品和資料倉儲的關係,這麼一個簡單的從產品到資料倉儲的紐帶,就是日誌的收集系統。

說到 Airbnb 的平臺系統,在開發和完善過程中也踩到了很多坑,取得了很多進步。這是大概兩年前的狀態,使用的是無格式的 JSON 日誌,我們有超過 800 種不同的日誌記錄型別,由於產品迭代和程式碼改動,沒有約束所以非常容易出錯,缺乏監控手段,保證整個日誌平臺的可靠性。

這是一些例子,在左邊我們通過資料產生一些圖表,這些圖表由於整個資料平臺系統的不可靠,經常會不顯示或者資料比較陳舊。另外,右邊有一個白板,我們專案經理會負責一個白板,寫著有多少天沒有產生資料,資料平臺執行了,自從建立這個白板之後數字沒有超過 10。

這也是真實的郵件,是某個組的專案經理髮給他們全組的員工,說 dashboard 圖表大家不要去相信,因為資料更新地不夠快,或者資料是不準確的。

我們可以看到,大資料領域,資料的收集處理的平臺對大資料和整個公司的資料驅動是非常重要的。

日誌平臺的需求

對於這麼一個日誌系統的平臺有哪些需求呢?我們發現,第一個是資料的時效性,資料必須按照一定的時間落盤,有一定的可預測性。第二個,資料的完整性,就是說我們資料不能丟失,不能損壞,也不能夠重複。第三個,保證資料的質量,就是說資料不能是無效的資料,如果資料沒有辦法反序列化,或者資料丟失了必須的欄位,我們後續的處理就沒有辦法進行。

Airbnb 日誌平臺構架

我們看一下整個 Airbnb 日誌平臺大體構架。首先我們有一些前端的應用,比如網頁和手機的 APP,這是我們的客戶端。我們整個伺服器是構建在 Ruby 上的,所以有 Ruby 的服務叢集,同時還有很多內部產品的叢集。所有的叢集能夠產生資料,資料或者直接傳送到 Kafka,或者通過一些代理閘道器發生了 Kafka,Kafka 是整個資料訊息的中介軟體。會有一些作業定期地去消費這些訊息、這些日誌,我們使用 Camus,是以離線或者批處理的方式把 Kafka 中間的資料放在我們的資料倉儲,上面我們用 Hive 或 Presto 進行查詢。更重要的一環是,收集完這些資料之後,把資料通過一個派生資料庫,得到相關的資料產品,用於提高和改善我們前端的服務。這就是資料收集並回饋到產品的過程。

在這麼一個比較複雜的框架下有很多元件會發生錯誤。首先,程式死鎖,會丟失資料,或者 Kafka 代理會產生故障造成資料丟失。因為網頁和手機都是執行在客戶端的,會產生一些無效的資料,或者惡意的不能夠反序列化的資料。我們發也現很多軟體 Bug,這些軟體 Bug 也會造成資料的丟失。同時伺服器節點故障,比如硬碟、記憶體和網路的故障會導致資料不能夠按時的或者不能進入資料系統。還有做一些代理會快取配置,快取配置如果沒有正常的數值,也會造成快取溢位,資料丟失。

考慮到整個系統之後,我們希望去改善或者去提高日誌收集的平臺,我們發現有五個方面可以改進:

  • 首先,我們需要模組監測,作為整個資料平臺中間,每個模組需要保證它的正確性和可靠性;

  • 第二點,儘管系統中間的每個模組都正確執行了,但是也不能保證系統端到端的可靠性,所以還需要一個端到端的稽核機制;

  • 第三點,發現如果日誌的格式沒有一定約束的話,會很容易產生非常多的無效日誌,不能保證資料質量,因此我們對日誌的格式也應該有一定的約束性和一定的強制性;

  • 第四點,整個系統出現異常的情況下,怎麼樣能夠快速地或者自動地去檢測到這個異常,所以需要一個異常檢測的模組;

  • 最後一點,現在越來越多的系統已經不能滿足批處理和離線的方式,在我們的日誌平臺中間也需要引入實時流處理的模組,使得日誌能夠更快地讓使用者查詢或者進行一些資料聚合操作。

下面分別介紹一下每個方向的工作。

模組檢測

首先,模組故障的監控,這個是比較直接的,對於日誌系統中間的每個模組需要保證正確性和可靠性。比如程式的監測,程式是否活著,對於 CPU 和記憶體的使用量。因為日誌系統有資料的輸出和輸入,對比資料的輸出和輸入幫助我們發現系統的故障。很多時候,比如對 Airbnb 網站來說,資料是有季節性的,比如說每週一、每週二我們平臺的資料量會上升,到了週末資料量會下降,因此對資料會有一個季節性的對比,把這週一的資料和上週一的資料對比看有沒有差別,如果差別很大看系統是不是有一定的錯誤。有了這些基本的模組級別指標之後就可以建立預警機制,發生錯誤的時候可以告知,檢查系統錯誤。

端到端稽核

除了模組級別的監測之外我們還要考慮端到端的稽核制度。首先,我們監測每個模組不足以保證整個系統的可靠性,比如有些錯誤是未知的,沒有看到過所以目前的監測手段會失效,因為我們不知道監測怎麼樣的指標去發現錯誤。第二個,需要量化整個系統的可靠性。第三個,在發生錯誤的時候要儘快定位到錯誤發生的模組。第四個,需要除了應用系統本身的指標去監測系統之外,還希望有一些第三方的資料或者帶外的形式,和日誌系統的指標進行對比,發現系統的錯誤。

基於這幾點我們主要提供了心跳日誌和資料庫更新日誌。心跳日誌就是一種虛假的或者人工的日誌,能夠定期向不同的服務傳送,按照一定的速率,通過在後端資料庫裡面聚合這些心跳日誌,就知道整個平臺是不是產生了資料丟失,這個方法非常簡單、有效、可靠。另外,我們可以通過一些資料庫的更新日誌,比如訂單日誌,每次訂單產生都會對資料庫產生一次更新,更新的次數會被統計出來,根據更新的次數對比在後臺資料操作中訂單日誌的差別,我們可以發現整個系統日誌平臺是不是可靠,資料庫的更新日誌提供一種帶外的方式檢查整個平臺的可靠性。

對於整個平臺端到端的日誌稽核,主要是在日誌中新增一些輔助的資訊,比如日誌 ID(唯一識別的 ID)、主機名稱、程式號、序列號、時間戳等等,有了這些輔助資訊之後就可以量化資料平臺的可靠性。同時可以計算,日誌在整個系統平臺中間每一跳的可靠性。

這是一個簡單的例子,在每個日誌中間需要新增這些資訊,比如資料的型別、資料唯一的標識,資料在整個流程中會經過節點,每個節點會有一些型別、主機號、IP 地址、時間戳、序列號等等。

日誌格式

有了端到端的保證和模組級別監控之後,依然不能保證整個系統的可靠性,是因為日誌的格式還沒有得到保證,會產生一些錯誤的日誌格式。

回顧一下兩年前使用的無效日誌,我們有超過 800 種的日誌型別,容易出錯,缺乏很多監測,導致資料事故和資料丟失,也對整個公司和資料平臺造成不信任。

這是真實的郵件,每次產生資料事故之後都會發報告,告訴大家我們在程式碼重構中不小心丟失了日誌中間的某個域,導致資料錯誤或不能被使用,甚至完全把某一種日誌丟掉了。

針對這種情況我們就想到了需要對日誌的格式進行約束,我們選擇了 Apache Thrift,是與語言無關的格式定義語言,有豐富的客戶端,支援 Java、Ruby、JS、Mobile 的開發端。這個語言本身比較簡單、容易理解,有助於讓資料科學家和產品工程師共同去定義這種日誌格式,因為日誌不光是要傳送,還要被資料科學家所理解去處理,所以我們覺得 Apache Thrift 能夠滿足這樣的協議。同時我們定義了標準的開發流程,讓大家遵照這個開發流程。

為什麼選擇 Thrift 呢?第一是語法比較直觀,非工程師也容易理解。第二點,它在 Ruby 環境中有良好的效能。同時日誌格式資訊和開發文件統一存取。第四,我們也有嚴格的版本管理資訊,比如日誌是前項還是後項的相容性,在日誌的進化當中,可以保證日誌不會破壞後臺的處理。同時我們也給出了相應的統一發布流程,可以釋出 Jar、Gem 檔案,使得後端服務和客戶端可以使用這樣的日誌模式。

這是簡單的例子,我們定義了這麼一個日誌格式,在 Java 和 Ruby 語言當中,有一些 field 必須在日誌中間,還有一些 field 是可選的。

這是在 JS 當中有這樣的格式之後,開發過程中如果忽略了一些必須的 field,開發時候發現錯誤及時更正,避免錯誤。

這是有了日誌格式之後的開發流程。首先要定義格式,資料科學家和工程師共同定義這個格式,然後通過統一的釋出流程,把資料日誌傳送到伺服器或者客戶端,客戶端或伺服器就會用這個日誌格式產生日誌,通過相應的流處理應用,將日誌傳送到資料倉儲,最後在資料倉儲中間對日誌進行丟失檢測、異常查詢。

我們也開發一些前端的應用,使得使用者能夠非常方便地通過關鍵字查詢日誌,並且日誌格式提供了一些後設資料,如日誌的作者、版本等等。同時我們也做一些簡單程式碼的模板生成或者查詢語言生成,比如給定日誌之後可以對不同語言型別產生程式碼樣板,在不同語言環境下簡單地使用日誌。

實時流處理

上面提到的三點就是模組級別的監測、端到端的稽核和日誌的格式。現在越來越多的應用需要實時的處理,因此在日誌系統中間加入了實時流處理。

回顧一下之前整個日誌的框架。有前端的移動端和服務,日誌通過代理伺服器或者直接進入 Kafka 匯流排。引入了流處理之後發生了大變化,通過 AirStream 這個流處理平臺,資料實時地被 AirStream 處理,放入 Hbase,Hbase 是現在使用的儲存方案,Druid 是多維度統計的儲存時間序列的平臺。我們有定時的作業可以把 Hbase 或實時的作業批量放到 Hbase 資料倉儲中間,同時我們開發了 Presto 在 Hbase 上面的介面,使得使用者可以用 Presto 對這些實時的資料進行查詢。在反饋流程上面,除了原來的批處理反饋同時也引入了實時的反饋,可以將實時的訊號反饋給產品伺服器,使它能夠做出更好的改進。

這是實時處理的一些重要的部件。第一基於 Spark Streaming 對實時資料進行了實時注入。第二我們使用了 HBase 對資料進行去重,並且按照資料型別進行分片,同時開發了 Hive/Presto 介面訪問 Hbase,提供日誌的實時查詢,同時我們對資料進行實時多維度聚合的操作。

這是我們的一個介面,當資料注入之後,可以對資料進行一些維度上的分析。這是簡單查詢的例子,圖的右邊是一些維度的定義,比如查詢來自於哪個平臺,它的查詢語言是什麼,它來自於哪個頁面,在這個例子當中看到對於這個查詢日誌資料來說來自於 iOS 平臺、英語國家數量是最多的。

整個介面也是我們自主開發的資料視覺化的模組,叫做 Superset,在 GitHub 上已經開源了,可以支援很多資料來源,比如我們這裡用的資料來源,可以把實際資料發掘出來顯示在圖形上,同時有許多其他的資料來源支援,還支援各種不同的視覺化模組,比如說支援這樣持續的線狀圖,基於地圖的視覺化,或者一些熱點圖、餅狀圖等等,這是一個非常好用的圖形視覺化服務。

異常檢測

最後一點,我們還需要對資料進行一些異常的檢測。

這是一個非常實際的例子,比如在某年 9 月 22 日上線了日期的選擇元件,右邊是我們 Airbnb 的主頁,主頁的第一行給大家顯示了需要搜尋哪裡的房源,入住日期和退房日期是怎樣的,讓使用者快捷地定義這個元件。我們開啟了 AB 測試,有 50% 的使用者可以看到這個新的元件,50% 的使用者還是使用舊的日期元件。一個星期之後,9 月 29 日實驗結果表明新元件導致搜尋下降 14%,這是個糟糕的結果,我們就下線了新元件,搜尋指標恢復。

這個例子的目的在於,一旦發生了錯誤,如何分析原因?這是一個繁瑣的過程,基於很多因素,依賴於經驗、試錯和運氣,經常開發中遇到上線新的產品、新的功能之後,發現指標比如訂單量、搜尋量下降了,需要找到根源所在。對於具體的例子來說原因是,如果把搜尋的下降量按照國家、地區分類、搜尋來自的平臺,比如是網頁版或者手機版,並且把搜尋的下降按頁面來分類發現,原來在手機平臺上、義大利語頁面搜尋下降是最多的,而其他平臺或其他維度沒有受到影響。我們最後發現原來是新的 JS 元件,在特定的環境下、特定的語言平臺下有 Bug,導致整個資料搜尋量的下降。

我們開發環境中經常碰到這樣的問題,怎樣不這麼繁瑣、費人工的查詢問題?開發了一個 Curiosity 系統對於頂層聚合指標進行自動異常檢測。如何判定異常檢測的組合,我們有策略地儘可能去嘗試各種不同的組合,比如對於任何一個頂層聚合指標來說,有很多維度,如搜尋的國家、作業系統、渠道,每一個日誌資料會有不同的維度,這些維度會產生成百上千的維度組合,如何在不同的維度組合下發現聚合指標產生異常呢?這就是 Curiosity 要解決的問題。分幾個步驟:

  • 第一個,獲取實際的資料來源;

  • 第二個,可以想象成一個廣度搜尋的過程,對不同的維度組合進行廣度搜尋,每次給定一個維度,對這個維度進行兩個方向的異常檢測,一個是水平方向,一個是垂直方向。所謂水平方向,時間序列方向,比如搜尋來自的語言,在這個時間軸方向是不是有大的波峰或者波谷,我們就認為產生了異常。垂直方向就是給定一個時間段,對維度不同的取值是否異常,比如在這個時間段內來自於一種語言的搜尋比其他語言的搜尋行為更加可疑,就是個異常;

  • 第三個,把水平異常檢測結果和垂直異常檢測結果結合起來進行裁剪,比如發現在水平維度上,只有英語國家產生了異常,就可以裁剪這個維度其他值,使得廣度搜尋不會指數級的增長。當我們對一個維度分析完畢之後,會把這個維度異常的值和下一個維度合併,進行下一個廣度的搜尋。

具體到實現,我們抽象了具體的設計介面,是因為雖然對於異常的檢測,不同的系統需要不同的演算法、不同的資料來源,比如資料具有季節性,因此一個簡單的檢測波峰和波谷按照閾值的檢測不一定能夠有效,我們就會用其他的一些演算法。所以在實現 Curiosity 的時候,每個模組都進行了介面化的設計,資料來源的獲取,水平異常檢測,垂直異常檢測,然後維度裁剪,廣度搜尋。對於每個介面我們有基本的實現,比如對於資料來源實現了 Druid 資料來源,對於檢測我們實現了基本的演算法,比如基於閾值或者 Kalman Filter,演算法都是可定製化的。最後把廣度搜尋分片,基於 Spark 支援分散式執行,加快異常檢測的過程。

異常檢測的結果我們會把它以一個圖形化的方式展示出來,展示的過程基於一篇論文。最上面是日誌資料的維度資訊,這是一個搜尋的日誌,它的維度有搜尋來自的國家、搜尋目標的國家、搜尋來自的渠道和搜尋使用的作業系統或者平臺的資訊。對於這些不同的維度來說,顏色的深淺表示這個維度可能發生異常的概率高低。

上面這個表格,垂直方向是搜尋來自國家的維度,橫向是一個時間軸,每一個單元格代表這個時間段內國家異常的狀況,顏色越深代表異常越嚴重,比如法國人的搜尋在 5 月 15 日產生了異常,所以網格是紅色的。如果網格的邊緣很寬,表示如果把這個維度定義為法國人搜尋的維度之後,那麼法國人在搜尋的目標國家也會產生很大的異常的可能性,所以可以點選法國人的維度繼續檢測下一個異常發生的狀況。

寫在最後

最後總結一下,我們這個日誌系統有幾個特點:第一個,提供了系統級的檢測和報警。第二個,可以量化整個平臺的可靠性。第三個,對日誌的格式進行規範,能夠儘量減少無效性的資料。第四個,引入實時流處理日誌能夠實時查詢。第五個,開發了日誌異常檢測服務,可以很快地檢測出異常發生的狀況。

推薦一個活動

【百度 AI 開發者大會】7 月 5 日,Baidu Create 2017 百度 AI 開發者大會將在北京國家會議中心舉辦。百度創始人、董事長兼執行長李彥巨集,百度集團總裁兼營運長陸奇,將釋出面向開發者和生態合作伙伴的重要計劃。DuerOS 開放平臺、Apollo 開放平臺等百度 AI 生態重要戰略、技術、業務進展、解決方案,也將首次面向開發者及各行業合作伙伴集中展現,釋放生態勢能。詳情請戳 「 閱讀原文 」

相關文章