日誌和實時流計算處理

OReillyData發表於2016-06-21


640?wx_fmt=jpeg

到目前為止,我還僅僅只是描述了一些把資料從一個地方拷貝到其他地方的多種的方法。然而,在儲存系統間挪動位元組並不是故事的結尾。實際上我們發現,“日誌”是“流”的另外一種說法,而日誌(的處理)是流計算處理的核心。

但是先等一下,到底什麼是流計算處理?

如果你是上世紀九十年代末和二十一世紀初的資料庫或者資料基礎設施產品的粉絲,你可能會把流計算處理和那些通過SQL引擎或者用“流程圖”介面來進行資料驅動的處理過程聯絡起來。

而如果你是追隨著爆炸性增長的開源資料系統的人,你可能就會把流計算處理和諸如StormAkkaS4Samza這樣的系統聯絡起來。很多人會把這些系統看成是一個非同步訊息處理系統,和那些基於叢集的RPC層上的應用沒什麼區別(事實上有些系統確實是這樣)。我還曾經聽有人過把流計算描述成一種模式,即立刻處理資料,隨後就丟棄。

上述兩種觀點都有失偏頗。流計算處理與SQL毫無關係;同時也不侷限於實時處理系統。沒有任何的理由來限制你去用多種語言來處理昨天或者一個月以前的資料流;也沒有說你必須(或者應該)把獲得的原始資料丟棄掉。

我對流計算處理的看法則更加寬泛,即能做持續資料處理的基礎設施。我認為流計算處理的計算模型可以是如同MapReduce那樣的分散式處理框架一樣的通用,只要它能提供低延遲的結果就可以。

而真正來驅動(或決定)處理模型的則是資料收集的方法。通過批次收集的資料則自然由按批次處理。對於持續流入的資料,就用持續的實時處理方式。

美國國家統計局的人口普查資料是一個按批次收集資料的好例子。統計局會定期啟動人口普查,派專人上門去挨家挨戶的收集美國公民的人口資料。這種方式對於在第一次普查開始的1790年(參見圖1-1)來說是有道理的。那時候的資料收集在本質上就是批次的,因為要騎馬去走訪,再在紙上填寫好統計記錄,再把這些記錄一批批地送到中心點去由人工來累加計算。而今時今日,在你給別人說這個人口普查的過程的時候,人們會立刻質疑為什麼我們不記錄一個出生和死亡的記錄,然後可以隨時隨地的用任何的粒度來計算人口總數。

640?wx_fmt=png

圖1-1. 第一次美國人口普查是批次收集資料的,因為受限於當時的技術條件。然而,在一個數字化、網路化的世界裡,批次資料收集早已不是必須的了

當然,這(人口普查)是一個極端的例子。但是現在很多的資料傳輸過程依然依賴於定期的收集和批次化傳輸與整合。顯然批次收集資料的最自然的處理就是批次處理。隨著這些過程逐步被實時輸入收集所替代,我們也要相應的開始進行實時的資料處理,從而能平滑所需的處理資源,並降低延遲。

一個現在的網際網路公司並不需要有任何批次資料收集。網站產生的資料或是使用者行為資料或是資料庫的改變,而兩者都是持續發生的。事實上,如果你仔細想想,幾乎任何的業務的本質的機制都幾乎是一個持續的過程。如傑克 鮑爾所說,事件總是隨時發生。出現是批次收集資料的情況,一般都是因為一些手工的過程或者是缺乏數字化,或者是因為歷史原因造成的沒法自動化或者數字化的材料。這時傳遞和對資料做出反饋一般都非常慢,如果整個過程需要運送紙張並由人來做處理。剛剛實現自動化後,一般還是會保留原來的處理流程,因此即便是媒介發生了改變,而(這種)流程還是會持續很長時間。

每天執行的批次處理資料的產品經常是有效地模擬了用一天為時間視窗的持續計算。而底層的資料當然是總在改變。

