MapReduce後誰主沉浮:怎樣設計下一代資料處理技術

danny_2018發表於2022-11-15

2014 年之前的大資料歷史,也就是 MapReduce 作為資料處理的預設標準的時代。重點探討了 MapReduce 面對日益複雜的業務邏輯時表現出的不足之處,那就是:1. 維護成本高;2. 時間效能不足。同時,我們也提到了 2008 年誕生在 Google 西雅圖研發中心的 FlumeJava,它成為了Google 內部的資料處理新寵。

那麼,為什麼是它扛起了繼任 MapReduce 的大旗呢?

要知道,在包括 Google 在內的矽谷一線大廠,對於內部技術選擇是非常嚴格的,一個能

成為預設方案的技術至少滿足以下條件:

1. 經受了眾多產品線,超大規模資料量例如億級使用者的考驗;

2. 自發地被眾多內部開發者採用,簡單易用而受開發者歡迎;

3. 能透過內部領域內專家的評審;

4. 比上一代技術僅僅提高 10% 是不夠的,必須要有顯著的比如 70% 的提高,才能夠說服

整個公司付出技術遷移的高昂代價。就看看從 Python 2.7 到 Python 3 的升級花了多少年了,就知道在大廠遷移技術是異常艱難的。今天這一講,我不展開講任何具體技術。我想先和你一起設想一下,假如我和你站在 2008 年的春夏之交,在已經清楚了MapReduce 的現有問題的情況下,我們會怎麼設計下一代大規模資料處理技術,帶領下一個十年的技術革新呢?

我們需要一種技術抽象讓多步驟資料處理變得易於維護上一講中提到過,維護協調多個步驟的資料處理在業務中非常常見。

像圖片中這樣複雜的資料處理在 MapReduce 中維護起來令人苦不堪言。

為了解決這個問題,作為架構師的我們或許可以用有向無環圖(DAG)來抽象表達。因為有向圖能為多個步驟的資料處理依賴關係,建立很好的模型。如果你對圖論比較陌生的話,可能現在不知道我在說什麼,你可以看下面一個例子,或者複習一下極客時間的《資料結構與演算法之美》。

蕃茄炒雞蛋這樣一個菜,就是一個有向無環圖概念的典型案例。比如看這裡面番茄的處理,最後一步“炒”的步驟依賴於切好的番茄、打好的蛋、熱好的油。而切好的番茄又依賴於洗好的番茄等等。如果用 MapReduce 來實現的話,在這個圖裡面,每一個箭頭都會是一個獨立的 Map 或 Reduce。

為了協調那麼多 Map 和 Reduce,你又難以避免會去做很多檢查,比如:番茄是不是洗好了,雞蛋是不是打好了。最後這個系統就不堪重負了。但是,如果我們用有向圖建模,圖中的每一個節點都可以被抽象地表達成一種通用的資料集,每一條邊都被表達成一種通用的資料變換。如此,你就可以用資料集和資料變換描述極為宏大複雜的資料處理流程,而不會迷失在依賴關係中無法自拔。我們不想要複雜的配置,需要能自動進行效能最佳化

上一講中提到,MapReduce 的另一個問題是,配置太複雜了。以至於錯誤的配置最終導致資料處理任務效率低下。

這種問題怎麼解決呢?很自然的思路就是,如果人容易犯錯,就讓人少做一點,讓機器多做一點唄。

我們已經知道了,得益於上一步中我們已經用有向圖對資料處理進行了高度抽象。這可能就能成為我們進行自動效能最佳化的一個突破口。回到剛才的番茄炒雞蛋例子,哪些情況我們需要自動最佳化呢?

設想一下,如果我們的資料處理食譜上又增加了番茄牛腩的需求,使用者的資料處理有向圖就變成了這個樣子了。

理想的情況下,我們的計算引擎要能夠自動發現紅框中的兩條資料處理流程是重複的。它要能把兩條資料處理過程進行合併。這樣的話,番茄就不會被重複準備了。

同樣的,如果需求突然不再需要番茄炒蛋了,只需要番茄牛腩,在資料流水線的預處理部分也應該把一些無關的資料操作最佳化掉,比如整個雞蛋的處理過程就不應該在執行時出現。

另一種自動的最佳化是計算資源的自動彈性分配。比如,還是在番茄炒蛋這樣一個資料處理流水線中,如果你的規模上來了,今天需要生產 1噸的番茄炒蛋,明天需要生產 10 噸的番茄炒蛋。你發現有時候是處理 1000 個番茄,有時候又是 10000 個番茄。如果手動地去做資源配置的話,你再也配置不過來了。

我們的最佳化系統也要有可以處理這種問題的彈性的勞動力分配機制。它要能自動分配,比如100 臺機器處理 1000 個番茄,如果是 10000 個番茄那就分配 1000 臺機器,但是隻給熱油 1 臺機器可能就夠了。

