摘要:本文整理自阿里雲高階技術專家宋辛童 (五藏),在 FFA 2022 核心技術專場的分享。本篇內容主要分為五個部分:
- Flink Shuffle 的演進
- 流批融合
- 雲原生
- 自適應
- Shuffle 3.0
一、Flink Shuffle 的演進
在整個 Shuffle 的演進過程中,其實並沒有明確提出過所謂 Shuffle 1.0 和 2.0 的概念。但從它的技術發展經歷中,我們能把它分成如上圖所示的兩個階段。
在 Shuffle 1.0 階段,Shuffle 只具備基礎的資料傳輸能力,Flink 專案也處於相對年輕的階段。
在 Shuffle 2.0 階段,我們對 Shuffle 做了一系列最佳化。
- 在效能方面,我們對資料的序列化,底層網路的記憶體複製進行了最佳化,並針對 Batch 場景設計了 Sort-Based Blocking Shuffle,這種 Shuffle 方式可能對磁碟 IO 會更加友好。
- 在穩定性方面,我們引入了 Credit-Based 流控機制,這種機制會比原本依賴於 TCP 的反壓機制更具穩定性。此外,社群還引入了 Buffer-Debloating 機制,使其能夠在反壓的狀態下減少資料積壓對 checkpoint 的影響。
- 在流批一體方面,我們將 Shuffle 模組進行 Service 外掛化重構,讓第三方開發的 Shuffle 實現成為可能。除此之外,我們還為批場景中的 Remote Shuffle Service 技術鋪墊了道路。
綜上我們可以發現,不管是效能還是穩定性,都是 Flink 上大規模生產必備的能力,而流批一體是 Flink 社群過去發展的主要方向之一。從整個 Shuffle 2.0 階段,我們發現 Flink Shuffle 已經趨於成熟,在生產中表現優異。
說到 Shuffle 3.0 的時候,我們重點要關注哪些問題呢?或者說隨著時代的發展、技術的進步,對於 Shuffle 又提出了哪些新的挑戰呢?這裡我們也列出了三個關鍵詞:分別是流批融合、雲原生和自適應。接下來,也會逐一的去跟大家做一個展開的探討。
二、流批融合
“流批融合”與“流批一體”有什麼樣的聯絡和區別?
如上圖所示,左邊是 Flink 經典的流批一體架構。在這套架構中,Flink 提供了流批統一的 API 表達,然後使用統一的引擎也就是 Flink,進行流和批的資料處理。此外,我們通常會把實時任務與離線任務排程到同一個叢集進行混部,從而提升研發運維效率和資源利用率。
目前,Flink 流批一體架構主要體現在面向使用者的流批一體。如果看引擎的內部,我們會發現,一個 Flink 任務的流模式和批模式的區別非常明顯,而整套架構中也仍然存在離線和實時兩條資料鏈路。由此可見,流批一體主要是一個面向使用者的概念。
流批融合,所謂 Flink 流和批融合的能力,不僅僅是將流和批的技術放在一個引擎當中,我們希望能在引擎側打破流和批的技術邊界,既有流技術,又有批技術,同時服務不同的場景。
在流批融合方面,主要有如下兩個要點:
- 第一,在批處理場景下,Flink 作為以流式為核心的引擎,不但借鑑和學習了成熟的批技術經驗,還具備很多獨一無二的優勢。比如我們在流處理時,上下游任務同時執行,流式核心引擎能夠保證資料不落盤進行直接傳輸,從而降低 IO 開銷,提升效能。除此之外,在流處理上有基於 checkpoint 的容錯機制,它擁有更靈活、更精細的容錯能力。
- 第二,流式引擎具備批處理的能力之後,反過來也能夠更好地服務流處理場景。比如批作業資料通常需要排序,它在狀態訪問時具有更好的效能與效果。除此之外,批資料的中間資料會落盤,具有可重複消費的特點,這對容錯也有比較好的提升。
流批融合主要強調,打破流和批的邊界。從引擎側把所有技術放在一起使用,服務於不同的場景。不難看出流批融合的概念是端到端的事情,貫穿執行計劃最佳化、編譯、排程、執行、Shuffle、容錯等場景,都需要按照流批融合的概念進行改變和提升。
Hybrid Shuffle 是一種將流技術應用於批場景的技術。
目前,Flink Shuffle 主要有 Pipelined Shuffle 和 Blocking Shuffle。其中,流式 Pipelined Shuffle 的上下游任務是同時執行的,大幅縮短任務的執行時間。同時,其資料可以在任務間直接傳遞,不需要落盤。
但是目前 Pipelined Shuffle 在批場景下,仍處於生產不可用的狀態。因為它在上下游同時執行時,資源需求較高。如果同時存在多個任務,每個任務只能拿到一部分資源,很容易形成資源排程的死鎖。
批式 Blocking Shuffle 有更好的資源自適應能力。在極限情況下,我們可以用一個 slot 執行完所有任務。但是它的效能較慢,因為批任務按 stage 排程的方式執行,每個 stage 都需要等待長尾任務完成。其次,它的資料需要全部落盤,導致 IO 開銷較大。
由此可見,不管是流式 Shuffle 還是批式 Shuffle,它們在某種特定的情況下,都會出現資源碎片的現象,即雖然持有資源卻不能夠排程任務並執行,從而會造成資源浪費。
Hybrid Shuffle 是想將流式 Shuffle 跟批式 Shuffle 的特點結合在一起,讓使用者在寫資料時,既可以寫入記憶體透過記憶體直接進行消費,也可以在記憶體中存放不下這麼多資料、下游消費不夠及時的時候,將資料寫入到磁碟當中進行後期消費。透過自適應切換,在上游產出資料的過程中和完成後,下游可以隨時消費,從而徹底消除資源碎片的情況。
Hybrid Shuffle 在資源充足的情況下,上下游的所有任務可以同時執行,它的效能跟流式 Pipeline Shuffle 相同。在資源受限的條件下,Hybrid Shuffle 可以先讓上游執行,將資料落到磁碟之後,下游再進行消費。其資源的自適應性比 Blocking Shuffle 更好。
除此之外,Hybrid Shuffle 在記憶體跟磁碟之間進行切換,是一種動態的自適應切換,並不是靜態的一次性切換。我們在資料消費的過程中,可以隨時在記憶體寫滿的狀態下,切換到磁碟模式。當記憶體中的資料被消費,留出更多的空間後,它又可以切換回記憶體進行消費。
目前,Hybrid Shuffle 已經在 Flink 1.16 釋出。經過測試,Hybrid Shuffle 相比 Blocking,在資源受限的條件下,效能提升了 7.2%。如果在資源充足的情況下,Hybrid Shuffle 會比 Blocking 有更大幅度的效能提升。
接下來,在 Flink 1.17 時,我們會繼續對 Hybrid Shuffle 進行完善與最佳化。主要包括針對廣播資料的效能最佳化,以及對大規模生產中批處理的其他重要特性的相容。
Single Task Failover 單點重啟是將批技術應用於流場景的技術。Flink 在流式任務中,如果一個任務出現失敗,關聯的上下游任務都要進行全域性重啟,才能保證資料一致性,但是這種全域性重啟的成本較高,特別是一些大規模、複雜的作業。
單點 Failover 能夠做到當出現 Failover 時,只對當前失敗任務進行重啟。目前,我們支援三種一致性語義,分別是 Best-effort、At-least-once、Exactly-once。一致性的保障越強,相應的開銷就越高。其中,Best-effort 需要恢復任務狀態。為了解決這個問題,我們採用分散式區域性快照的方式,給每個任務做定時的區域性快照,避免全域性的同步開銷。在 At-least-once 語義下,我們需要對上游資料進行重放,避免資料丟失。在 Exactly-once 語義下,我們不僅需要對資料進行重放,下游還要對資料進行去重。
不管是重放輸入,還是去重輸出,都是在 Shuffle 層面完成。它們跟 Blocking Shuffle 的資料落盤半持久化、支援重複消費具有很高的相似性。所以在實踐中,我們是基於現有的批 Shuffle 能力,進行了擴充套件和二次開發。
目前,Single Task Failover 的工作,仍處於內部實踐階段,At-least-once 語義即將在阿里雲內部上線,Exactly-once 則還處於研發當中。
三、雲原生
Shuffle 3.0 在雲原生場景下的實踐。從 Flink 1.9 版本開始,我們就一直在建設 Flink 雲原生部署體系,包括 Native K8s 的部署模式、輕量化客戶端的 Application Mode、Native K8s HA 模式,以及 Reactive Scaling 的資源管理方式等等。
Flink 雲原生部署體系越來越完善。用於 Flink 流式任務的生產也相對比較成熟,並經過了大量的生產檢驗。但我們在執行批任務時,仍會遇到問題。
其中,最主要的問題是批的 Shuffle 資料儲存。在 Batch 任務中,我們需要對大量的中間資料進行落盤,這個時候就產生了資料存放在哪的問題。目前 Flink 有兩種主流的 Shuffle 模式,即 Internal Shuffle 和 Remote Shuffle。
Internal Shuffle 的資料直接寫在 TM,這裡有兩個問題。
- 第一,資源效率問題。在雲生或雲端計算場境下,資源的彈性伸縮能力是非常重要的。在 Flink 的 Internal Shuffle 中,當我們把資料寫在 TM 本地時,TM 無法及時釋放資源,限制了計算資源的彈性。
- 第二,磁碟成本問題。一個物理機的磁碟在容器化的場境下,我們無法精確的界定每個 TM 需要配置多少磁碟空間。如果配置空間較多,成本就較高,會造成資源浪費。如果配置空間不足,會影響資料處理的穩定性。
雖然雲盤擁有動態掛載,共享儲存空間等能力,但其成本相比磁碟較高,訪問速度也比本地訪問慢一些,同時動態掛載也比較費時。
綜上所述,Internal Shuffle 的問題主要是資源效率以及磁碟成本。
Remote Shuffle 的問題是資料傳輸開銷。原本 Shuffle 資料只需要在兩個 TM 之間進行傳輸,現在我們需要先從上游的 TM 傳輸給一個遠端系統,然後下游的 TM 再從遠端系統進行消費,這會讓傳輸的成本至少增加一倍。
此外,我們不但需要運維部署 Flink 叢集,還需要額外部署一套 Remote Shuffle Service 叢集,從部署運維上也會產生一部分成本開銷。
最後,Remote Shuffle Service 雖然能夠在一定程度上緩解磁碟空間和磁碟成本問題,因為它可以建立一個 Remote Shuffle Service,同時服務大量不同的 Flink 例項,可以起到削峰填谷的作用,但它並不能從根本上消除磁碟空間的問題。
所以目前 Internal Shuffle 和 Remote Shuffle 都沒有非常完善的解決方案,來解決 Flink 在雲原生場景下 Batch 資料的儲存問題。
大家在使用雲產品時,經常使用物件儲存。基於物件儲存的 Shuffle,擁有靈活的資源彈性,成本相對較低。但物件儲存往往是不可修改的,上游在寫資料的過程中,資料對下游不可見,一旦下游資料可見,上游則無法對資料進行修改或追加。除此之外,其效能相比本地磁碟或雲盤,仍有一定的差距。
因此在流處理場景下,基於物件儲存的 Shuffle 仍面臨一些挑戰。一方面,需要基於不可修改的物件儲存,實現邊讀邊寫的能力。另一方面,物件儲存很難滿足低延需求。雖然物件儲存很難獨立支撐 Shuffle 資料管理,但當本地磁碟不夠時,可以將物件儲存作為其他資料儲存方式的補充,從而實現效能和成本的均衡。
目前,基於物件儲存的 Shuffle,仍處在內部實踐階段,預計在 Flink 1.18 版本釋出。
四、自適應
自適應,在最新的 Flink 1.16 中,有四種不同的 Shuffle,分別是 Pipelined Shuffle、Hash Blocking Shuffle、Sort-Based Blocking、以及最新推出的 Hybrid Shuffle。未來,Flink 可能會引入 Single Task Failover、物件儲存 Shuffle、Merge-Based Shuffle 等等。除此之外,在第三方專案中,Flink Remote Shuffle 也是基於 Flink Shuffle 的介面實現。
大量不同的 Shuffle 實現同時存在,也帶來了一些問題。使用者不知道如何選擇 Shuffle 型別,使用起來比較困難。根據場景選擇適合的 Shuffle 型別,這需要使用者對 Shuffle 內部原理有深入的瞭解。選擇 Shuffle 型別之後,在實際生產中,使用者對 Shuffle 進行引數調優時,也面臨不同的 Shuffle 型別調優引數及原理均有所差異的問題。除此之外,由於有些使用者的場景比較豐富,可能需要同時使用多種 Shuffle 型別。這些 Shuffle 型別如何進行搭配?其複雜性給使用者使用帶來了困難。
在開發者維護方面,隨著出現了越來越多的 Shuffle,工作人員需去維護更多的程式碼,甚至重複開發。除此之外,Shuffle 內部的複雜度,開始向 Flink 全鏈路擴散,比如 SQL 編譯、排程執行等等。為專案的長期的維護,帶來了一定的影響。
為了解決上述問題,我們提出了三種提高自適應性的方法。
- 第一,複雜性反轉。讓 Shuffle 適配外部條件,並決定當前需要選擇哪一種 Shuffle 實現,降低操作的複雜性。
- 第二,減少外部資訊依賴。我們希望根據實際掌握的資訊,做出最好的決策。我們可以把非必要資訊,轉化為補充資訊,同時對能自動獲取的資訊儘量自動獲取,減少 Shuffle 與其他模組的資訊依賴。
- 第三,我們希望在執行過程中,根據使用環境的變化,Shuffle 能夠自動調整自己的行為,消除不同 Shuffle 型別之間的邊界,以適應執行時的動態變化。
五、Shuffle 3.0
最後介紹一下,基於上述關鍵詞,我們提出的 Flink Shuffle 3.0 架構設計。這套架構被稱為自適應的分層儲存架構。在這套架構中,我們將 Shuffle 上下游間的資料交換過程,抽象為上游將資料寫入某種儲存當中、下游再從該儲存中抽取需要查詢的資料的過程。
在分層自適應儲存架構中,包含一個寫端 Selector 和一個讀端 Selector,主要負責向不同的儲存介質寫資料和讀資料。在中間的儲存層,隱藏了內部實現細節,具有統一的抽象。
在動態自適應方面,寫端按照優先順序,進行儲存層的資料寫入。如果遇到空間不足等問題,儲存層會反饋當前無法接收資料,然後繼續寫下一個優先順序的儲存層。在讀端,我們按照優先順序的順序,依次去查詢想要的資料。透過分層儲存加動態自適應的方式,我們將多種儲存層的介質,進行融合和互補,滿足我們在不同情況下的需求。
在儲存層規劃方面,Local TM 層主要有記憶體跟磁碟。在 Remote TM 層,使用者把資料寫到第三方 TM 的記憶體跟磁碟中,進行管理。此外還有遠端儲存介質層。
目前,我們在 Shuffle 3.0 自適應儲存架構的探索中,遇到了如下關鍵技術問題。
在資料分組方面,不同位置存放的資料分組方式不同,決定了資料索引結構和檔案儲存格式的差異。
在資料管理粒度方面,採用較大粒度在儲存層之間切換,降低切換頻率和查詢代價,不同儲存層內適合不同粒度。在儲存層內部,記憶體儲存比較適用較小的粒度,它對實時可見性的要求較高,管理資料的成本較低。而對於像物件儲存這樣的遠端儲存服務,我們會更關注如何減少檔案數量,傾向於相對較大的資料管理粒度。
在資料索引方面,資料存放的位置決定了適用不同的索引方式。比如本地 TM 和遠端 TM 上,記憶體索引的方式查詢效能更好。由於物件儲存缺乏外部的服務程式,對資料進行管理。所以我們基於檔案命名的方式,對檔案進行簡單的 list 操作,根據檔名判斷當前想要的資料,是否在檔案當中。
目前,Shuffle 3.0 仍處在探索階段。未來,在 Flink 1.18 時,社群會推出第一個版本的分層自適應架構儲存,包含本地 TM 記憶體、磁碟的儲存層,支援遠端物件儲存能力。後續我們會逐步增加流處理、Single Task Failover、遠端 TM 的記憶體+磁碟等能力。
更多內容
活動推薦
阿里雲基於 Apache Flink 構建的企業級產品-實時計算Flink版現開啟活動:
99 元試用 實時計算Flink版(包年包月、10CU)即有機會獲得 Flink 獨家定製衛衣;另包 3 個月及以上還有 85 折優惠!
瞭解活動詳情:https://www.aliyun.com/produc...