上面的討論有助於釐清對於流計算處理的常見誤解。通常認為某些種類的資料處理不適合用流計算系統來實現,而必須用批處理系統。我聽過的一個典型的例子就是計算百分位、最大值、均值和其他類似的需要用所有的資料來做的聚合統計。但是這往往帶來了某種誤解。確實,類似計算最大值這樣的塊操作需要用時間視窗內的所有資料。然後這樣的計算絕對能夠通過流計算系統來實現。事實上,如果你檢視早期的流計算的學術文章,一般它們完成的第一件事就是給出簡潔的語義來定義時間視窗,以便於針對於視窗的操作成為可能。

看到這裡,很容易能統一我對於流計算處理的觀點,即流計算是更寬泛。 它和是不是塊和非塊並沒有關係,僅僅只是一個底層資料裡包含了時間定義的處理機制,並不要求對於處理的資料需要有一個靜態的快照。這意味著流計算處理系統以一個使用者控制的頻率來產出結果,而不是一直等到資料全部到達。從這個角度看,流計算是批次計算的一個更泛化的操作。考慮到現在實時資料的的普及,這應該是一個更重要的泛化。

為什麼這種傳統的對於流計算處理的觀點成為一個先進的應用。我認為最大的原因是因為缺乏實時資料收集的方法,從而讓持續處理成為某種理論上的想法。

我確實認為缺乏實時資料收集的方法是商用流計算處理系統的夢魘。它們的客戶依然是在做面向檔案的日復一日的ETL和資料整合。構建流計算處理系統的公司一般專注於提供計算引擎來處理實時資料,但卻發現現實中很少有客戶有實時資料流。事實上在我在領英的早期時光,有個公司試圖賣給我們一套非常酷的流計算處理系統,但因為當時我們所有的資料都是按小時收集的檔案,所以我們所能想到的就是把這些小時檔案在每小時結束的時候餵給這個系統。這個公司的工程師發現這是一個非常常見的問題。唯一真實的例外就是金融界。在這個領域裡流計算處理有一些成功的案例,而恰恰是因為這個領域裡實時流資料才是主流,而如何處理這些實時資料流才是主要關注點。

即使是在健康的批處理生態系統裡,實際上流計算處理作為基礎架構型別的適用性也是很強的。它涵蓋了實時處理/相應業務和離線批處理業務的基礎架構上的鴻溝。對於現代的網際網路企業,我認為大約25%的程式碼是關於這種鴻溝的。

現在發現日誌(log)解決了流計算處理裡的一些非常關鍵的技術問題。後面我會陸續介紹這些問題,但其中最大的問題它解決的就是它讓資料成為了實時的多訂閱者的資料匯入機制。

對那些希望能更多瞭解日誌和流計算處理間的關係人,我們提供了開源的Samza,一個專門為這些想法構建的實時流計算處理系統。在這個連結裡面我們很詳細地介紹了這些想法的應用。但這不是專門為了某個特定的流計算處理系統的,因為幾乎所有的主要流計算處理系統都和Kafka有某種程度的整合,讓Kafak來作為資料的日誌來進行處理。

資料流圖

關於流計算處理的最有趣的方面就是它和一個流計算處理系統的內部機制沒有任何關係,想反的是,相關的是他擴充套件了我們前面資料整合討論裡的資料來源的觀點。我們主要討論了主要資料來源和主要資料的日誌化。即事件和資料都是直接由各種應用執行中生成的。流計算處理讓我們可以也包含從其他資料來源裡計算出的資料來源。 這些計算出來的資料來源對消費者而言與用來計算其的其他資料來源沒什麼區別(請參看圖1-2)。

640?wx_fmt=png

圖1-2.來自多日誌的多路流處理圖

這些計算出來資料來源可能會包含相當複雜和智力的成分在其處理過程裡,因此也是極具價值的。例如,谷歌在這裡描述了它是如何在一個流計算處理系統上重構它的網頁爬取、處理和建索引的管道的過程。這可能是這個行星上最複雜、最大規模的資料處理系統之一了。

所以什麼是流計算處理過程?對於我們的目的而言,一個流計算處理工作就是那些從日誌中讀取並輸出到日誌或其他系統的任務。那些作為輸入和輸出的日誌把整個流程連線成了一個處理階段的圖。使用這種中心化日誌的形式,你就能觀察所有機構的資料的獲取、轉換和流動,其實就是一系列日誌以及從他們中讀出和寫入他們的過程。

