1. 程式人生 > >The Beam Model:Stream & Tables翻譯(上)

The Beam Model:Stream & Tables翻譯(上)

作者:周思華

歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。



本文嘗試描述Beam模型和Stream & Table理論間的關係(前者描述於資料流模型論文、the-world-beyond-batch-streaming101和the-world-beyond-batch-streaming-102,後者被MartinKleppmann和JayKreps推廣)。事實證明,Steam & Tables理論對描述Beam模型的底層基礎觀點具有啟發性意義。此外,考慮穩健的流處理概念能被整合進SQL,清楚瞭解它們間的關係是特別有益的。考慮到完整性,本文首先會對上述文章(主要是Martin和Jay的帖子)進行一個簡要的回顧。

1 Steam & Tables的基礎知識


Steam & Tables的基本思想源於資料庫。熟悉SQL的人都可能熟悉表和表的基本屬性,大致概括為,表包含行和列,每行都由顯式或隱式的鍵唯一標識。 回憶下大學資料庫系統課程,可能會記得大部分資料庫底層所使用到的資料結構是一個只能進行追加寫的日誌檔案。事務應用於資料庫表時,這些事務首先會記錄在這個日誌中,然後事務會順序的作用於目標表來實現更新操作。在Steam & Tables概念中,上面提及的事務日誌實際上就是流。從這個角度來看,我們現在可以理解如何將流轉化為表:將流中的事務順序的執行,其執行結果便成為了表。但是如何將錶轉化為流?本質上是逆思想:流是表的更新日誌。資料庫系統中的物化檢視功能是一個用於說明表到流轉換的不錯案例。SQL中的物化檢視,允許你指定表的查詢語句,系統將這個查詢語句視為另一張表。物化檢視本質上是查詢的快取版本,當源表內容發生變化時,系統需要確保檢視對應的內容最新。顯而易見,物化檢視是基於源表的更新日誌實現,任何時刻源表發生變化都會被記錄下來,然後資料庫評估物化檢視的查詢上下文是否需要更新,並將結果更新到物化檢視上,以此來保證檢視的內容為最新。

結合以上兩點,進行歸納總結,我們可以得出一個Steam & Tables的相對理論:


  • 流→表:隨著時間,在流上不斷運用聚合操作,其結果便是表。

  • 表→流:隨著時間,觀測到的表上的變更操作序列便是流。


這是一對非常強大的概念,它們能被正確的應用到了流處理中是ApacheKafka取得巨大成功的一個重要原因,其生態系統就是圍繞這些基本原則構建而成。然而,這些理論本身沒有足夠泛化到可以將Steam & Tables與Beam模型中所有概念相結合。為此,我們必須更深入一點。

1.1 關於Steam & Tables的通用理論
如果想將Steam & Tables理論和我們所知道的Beam模型相結合,需要把一些零散的知識結合起來,特別是:

  • 批處理是如何適應Steam & Tables這些理論的?流與有界和無界資料集的關係是什麼?

  • 如何將What、Where、When和How四個問題對映到Steam&Tables中


在此之前,我們首先需要對面臨的問題有個清晰的認知。除了通過上述定義來理解Steam & Tables間的關係外,獨立定義它們的含義也很有必要。先從簡單的角度看下Steam & Tables的定義,這對我們未來的一些分析很有幫助,它們如下: 

  • 表:靜態的資料

這並不是說表的內容是不變的。幾乎所有實時表的內容都會以一些方式隨時間不斷變化。但在給定時刻,表的快照提供了資料庫整體資料中的一部分資料檢視。通過這種方式,表提供了一個供資料停下來快取的靜態場所:隨著時間推移,在這裡資料可以被累積計算、並且可以被觀測。

  • 流:動態的資料

表捕獲的是某一特定時間點的資料檢視,而流捕獲的是資料隨時間的變化發展。JulianHyde喜歡說流像表的求導結果,表像流的積分結果,這種使用數學思維來理解是不錯的方式。
雖然流與表密切相關,即使在許多案例中,一方來源完全借鑑於另一方,但一定要記住,它們之間是存在區別的。雖然區別是微妙的,但也是重要的,我們會在下面看到。

2 批處理 vs Stream & Tables
隨著討論的深入,讓我們開始總結一些零散分析。首先,我們要解決的第一個問題是關於批處理的問題。最後,我們將發現第二個關於流與有界和無界資料的關係的問題將自然而然地從第一個答案中得到解決。

