去年我們釋出了Answers,至今移動社群產生了驚人的使用量,讓我們感到興奮不已。現在Answers每天處理50億次會話,並且這個數量在持續增加。上億裝置每秒向Answers端點傳送數以百萬計的請求。在你已經閱讀到此處的這段時間裡,Answers後臺收到並處理了一千萬次分析事件。
其中的挑戰是如何利用這些資訊向移動開發者提供可靠的、實時的、有實際價值的洞見(視角)去了解他們的移動應用。
在高層,我們依靠 元件解耦、非同步通訊、在應對災難性故障時優雅地服務降級等原則來幫助架構決策。我們使用Lambda架構將資料完整性和實時資料更新結合起來。
在實踐過程中,我們需要設計一個能夠接收並儲存事件、執行離線和實時計算且能將上述兩種計算結果整合成相關資訊的系統。這些行為全部都要以百萬次每秒的規模執行。
讓我們從第一個挑戰開始:接受並處理這些事件。
事件接收
在設計裝置-伺服器通訊的時候,我們的目標是:減少對電池和網路使用的影響;確保資料的可靠性;接近實時地獲取資料。為了減少對裝置的影響,我們批量地傳送分析資料並且在傳送前對資料進行壓縮。為了保證這些寶貴的資料始終能夠到達我們的伺服器,在傳輸失敗隨機退避後以及達到裝置儲存達到上限時,裝置會進行重傳。為了確保資料能夠儘快到達伺服器,我們設定來多個觸發器來使裝置嘗試傳送:當程式執行於前臺的時候,事件觸發器每分鐘觸發一次;一個訊息數量觸發器和程式轉入後臺觸發器。
這樣的通訊協議導致裝置每秒傳送來數以萬計壓縮過的有效載荷。每一個載荷都包含數十條事件。為了能夠可靠的、易於線性伸縮的方式去處理載荷,接收事件的服務必須極度簡單。
這個服務使用GO語言編寫,這個服務使用了亞馬遜彈性負載均衡器(ELB),並將每一個訊息負荷放入一個持久化的Kafka佇列。
儲存
Kafka是一個持久儲存器,因為它把收到的訊息寫入磁碟並且每個訊息都有多份冗餘。因此一旦我們知道資訊到了Kafka佇列,我們就可以通過延遲處理、再處理來容忍下游延遲和下游失敗。然而,Kafka不是我們歷史資料的永久真理之源——按照上文提到的速度,僅僅是幾天的資料,我們也需要數以百計的box來儲存。因此我們把Kafka叢集配置為將訊息只保留幾個小時(這些時間足夠我們處理不期而至的重大故障)並且將資料儘快地存入永久儲存——亞馬遜簡易儲存服務(Amazon S3)。
我們廣泛地使用Storm來進行實時資料處理,第一個相關的Topology就是從Kafka讀取資訊並儲存到Amazon S3上。
批量計算
一旦這些資料存到了S3上,我們可以使用亞馬遜彈性MapReduce(Amazon EMR)來計算我們的資料能夠計算的任何東西。這既包括要展示在客戶的儀表盤上的資料,也包括我們為了開發新功能而開發的實驗性的任務。
我們使用Cascading框架編寫、Amazon EMR執行MapReduce程式。 Amazon EMR將我們儲存到S3上的資料作為輸入,處理完畢後,再將結果存入S3。我們通過執行在Storm上的排程topology來探測程式執行完畢,並將結果灌入Cassandra叢集,這樣結果就能用於亞秒級查詢API。
實時計算
迄今,我們描述的是一個能夠執行分析計算的持久的容錯的框架。然而,存在一個顯眼的問題——這個框架不是實時的。一些計算每小時計算一次,有的計算需要一整天的資料作為輸入。計算時間從幾分鐘到幾小時不等,把S3上的輸出匯入到服務層也需要這麼多時間。因此,在最好情況下,我們的資料也總是拖後幾個小時,顯然不能滿足實時和可操作的目標。
為了達成實時的目標,資料湧入後進行存檔的同時,我們對資料進行流式計算。
就像我們的儲存Topology讀取資料一樣,一個獨立的Storm Topology實時地從Kafka Topic中讀取資料然後進行實時計算,計算的邏輯和MapReduce任務一樣。這些實時計算的結果放在另一個獨立的Cassandra叢集裡以供實時查詢。
為了彌補我們在時間以及在資源方面可能的不足,我們沒有在批量處理層中而是在實時計算層中使用了一些概率演算法,如布隆過濾器、HyperLogLog(也有一些自己開發的演算法)。相對於那些蠻力替代品,這些演算法在空間和時間複雜度上有數量級的優勢,同時只有可忽略的精確度損失。
合併
現在我們擁有兩個獨立生產出的資料集(批處理和實時處理),我們怎麼將二者合併才能得到一個一致的結果?
我們在API的邏輯中,根據特定的情況分別使用兩個資料集然後合併它們。
因為批量計算是可重現的,且相對於實時計算來說更容錯,我們的API總是傾向於使用批量產生的資料。例如,API接到了一個三十天的時間序列的日活躍使用者數量資料請求,它首先會到批量資料Cassandra叢集裡查詢全範圍的資料。如果這是一個歷史資料檢索,所有的資料都已經得到。然而,查詢的請求更可能會包含當天,批量產生的資料填充了大部分結果,只有近一兩天的資料會被實時資料填充。
錯誤處理
讓我們來溫習幾個失效的場景,看一下這樣的架構在處理錯誤的時候, 是如何避免當機或者損失資料,取之以優雅地降級。
我們在上文中已經討論過裝置上的回退重試策略。在裝置端網路中斷、伺服器端短時無服務情況下,重試保證資料最終能夠到達伺服器。隨機回退確保裝置不會在某區域網路中斷或者後端伺服器短時間不可用之後,不會壓垮(DDos攻擊)伺服器。
當實時處理層失效時,會發生什麼?我們待命的工程師會受到通知並去解決問題。因為實時處理層的輸入是儲存在持久化的Kafka叢集裡,所以沒有資料會丟失;等實時處理恢復之後,它會趕上處理那些停機期間應該處理的資料。
因為實時處理和批處理是完全解耦的,批處理層完全不會受到影響。因此唯一的影響就是實時處理層失效期間,對資料點實時更新的延遲。
如果批處理層有問題或者嚴重延遲的話,會發生什麼?我們的API會無縫地多獲取實時處理的資料。一個時間序列資料的查詢,可能先前只取一天的實時處理結果,現在就需要查詢兩到三天的實時處理結果。因為實時處理和批處理是完全解耦的,實時處理不受影響繼續執行。同時,我們的待命工程師會得到訊息並且解決批處理層的問題。一旦批處理層恢復正常,它會執行那些延遲的資料處理任務,API也會無縫切換到使用現在可以得到的批處理的結果。
我們系統後端架構由四大元件構成:事件接收,事件儲存,實時計算和批量計算。各個元件之間的持久化佇列確保任意元件的失效不會擴散到其他元件,並且後續可以從中斷中恢復。API可以在計算層延遲或者失效時無縫地優雅降級,在服務恢復後重新恢復;這些都是由API內部的檢索邏輯來保證的。
Answer的目標是建立一個儀表盤,這個儀表盤能夠把了解你的使用者群變得非常簡單。因此你可以將時間花費在打造令人驚歎的使用者體驗上,而不是用來掘穿資料。從現在就開始,點選此處更多瞭解Answers。
非常感謝致力於將此架構實現(付諸現實)的Answers團隊。還有《Big Data》這本書的作者Nathan Marz。
貢獻者
Andrew Jorgensen, Brian Swift, Brian Hatfield, Michael Furtak, Mark Pirri, Cory Dolphin, Jamie Rothfeder, Jeff Seibert, Justin Starry, Kevin Robinson, Kristen Johnson, Marc Richards, Patrick McGee, Rich Paret, Wayne Chang.