一個流計算處理過程並不必需要有一個時髦的框架。它可以是任何一個或多個讀取和寫入日誌的過程。額外的基礎架構和支援能夠幫助管理和擴充套件這種近乎實時的處理過程程式,而這也就是流計算框架所做的。


日誌和流計算處理

為什麼你在所有的流計算處理裡需要日誌?為什麼不是讓處理單元通過簡單的TCP或者其他輕量級的訊息協議來更直接的通訊?有多個理由來支援這一(日誌)模式。

首先,這種模式可以讓每個資料集都能為多訂閱者所用。每個流處理過程的輸入對任何需要的處理器都可用;同時每個輸出也都對任何需要的都可用。這一點不僅對生產資料流很好用,而且也在複雜的資料處理管道里除錯和監控階段很有幫助。能快速的進入一個輸出流並檢查它的有效性,同時計算一些監控的統計資料,或者僅僅只是看看資料長什麼樣,這些都使得開發變的非常有可追蹤性。

其次,這樣使用日誌能確保每個資料消費者處理過程中順序可以被保留。某些事件資料可能被按時間戳鬆散地排序了,但是不是每種事件資料都這樣。考慮從來自資料庫的一個更新流,我們可能有一系列的流處理任務來處理這些資料並準備為搜尋索引來做索引。如果對同一個記錄同時做兩次更新,那麼我們最後可能在索引的最終結果出錯。

這樣使用日誌的最後一個可能也是最重要(可探討)的原因是它提供了快取和對每個處理過程的隔離。如果一個處理器產生結果的速度比它後續的消費程式的處理能力快,我們可以有三種選擇:

  • 我們可以先暫停上游的處理任務,直到下游的任務可以處理。如果只用TCP而沒有使用日誌,這種情況是最可能發生的。

  • 我們就把資料丟棄掉。

  • 我們可以在兩個處理任務間快取資料。

丟棄資料在某些場合可能沒什麼。但是基本都是不可接受的,也從來不被希望這樣做。

暫停(上游)處理聽起來似乎是一個可接受的選擇。但實際中這會成為一個很大的問題。考慮到我們需要的不僅僅是對單一的應用流程建立模型,而是為整個機構建立全套的資料流模型。這就將不可避免的形成一個複雜的資料處理流網路,由不同的部門團隊的不同的資料處理器來組成,並支援不同的SLA。在這樣複雜的資料處理網路裡,如果因為後續處理能力不足或者失敗而導致上游的資料產生器被暫停,這都會級聯地影響上游資料流程式,從而使得的很多處理器都被暫停。

這樣看來唯一可用的選擇:快取。日誌可以是非常非常大的快取,可以讓處理程式被重啟,或者即使失效了也不會影響處理圖裡的其他部分。這也意味著某個資料消費程式可以停機很長時間,而不會影響上游的程式。只要在它重啟後能及時處理完快取的資料,大家都皆大歡喜。

在其他地方,這也不是一個不尋常的模式。巨大、複雜的MapReuce流就使用了檔案作為檢查點,並共享他們的中間處理結果。巨大、複雜的SQL處理管道也是建立了很多中間的臨時表。這裡僅僅只是運用了這種模式的抽象-日誌-使得它適合於處理運動中的資料。

StormSamza是兩種基於這個模式構建的流技術處理系統,也能使用Kafka或者其他類似的系統作為他們的日誌部分。

處理資料:Lambda架構和一個可替換的方案

一個基於這種日誌資料模型的有趣的應用就是Lambda架構,由內森·馬茲提出。他寫了一個廣為傳播的部落格(《如何打敗CAP定理》),其中介紹把流處理系統和批次處理相結合的方法。這個架構被證明是一個非常流行的想法。已經有專門的網站書籍了。

什麼是Lambda架構?如何運用?

Lambda架構一般類似於圖1-3

640?wx_fmt=png

圖1-3. Lambda架構

它工作的方式是不可變的一系列資料記錄被採集,並同時並行地送給批處理和流處理系統。資料轉換的邏輯被實現兩次,一次是在批處理系統裡,一次是在流處理系統裡。然後把二者處理的結果在查詢的時候合併,給出一個完整的答案。