2.1 從Stream & Tables的角度看MapReduce模型
為使我們分析起來更簡單,首先我們可以看下Stream &Tables理論如何與傳統的MapReduce任務相結合。就像它名字所表示的那樣,MapReduce由兩個關鍵的階段組成:Map階段和Reduce階段,為了使得我們的分析更加清晰,這裡將其拆分成6個子階段:
1. MapRead:消費輸入資料,將資料預處理成標準的K/V結構,為Map階段準備;
2. Map: 不斷的消費(可能並行)前面過程預處理的單個K/V對,輸出0或者多個K/V對;
3. MapWrite: Map階段輸出的具有相同key的value在這過程會被叢集聚合在一起,聚合後的資料形如(K,Iterator(V)),接著持久化這些(K, Iterator(V))資料,簡單來說,MapWrite就是基本的根據key 進行聚合然後checkpoint這些結果到儲存系統;
4. ReduceRead: 消費MapWrite階段持久化的shuffle資料(K可能作為分桶的key,從而寫入到不同的磁碟上),轉變成標準的(K,List(value))結構為Reduce階段做準備;
5. Reduce: 不斷消費一個Key對應的多條value,輸出0條或者多條記錄,這些記錄仍然對應這個key;
6. ReduceWrite:將Reduce階段的結果寫入資料儲存介質。

雖然在很多資料中,上述的MapWrite、ReduceRead階段會被統一稱為MapReduce中的Shuffle階段,但是出於我們的目的,這兩個階段最好單獨分開看待。將MapRead和ReduceWrite分別看成是Sources與Sinks可能更好理解。除此之外,我們現在看看它們與Stream &Tables理論又存在哪些關係?

2.2 從Stream & Tables的角度看Map過程
有一點需要說明一下,由於在map階段中,它的輸入、輸出都是表的形式,有些人可能會自然而然的認為,map過程中涉及到的都是隻有表而已。畢竟對於批處理任務來說,大家都知道它是以表作為輸入,然後再輸出結果表。如果把整個批處理過程看出是執行一段SQL語句的話,可能更好理解一些。但是map過程與表之間的關係到底是什麼呢?難道它就真的只與表有關,與流就一點關係也沒有嗎?下面讓我們一步步深入的進行說明?

首先,MapRead消費一張表,然後產生結果資料,這些結果資料又被下一步Map階段作為輸入資料,想要理解的更透徹些,可以看下Map階段的API,JAVA介面如下:
voidmap(KI key, VI value, Emit);
每消費一條input表中的k/v對,都將呼叫一次map方法,如果你發現這裡輸入表的記錄資料像流一樣被處理,那麼恭喜你,你是對的。稍後我們將更進一步的去看錶是如何轉化為流,但是現在,我們已經瞭解到MapRead階段會迭代消費輸入表中的資料,同時使這些資料以流的形式供Map階段消費。

下一步,Map階段消費流,然後幹什麼呢?由於map執行的是對一個元素的轉化操作,因此它不會做任何阻止資料流動的事情,通過過濾一些元素或者拆分一些元素成為多個元素,它可以有效改變流中的資料,但是map階段結束以後,這些元素彼此相互獨立。因此可以說,map階段消費流同時產出流。

一旦map階段結束以後,就進入了MapWrite階段,我上面提到,MapWrite根據key聚合記錄,然後以這種資料結構持久化到儲存介質中。這裡儲存到持久化儲存其實不是嚴格必須的,也可以儲存到其他地方(假如上一節點流被儲存了,中間結果再失敗的時候就可以通過重新計算上個節點得到,類似spark的的RDD方法),最重要的是在這一步中記錄被聚合到了一起,並被儲存在儲存介質上,可能是記憶體、磁碟、其他能夠儲存的介質。這個重要的原因是,聚合操作導致的結果是,那些先前在流中一條一條流動的資料通過key被放到同一位置,因此能夠針對每個key後的分組資料進行聚合處理,注意這裡是不是和前面提到定義流到表的轉換很像呢?隨著時間推移,更新流的聚合結果進而產生表,MapWrite以key來將流中的資料進行分組,將分組資料再寫入下一級,因此將流又轉化為了表。


到此為止我們已經討論了MapReduce過程的上半部分(Map部分),來看下我們目前為止看到了什麼?(在圖1中)