這裡的比喻其實是很粗糙也不精準的。我想用這樣兩個例子表達的觀點是,在資料處理開始前,我們需要有一個自動最佳化的步驟和能力,而不是按部就班地就把每一個步驟就直接扔給機器去執行了。

我們要能把資料處理的描述語言,與背後的執行引擎解耦合開來前面兩個設計思路提到了很重要的一個設計就是有向圖。

用有向圖進行資料處理描述的話,實際上資料處理描述語言部分完全可以和後面的運算引擎分離了。有向圖可以作為資料處理描述語言和運算引擎的前後端分離協議。

舉兩個你熟悉的例子可能能更好理解我這裡所說的前後端分離(client-server design)是什麼意思:

比如一個網站的架構中,伺服器和網頁透過 HTTP 協議通訊。

比如在 TensorFlow 的設計中,客戶端可以用任何語言(比如 Python 或者 C++)描述計算圖,執行時引擎(runtime) 理論上卻可以在任何地方具體執行,比如在本地,在 CPU,或者在 TPU。

那麼我們設計的資料處理技術也是一樣的,除了有向圖表達需要資料處理描述語言和運算引擎協商一致,其他的實現都是靈活可擴充的。

比如,我的資料描述可以用 Python 描述,由業務團隊使用;計算引擎用 C++ 實現,可以由資料底層架構團隊維護並且高度最佳化;或者我的資料描述在本地寫,計算引擎在雲端執行。

我們要統一批處理和流處理的程式設計模型關於什麼是批處理和流處理概念會在後面的章節展開。這裡先簡單解釋下,批處理處理的是有界離散的資料,比如處理一個文字檔案;流處理處理的是無界連續的資料,比如每時每刻的支付寶交易資料。

MapReduce 的一個侷限是它為了批處理而設計的,應對流處理的時候不再那麼得心應手。即使後面的 Apache Storm、Apache Flink 也都有類似的問題,比如 Flink 裡的批處理資料結構用 DataSet,但是流處理用 DataStream。

但是真正的業務系統,批處理和流處理是常常混合共生,或者頻繁變換的比如,你有 A、B 兩個資料提供商。其中資料提供商 A 與你簽訂的是一次性的資料協議,一次性給你一大波資料,你可以用批處理。而資料提供商 B 是實時地給你資料,你又得用流處理。更可怕的事情發生了,本來是批處理的資料提供商 A,突然把協議修改了,現在他們實時更新資料。這時候你要是用 Flink 就得爆炸了。業務需求天天改,還讓不讓人活了?!因此,我們設計的資料處理框架裡,就得有更高層級的資料抽象。不論是批處理還是流處理的,都用統一的資料結構表示。程式設計的 API 也需要統一。這樣不論業務需求什麼樣,開發者只需要學習一套 API。即使業務需求改變,開發者也不需要頻繁修改程式碼。

我們要在架構層面提供異常處理和資料監控的能力真正寫過大規模資料處理系統的人都深有感觸:在一個複雜的資料處理系統中,難的不是開發系統,而是異常處理。

事實正是如此。一個 Google 內部調研表明,在大規模的資料處理系統中,90% 的時間都花在了異常處理中。常常發生的問題的是,比如在之前的番茄炒雞蛋處理問題中,你看著系統 log,明明買了 1000 個雞蛋,炒出來的菜卻看起來只有 999 個雞蛋,你仰天長嘆,少了一個蛋到底去哪裡了!

這一點和普通的軟體開發不同。比如,伺服器開發中,偶爾一個 RPC 請求丟了就丟了,重試一下,重啟一下能過就行了。可如果在資料處理系統中,資料就是錢啊,不能隨便丟。比如我們的雞蛋,都是真金白銀買回來的。是超市買回來數錯了?是打蛋時候打碎了?還是被誰偷吃了?你總得給老闆一個合理的交代。

我們要設計一套基本的資料監控能力,對於資料處理的每一步提供自動的監控平臺,比如一個監控網站。

在番茄炒蛋系統中,要能夠自動的記錄下來,超市買回來是多少個蛋,打蛋前是多少個蛋,打完蛋是多少個蛋,放進鍋裡前是多少個蛋等等。也需要把每一步的相關資訊進行儲存,比如是誰去買的蛋,哪些人打蛋。這樣出錯後可以幫助使用者快速找到可能出錯的環節。

小結

透過上面的分析,我們可以總結一下。如果是我們站在 2008 年春夏之交來設計下一代大規模資料處理框架,一個基本的模型會是圖中這樣子的:但是這樣粗糙的設計和思想實驗離實現還是太遠。你可能還是會感到無從下手。後面的章節會給你補充一些設計和使用大規模資料處理架構的基礎知識。同時,也會深入剖析兩個與我們這裡的設計理念最接近的大資料處理框架,Apache Spark 和 Apache Beam。

來自 “ 媚婉蘭君 ”, 原文作者:媚婉蘭君;原文連結:https://new.qq.com/rain/a/20221107A04LOJ00,如有侵權,請聯絡管理員刪除。

相關文章