這個方式有很多中變形,這裡我是有意地簡化了許多。例如你可以使用多種類似的系統,如Kafka、Storm和Hadoop。而大家也經常會使用兩種不同的資料庫來儲存輸出結果,一種是專門為實時處理優化的資料庫,而其他的則是為批處理所準備的。

Lambda架構的優點?

Lambda架構強調保留原始輸入資料不變。我認為這是一個非常重要的特性。處理複雜資料流的能力得到了很大的加強,因為能夠看到資料是什麼樣和輸出是什麼樣。

我還喜歡這個架構強調了重複處理資料的問題。能重複處理資料對流計算系統來說是一個重大挑戰,但卻經常被忽略。

對於重複處理,我的意思是再次處理輸入的資料從而再次計算出結果。這是一個太明顯,但也經常被忽略的需求。程式碼總是在變。所以如果你的程式碼已經從輸入流中計算出了結果,當程式碼改變後,你需要再次計算輸出來檢查程式碼修改的效果。

為什麼程式碼會變?也許是因為你的應用在演進,你又想計算一些之前不需要的新的輸出項。或者是你發現了一個程式碼缺陷並修好了。無論如何,當這件事發生的時候,你就需要重新計算你的輸出結果。我發現很多的人試圖去構建一個實時資料處理系統,但根本不去仔細思考這個問題,並最終導致系統不能很快的演進,僅是因為沒有一個好的方法來解決重複處理的需求。

它的缺點

Lambda架構的一個問題就是需要維護兩套複雜的分散式系統的程式碼,這看起來就很頭疼。但我不認為這個問題不能解決。

如Storm和Hadoop這樣的分散式框架的程式設計是很複雜。不可避免的是程式碼最後會專門為所使用的框架而特殊地構建。由此導致的運維的複雜性是被所有使用這個框架的人所一致同意的。

解決這一問題的一個方法是對實時和批次框架抽象出一種語言或框架。用這個高階框架來寫你的程式碼,然後去“編譯”成或是流計算處理程式碼, 或是MapReduce的批處理程式碼。Summingbird就是這樣做的一個框架。它確實讓事情好了一些,但我不認為它真正解決了問題。最終,即便你能避免為應用寫兩套程式碼,運維兩套系統的負擔還是很重的。新的抽象僅僅是提供了兩套系統的交集所支援的特徵。更糟的是,使用這一統一的框架就把整個生態系統裡那些使得Hadoop非常強大的工具和語言(如Hive、Pig、Crunch、Cascading、Oozie等)給排除在外了。

打個比方,想想那個使跨資料庫物件關係對映(ORM)透明化的臭名昭著的難題。而這也還是對非常相同的系統進行抽象來用標準介面語言提供相同的能力哦。那麼去抽象化兩個完全不同的構建於剛剛穩定化的分散式系統上的程式設計模式將會更加的難。

一個備選方案

作為一個基礎架構的設計者,我認為真正有意義的問題是:為什麼不去改進流計算系統來解決所有的問題集?為什麼你需要再貼上一個別的系統(批處理)?為什麼你不去把實時處理和在程式碼改變時需要的重複處理一起解決?流計算處理系統已經有了並行的概念,為什麼不是去通過增加並行性來解決重複處理的問題,並很快的再現歷史?答案是你可以這麼做。我認為這就是如果你需要構建這樣一個系統的一個合理的備選方案。

當我和別人討論這個思想的時候,有時他們會告訴我流計算處理對於高吞吐率處理歷史資料並不合適。但這是他們基於已經使用的系統的缺陷的直覺反應。這些系統要不就是很難擴充套件,或者是根本就沒存歷史資料。但沒有理由認為這就是對的。流計算處理系統的基本抽象就是資料流的有向無環圖(DAG)。這和傳統的資料倉儲(如Volcano)的基本抽象是一樣的,並和MapReduce的後繼Tez的基本底層抽象也是一樣。流計算處理僅僅是對這一資料流模型的泛化,即對中間結果提供檢查點(checkpointing)並持續地輸出到最終的使用者。

那麼我們怎麼才能從我們的流處理任務裡完成重複處理?我最喜歡的方法其實非常的簡單。

1. 使用Kafka或者其他的一些能幫你儲存你想重複處理的資料的全部日誌的系統,這些系統還要能支援多訂閱者功能。例如,如果你想重複處理之前30天的資料,那就把Kafka的儲存時間設成30天。

