Spark 比拼 Flink:下一代大資料計算引擎之爭,誰主沉浮?
下一代大資料計算引擎
自從資料處理需求超過了傳統資料庫能有效處理的資料量之後,Hadoop 等各種基於 MapReduce 的海量資料處理系統應運而生。從 2004 年 Google 發表 MapReduce 論文開始,經過近 10 年的發展,基於 Hadoop 開源生態或者其它相應系統的海量資料處理已經成為業界的基本需求。
但是,很多機構在開發自己的資料處理系統時都會發現需要面臨一系列的問題。從資料中獲取價值需要的投入遠遠超過預期。常見的問題包括:
- 非常陡峭的學習曲線。剛接觸這個領域的人經常會被需要學習的技術的數量砸暈。不像經過幾十年發展的資料庫一個系統可以解決大部分資料處理需求, Hadoop 等大資料生態裡的一個系統往往在一些資料處理場景上比較擅長,另一些場景湊合能用,還有一些場景完全無法滿足需求。結果就是需要好幾個系統來處理不同的場景。
(來源:https://mapr.com/developercentral/lambda-architecture/)
上圖是一個典型的 lambda 架構,只是包含了批處理和流處理兩種場景,就已經牽涉到至少四五種技術了,還不算每種技術的可替代選擇。再加上實時查詢,互動式分析,機器學習等場景,每個場景都有幾種技術可以選擇,每個技術涵蓋的領域還有不同方式的重疊。結果就是一個業務經常需要使用四五種以上的技術才能支援好一個完整的資料處理流程。加上調研選型,需要了解的數目還要多得多。
下圖是大資料領域的全景。有沒有暈?
- 開發和執行效率低下。因為牽涉到多種系統,每種系統有自己的開發語言和工具,開發效率可想而知。而因為採用了多套系統,資料需要在各個系統之間傳輸,也造成了額外的開發和執行代價,資料的一致也難以保證。在很多機構,實際上一半以上的開發精力花在了資料在各個系統之間的傳輸上。
- 複雜的運維。多個系統,每個需要自己的運維,帶來更高的運維代價的同時也提高了系統出問題的可能。
- 資料質量難以保證。資料出了問題難以跟蹤解決。
- 最後,還有人的問題。在很多機構,由於系統的複雜性,各個子系統的支援和使用落實在不同部門負責。
瞭解了這些問題以後,對 Spark 從 2014 年左右開始迅速流行就比較容易理解了。Spark 在當時除了在某些場景比 Hadoop MapReduce 帶來幾十到上百倍的效能提升外,還提出了用一個統一的引擎支援批處理,流處理,互動式查詢,機器學習等常見的資料處理場景。看過在一個 Notebook 裡完成上述所有場景的 Spark 演示,對比之前的資料流程開發,對很多開發者來說不難做出選擇。經過幾年的發展,Spark 已經被視為可以完全取代 Hadoop 中的 MapReduce 引擎。
正在 Spark 如日中天高速發展的時候,2016 年左右 Flink 開始進入大眾的視野並逐漸廣為人知。為什麼呢?原來在人們開始使用 Spark 之後,發現 Spark 雖然支援各種場見場景,但並不是每一種都同樣好用。資料流的實時處理就是其中相對較弱的一環。Flink 憑藉更優的流處理引擎,同時也支援各種處理場景,成為 Spark 的有力挑戰者。
Spark 和 Flink 是怎麼做到這些的,它們之間又有那些異同,下面我們來具體看一下。
Spark 和 Flink 的引擎技術
這一部分主要著眼於 Spark 和 Flink 引擎的架構方面,更看重架構帶來的潛力和限制。現階段的實現成熟度和侷限會在後續生態部分探討。
資料模型和處理模型
要理解 Spark 和 Flink 的 引擎特點,首先從資料模型開始。
Spark 的資料模型是彈性分散式資料集 RDD(Resilient Distributed Datasets)。 比起 MapReduce 的檔案模型,RDD 是一個更抽象的模型,RDD 靠血緣(lineage) 等方式來保證可恢復性。很多時候 RDD 可以實現為分散式共享記憶體或者完全虛擬化(即有的中間結果 RDD 當下遊處理完全在本地時可以直接優化省略掉)。這樣可以省掉很多不必要的 I/O,是早期 Spark 效能優勢的主要原因。
Spark 用 RDD 上的變換(運算元)來描述資料處理。每個運算元(如 map,filter,join)生成一個新的 RDD。所有的運算元組成一個有向無環圖(DAG)。Spark 比較簡單地把邊分為寬依賴和窄依賴。上下游資料不需要 shuffle 的即為窄依賴,可以把上下游的運算元放在一個階段(stage) 裡在本地連續處理,這時上游的結果 RDD 可以 省略。下圖展示了相關的基本概念。更詳細的介紹在網上比較容易找到,這裡就不花太多篇幅了。
Flink 的基本資料模型是資料流,及事件(Event) 的序列。資料流作為資料的基本模型可能沒有表或者資料塊直觀熟悉,但是可以證明是完全等效的。流可以是無邊界的無限流,即一般意義上的流處理。也可以是有邊界的有限流,這樣就是批處理。
Flink 用資料流上的變換(運算元)來描述資料處理。每個運算元生成一個新的資料流。在運算元,DAG,和上下游運算元連結(chaining) 這些方面,和 Spark 大致等價。Flink 的節點(vertex)大致相當於 Spark 的階段(stage),劃分也會和上圖的 Spark DAG 基本一樣。
在 DAG 的執行上,Spark 和 Flink 有一個比較顯著的區別。 在 Flink 的流執行模式中,一個事件在一個節點處理完後的輸出就可以發到下一個節點立即處理。這樣執行引擎並不會引入額外的延遲。與之相應的,所有節點是需要同時執行的。而 Spark 的 micro batch 和一般的 batch 執行一樣,處理完上游的 stage 得到輸出之後才開始下游的 stage。
在 Flink 的流執行模式中,為了提高效率也可以把多個事件放在一起傳輸或者計算。但這完全是執行時的優化,可以在每個運算元獨立決定,也不用像 RDD 等批處理模型中一樣和資料集邊界繫結,可以做更加靈活的優化同時可以兼顧低延遲需求。
Flink 使用非同步的 checkpoint 機制來達到任務狀態的可恢復性,以保證處理的一致性,所以在處理的主流程上可以做到資料來源和輸出之間資料完全不用落盤,達到更高的效能和更低的延遲。
資料處理場景
除了批處理之外,Spark 還支援實時資料流處理,互動式查詢,和機器學習,圖計算等。
(來源: https://databricks.com/spark/about )
- 實時資料流處理和批處理主要區別就是對低延時的要求。Spark 因為 RDD 是基於記憶體的,可以比較容易切成較小的塊來處理。如果能對這些小塊處理得足夠快,就能達到低延時的效果。
- 互動式查詢場景,如果資料能全在記憶體,處理得足夠快的話,就可以支援互動式查詢。
- 機器學習和圖計算其實是和前幾種場景不同的 RDD 運算元型別。Spark 提供了庫來支援常用的操作,使用者或者第三方庫也可以自己擴充套件。值得一提的是,Spark 的 RDD 模型和機器學習模型訓練的迭代計算非常契合,從一開始就在有的場景帶來了非常顯著的效能提升。
從這些可以看出來,比起 Hadoop MapReduce, Spark 本質上就是基於記憶體的更快的批處理。然後用足夠快的批處理來實現各種場景。
前面說過,在 Flink 中,如果輸入資料流是有邊界的,就自然達到了批處理的效果。這樣流和批的區別完全是邏輯上的,和處理實現獨立,使用者需要實現的邏輯也完全一樣,應該是更乾淨的一種抽象。後續會在深入對比流計算方面的時候做更深入的討論。
Flink 也提供了庫來支援機器學習,圖計算等場景。從這方面來說和 Spark 沒有太大區別。
一個有意思的事情是用 Flink 的底層 API 可以支援只用 Flink 叢集實現一些資料驅動的分散式服務。有一些公司用 Flink 叢集實現了社交網路,網路爬蟲等服務。這個也體現了 Flink 作為計算引擎的通用性,並得益於 Flink 內建的靈活的狀態支援。
總的來說,Spark 和 Flink 都瞄準了在一個執行引擎上同時支援大多數資料處理場景,也應該都能做到這一點。主要區別就在於因為架構本身的侷限在一些場景會受到限制。比較突出的地方就是 Spark Streaming 的 micro batch 執行模式。Spark 社群應該也意識到了這一點,最近在持續執行模式(continuous processing)方面開始發力。 具體情況會在後面介紹。
有狀態處理 (Stateful Processing)
Flink 還有一個非常獨特的地方是在引擎中引入了託管狀態(managed state)。要理解託管狀態,首先要從有狀態處理說起。如果處理一個事件(或一條資料)的結果只跟事件本身的內容有關,稱為無狀態處理;反之結果還和之前處理過的事件有關,稱為有狀態處理。稍微複雜一點的資料處理,比如說基本的聚合,都是有狀態處理。Flink 很早就認為沒有好的狀態支援是做不好留處理的,因此引入了 managed state 並提供了 API 介面。
一般在流處理的時候會比較關注有狀態處理,但是仔細看的話批處理也是會受到影響的。比如常見的視窗聚合,如果批處理的資料時間段比視窗大,是可以不考慮狀態的,使用者邏輯經常會忽略這個問題。但是當批處理時間段變得比視窗小的時候,一個批的結果實際上依賴於以前處理過的批。這時,因為批處理引擎一般沒有這個需求不會有很好的內建支援,維護狀態就成為了使用者需要解決的事情。比如視窗聚合的情況使用者就要加一箇中間結果表記住還沒有完成的視窗的結果。這樣當用戶把批處理時間段變短的時候就會發現邏輯變複雜了。這是早期 Spark Streaming 使用者 經常碰到的問題。直到 Structured Streaming 出來才得到緩解。
而像 Flink 這樣以流處理為基本模型的引擎,因為一開始就避不開這個問題,所以引入了 managed state 來提供了一個通用的解決方案。比起使用者實現的特定解決方案,不但使用者開發更簡單,而且能提供更好的效能。最重要的是能更好地保證處理結果的一致性。
簡單來說,就是有一些內秉的資料處理邏輯,在批處理中容易被忽略或簡化處理掉也能得到可用的結果,而在流處理中問題被暴露出來解決掉了。所以流計算引擎用有限流來處理批在邏輯上比較嚴謹,能自然達到正確性。主要做一些不同的實現來優化效能就可以了。而用更小的批來模擬流需要處理一些以前沒有的問題。當計算引擎還沒有通用解決方案的時候就需要使用者自己解決了。類似的問題還有維表的變化(比如使用者資訊的更新),批處理資料的邊界和遲到資料等等。
程式設計模型
Spark 1.6 時的 API 狀態
Spark 的初衷之一就是用統一的程式設計模型來解決使用者的各種需求。在這方面一直很下功夫。最初基於 RDD 的 API 就可以做各種型別的資料處理。後來為了簡化使用者開發,逐漸推出了更高層的 DataFrame(在 RDD 中加了列變成結構化資料)和 Datasets(在 DataFrame 的列上加了型別),並在 Spark 2.0 中做了整合(DataFrame = DataSet[Row])。Spark SQL 的支援也比較早就引入了。在加上各個處理型別 API 的不斷改進,比如 Structured Streaming 以及和機器學習深度學習的互動,到了今天 Spark 的 API 可以說是非常好用的,也是 Spark 最強的方面之一。
Flink 的 API 也有類似的目標和發展路線。Flink 和 Spark 的核心 API 可以說是可以基本對應的。今天 Spark API 總體上更完備一下,比如說最近一兩年大力投入的和機器學習深度學習的整合方面。Flink 在流處理相關的方面還是領先一些,比如對 watermark,window,trigger 的各種支援。
小結
Spark 和 Flink 都是通用的能夠支援超大規模資料處理,支援各種處理型別的計算引擎。兩個系統都有很多值得探討的方面在這裡沒有觸及,比如 SQL 的優化,和機器學習的整合等等。這裡主要是試圖從最基本的架構和設計方面來比較一下兩個系統。因為上層的功能在一定程度上是可以互相借鑑的,有足夠的投入應該都能做好。而基本的設計改變起來會傷筋動骨,更困難一些。
Spark 和 Flink 的不同執行模型帶來的最大的區別應該還是在對流計算的支援上。最開始的 Spark Streaming 對流計算想得過於簡單,對複雜一點的計算用起來會有不少問題。從 Spark 2.0 開始引入的 Structured Streaming 重新整理了流計算的語義,支援按事件時間處理和端到端的一致性。雖然在功能上還有不少限制,比之前已經有了長足的進步。不過 micro batch 執行方式帶來的問題還是存在,特別在規模上去以後效能問題會比較突出。最近 Spark 受一些應用場景的推動,也開始開發持續執行模式。2.3 裡的實驗性發布還只支援簡單的 map 類的操作。從最近 Spark+AI Summit 大會上的介紹來看(下圖),會發展成一個和 Flink 的流處理模式比較相似的執行引擎。不過從下圖來看,主要的功能都還在開發中或者待開發。對將來能做到什麼程度,和 Spark 原來的 batch 執行引擎怎麼結合,我們拭目以待。
本文筆者主要闡述 Spark 和 Flink 的技術與場景,後續將進一步探討二者的開源生態以及未來發展前景,包括近期動