圖 1: MapReduce的Map階段,表中的資料轉化為流之後又轉回到表



通過三個操作完成了從表到流再到表的轉換過程,MapRead將錶轉換成流,map階段又將該流轉變成了新流,最後這個新流經過 MapWrite又轉表回到表,接下來將會發現Reduce階段的三個操作和這三個操作很類似,儘管如此在接下來對Reduce階段進行說明的過程中,我仍然會指出一個重要的細節出來。

3 從Stream & Tables的角度看Reduce過程
在瞭解了MapWrite以後,ReduceRead本身相對無趣,因為它基本上與MapRead相同,除了讀取的是list形式的資料而不是單個值,因為MapWrite儲存的資料是k/list(v)對。 但是,它仍然只是迭代計算一個表的快照,將其轉換為流,這裡沒什麼新鮮的。

Reduce實際上只是一個Map階段的變形,接收每個鍵的值列表而不是單個值。因此,它仍然只是將單個(複合,(K,List(V)))記錄對映到零個或多個新記錄。ReduceWrite這裡是值得注意的一個過程,我們都知道這個過程會將流轉變成表,因為上面的Reduce過程產生流而最終的 ReduceWrite輸出卻是表。這個是如何做的?其實這個就像前面的MapWrite階段一樣,對前一個階段的輸出的流按照key進行分組,然後將結果持久化到儲存介質。假如你記得我前面提到的指定key對於reduce過程是一個可選的特徵,使用這個特徵,ReduceWrite和MapWrite基本相同,如果reduce的輸出沒有指定key,那麼資料到達下游以後會發生什麼呢?

再回想下經典sql表的執行語義將有助於理解將會發生什麼,儘管在sql表中推薦使用主鍵,但是sql表並不是嚴格需要主鍵來區分每行資料的,如果表中沒有主鍵,插入到表中的每條資料都被視為新的獨立的一行,儘管表中可能存在一條或者多條相同的資料,這裡大部分是通過為表增加自動遞增的列作為資料的key來實現的。在這些場景下這些key可能僅僅是一些物理塊的位置索引,不會當做邏輯識別符號去處理或者暴露出去。這個隱含的key,正是ReduceWrite中處理無Key資料情況的應對方法。 從概念上講,這仍然是按key分組的操作,但是由於缺少使用者提供的key,ReduceWrite認為每條資料都是新的,每條資料都擁有一個唯一的key,然後根據它進行分組(結果是每組僅有一條資料),最後將結果流傳到下游。

現在讓我們回顧下流/表的轉換的整個流程,可以發現它是“表 -> 流 -> 流 -> 表 -> 流-> 流 -> 表”的序列。儘管我們處理的是有界資料,儘管我們使用的是傳統的批處理思想,但其實本質仍然是流和表的轉化。



圖2:從流/表的角度來看MapReduce的Map和Rdeuce



4 總 結

通過這些分析,除了前面提到的兩個問題外還有哪些問題呢?

Question:批處理是如何適配到Stream & Tables理論中的?

Answer:不錯的問題,基本模式是:

  • 1. 讀取全部表的資料變成了流;

  • 2. 在分組操作之前,一個流被轉化為新的流;

  • 3. 分組將流變成了表;

  • 4. 重複步驟1-3,直到跳出整個流程。


Question:流與有界和無界資料的有什麼關聯嗎?

Answer:我們可以通過MapReduce例子看出,無論是對於有界還是無界的資料,流只是資料的動態形式。


通過這些分析,很容易發現Stream & Tables理論與有界資料的批處理理論差異並不大,事實上這更加支援我之前提出的批處理與流處理二者並無差異的想法,有了這些分析,我們可以很好的總結出一個通用的Stream & Tables理論,但是要把這些東西理清楚,我們最後要解決what/where/when/how這個四個問題,找出它們之間的聯絡。


網易有數:企業級大資料視覺化分析平臺。面向業務人員的自助式敏捷分析平臺,採用PPT模式的報告製作,更加易學易用,具備強大的探索分析功能,真正幫助使用者洞察資料發現價值。可點選這裡免費試用





相關文章:
【推薦】 如何準確又通俗易懂地解釋大資料及其應用價值?
【推薦】 雲架構師進階攻略(2)
【推薦】 Andorid自定義attr的各種坑