2. 當你想重複處理資料時,啟動第二個你的流計算系統的例項,從保留資料的開始再次處理這些資料,不過把結束輸出到新的表。

3. 當第二個例項已經可以趕上現有資料的進度了,就把應用定向到從新的輸出表裡讀取資料。

4. 停止原有版本的任務例項,並刪除舊的輸出表。

這個架構類似圖1-4裡所示。

 

640?wx_fmt=png

圖1-4. Lambda架構的一個備選替換方案,移除了批處理系統。

與Lambda架構不同,這個方法裡你只是當你程式碼改變而確實需要再計算結果的時候你才需要重複處理。當然這樣的重複計算只是用你程式碼的改進版本,使用相同的框架,並處理相同的資料。

很自然的,你希望能多給你的並行的重複處理任務更多的資源以便於讓它能非常快地完成。

當然,你可以進一步優化這個方法。很多情況下你可以把兩個輸出表合併。然而我認為讓兩個輸出表並存一段時間是有不少好處的。這可以及時回退回舊的邏輯,而你所需要做的僅僅只是把應用再重定向到舊的表。另外,對於一些非常重要的場景裡,你可以使用自動的A/B測試或者多臂強盜演算法來控制這個切換,保證新的程式碼確實是比舊有邏輯有改進而不是變的更差了。

需要注意的是,這個方法並不是說你的資料不能輸出到Hadoop裡。它僅僅是說你不必要在Hadoop裡面做重複處理。Kafka和Hadoop有非常好的整合,所以從中匯出任何Kafka的topic都很簡單。通常把一個流計算處理任務的輸出甚至是中間結果映象到Hadoop中,從而讓一些例如Hive這樣的分析工具來處理,或者是作為其他人的輸入,或是為離線資料處理流服務。這都是很有用的。

我們已經記錄瞭如何實現這個方法,包括使用Samza實現重複處理的框架的其他變形。

這兩種方法所對應的效率和資源之間的權衡是值得討論的。Lambda架構需要同時一直執行重複處理和實時處理任務。而我提出的方法僅僅只是在需要重複處理的時候才執行第二個任務的例項。然而我的提議要求有臨時的額外的輸出資料庫的儲存。同時資料庫也要能支援這樣大容量的寫操作。兩種情況下,重複處理所造成的額外任務都可以被平均分佈出去。如果你有很多這樣的任務,他們不需要一下都重複處理完。所以在一個共享的叢集裡,如果有很多個這樣的任務,你需要預留一部分容量來處理這些隨時會發生的任務。

我提議方法的真正的優勢不是效率,而是能讓大家在一個單一的處理框架裡開發、測試、除錯和運維他們的系統。

所以在簡介是很重要的場景裡,可以把我的方法作為Lambda架構的一個備選方案。

有狀態的實時處理

日誌和流計算處理之間的關係不僅僅限於重複處理。如果實際的流計算處理系統需要維護狀態資訊,這時使用日誌就可以有另一個新的用處了。

一些實時流處理系統僅僅是無狀態的一次性資料轉換。但是很多場景下都是比較複雜的計數、匯聚或視窗間的連線等操作。例如,你可能希望對事件流(比如點選流)進行增強,比如通過連線點選流和使用者帳號資料庫來給點選加上使用者資訊。不可避免的,類似這樣的處理最終都會需要儲存某種程度的狀態資訊。例如當計數的時候,你需要保留此前的計數值。這樣的狀態資訊怎麼樣才能保留下來當處理器自身會發生失效?

最簡單的方法就是把狀態資訊放到記憶體裡。然而如果處理器崩潰了,這個資訊就丟失了。如果狀態資訊僅僅是在一個視窗裡維護的,這個處理器就可以從這個視窗開始的點重新再來。但是如果計算一小時的計數,這個方法可能就不行了。

另外一個方法就是把所有的狀態資訊都儲存到一個異地系統,並通過網路獲取。這個方法的問題是沒有本地化的資料,還需要很多的網路傳輸。

我們怎麼能支援一些如把一張表分片到處理裡的操作?

回顧之前對於表和日誌的二元性的討論,這給我們提供了能把流轉換成表並和我們的處理並存的方法。這還提供了一個對於表失效的解決機制。

流處理器可以把它的狀態保持到一個本地的表或者索引裡,如dbdRocksDN,或者一些更不尋常的機制,比如Lucenefastbit索引等。這些儲存的內容是從輸入流裡匯入的(可能是首先做一個強制轉換後)。它對本地索引可以記錄下修改日誌(changelog),並儲存這些修改日誌,從而在系統奔潰或重啟後可以恢復出狀態資訊來。這就可以提供一個通用的機制來儲存狀態資訊在一個索引型別的本地內。

當(流)處理過程失效,它就從修改日誌裡恢復索引。日誌在這裡就變成了把本地的狀態資訊轉換成一個增長的隨時記錄的備份。

這個狀態管理的方法有一個優雅的特性,即處理器的狀態也被維護成了一個日誌。我們可以把這個日誌想成是資料庫表的修改日誌。事實上,處理器有一些伴隨著它的非常類似於同分片表的東西。因為狀態本身就是日誌,其他的處理器也可以訂閱它,這就可以非常有用。比如整個處理過程的目標就是更新輸出的最終狀態的場景。

當把從資料庫裡輸出的日誌結合起來看,日誌/表二元性的威力就非常清晰了。修改日誌(changlog)可以從資料庫裡抽取,並被不同的流處理器用不同的方式檢索來和事件流所連線。

我們提供了在Samza裡面使用這種型別的狀態管理的細節,以及很多實際的例子

日誌壓縮

當然,我們不能希望能保持所有時間的狀態改變的完整日誌。除非你有無限的空間,不然日誌總是要被清理的。我會介紹如何在Kafka裡面實現這個功能的。

在Kafka裡,清理有兩個選擇,取決於資料是僅包含純事件資料還是鍵值化的更新。對於事件資料,我的意思是沒有相關的情形發生,比如網頁瀏覽、點選或者其他你會在一個應用的日誌裡發現的東西。對於鍵值化的更新,我的意思是事件有特別記錄的狀態改變,而這被用某些鍵值所識別。資料庫的修改就是一個典型的鍵值化更新的例子。

對於事件資料,Kafka支援儲存資料的視窗。這個視窗可以是用時間(以天)或者是空間(以GB)為單位。大部分人僅僅只是使用它預設的一個星期為儲存視窗。如果你希望有無限的儲存期,就把這個視窗設成無限,你的資料就永遠不會丟失。

然而,對於鍵值化的資料,一個完整日誌的非常好的特徵就是你可以重現源系統的狀態。即,如果你有這個修改的日誌,你可以在另外一個資料庫裡再現這個表,並重建這個表的任意時間點的狀態。這對於不同系統也適用。你可以在另外一個資料庫裡再現源資料庫裡的更新,並維護資料的主鍵(一個搜尋的索引、一個本地庫等等)。

然而,隨著時間的延長,儲存完整的日誌會消耗越來越多的空間,再現過程也會越來越久。因此在Kafka裡,為了支援這種應用場景,我們支援不同型別的儲存。其中一個例子就展示在圖1-5裡。不是簡單地完全丟棄舊的日誌,我們收集了日誌末尾的部分裡過時的記錄作為垃圾。任何在日誌末尾的記錄有最近的更新就會適合於清理(只保留最新的更新)。這麼做就可以保證日誌儲存了一個源系統的完整的備份,但是我們現在不必在完全重建所有的之前的狀態了,而僅僅只是最近更新的狀態。我們把這個特性稱為日誌壓縮

640?wx_fmt=png

圖1-5.日誌壓縮可以確保日誌裡只保留每個鍵值的最新的更新。這對模型更新成可變資料的日誌是很有用的



作者:Jay Kreps

Jay Kreps是Confluent的聯合創始人和CEO。Confluent專注於Apache Kafka。在此之前,Jay是領英的主要架構師之一,專注於資料基礎架構和資料驅動的產品。他是多個可擴充套件的資料系統空間的開源專案的作者之一,包括Voldemort、Azkaban、Kafka和Samza。


長按二維碼識別關注

640?wx_fmt=jpeg



閱讀原文( read more ) 瀏覽更多資料文章。


相關文章