Flink Exactly-once 實現原理解析

大資料技術派發表於2021-12-12

關注公眾號:大資料技術派,回覆"資料",領取1024G資料。

這一課時我們將講解 Flink “精確一次”的語義實現原理,同時這也是面試的必考點。

Flink 的“精確一次”處理語義是,Flink 提供了一個強大的語義保證,也就是說在任何情況下都能保證資料對應用產生的效果只有一次,不會多也不會少。

那麼 Flink 是如何實現“端到端的精確一次處理”語義的呢?

背景

通常情況下,流式計算系統都會為使用者提供指定資料處理的可靠模式功能,用來表明在實際生產執行中會對資料處理做哪些保障。一般來說,流處理引擎通常為使用者的應用程式提供三種資料處理語義:最多一次、至少一次和精確一次。

最多一次(At-most-Once):這種語義理解起來很簡單,使用者的資料只會被處理一次,不管成功還是失敗,不會重試也不會重發。

至少一次(At-least-Once):這種語義下,系統會保證資料或事件至少被處理一次。如果中間發生錯誤或者丟失,那麼會從源頭重新傳送一條然後進入處理系統,所以同一個事件或者訊息會被處理多次。

精確一次(Exactly-Once):表示每一條資料只會被精確地處理一次,不多也不少。

Exactly-Once 是 Flink、Spark 等流處理系統的核心特性之一,這種語義會保證每一條訊息只被流處理系統處理一次。“精確一次” 語義是 Flink 1.4.0 版本引入的一個重要特性,而且,Flink 號稱支援“端到端的精確一次”語義。

在這裡我們解釋一下“端到端(End to End)的精確一次”,它指的是 Flink 應用從 Source 端開始到 Sink 端結束,資料必須經過的起始點和結束點。Flink 自身是無法保證外部系統“精確一次”語義的,所以 Flink 若要實現所謂“端到端(End to End)的精確一次”的要求,那麼外部系統必須支援“精確一次”語義;然後藉助 Flink 提供的分散式快照和兩階段提交才能實現。

分散式快照機制

我們在之前的課程中講解過 Flink 的容錯機制,Flink 提供了失敗恢復的容錯機制,而這個容錯機制的核心就是持續建立分散式資料流的快照來實現。

同 Spark 相比,Spark 僅僅是針對 Driver 的故障恢復 Checkpoint。而 Flink 的快照可以到運算元級別,並且對全域性資料也可以做快照。Flink 的分散式快照受到 Chandy-Lamport 分散式快照演算法啟發,同時進行了量身定做,有興趣的同學可以搜一下。

Barrier

Flink 分散式快照的核心元素之一是 Barrier(資料柵欄),我們也可以把 Barrier 簡單地理解成一個標記,該標記是嚴格有序的,並且隨著資料流往下流動。每個 Barrier 都帶有自己的 ID,Barrier 極其輕量,並不會干擾正常的資料處理。

image.png

如上圖所示,假如我們有一個從左向右流動的資料流,Flink 會依次生成 snapshot 1、 snapshot 2、snapshot 3……Flink 中有一個專門的“協調者”負責收集每個 snapshot 的位置資訊,這個“協調者”也是高可用的。

Barrier 會隨著正常資料繼續往下流動,每當遇到一個運算元,運算元會插入一個標識,這個標識的插入時間是上游所有的輸入流都接收到 snapshot n。與此同時,當我們的 sink 運算元接收到所有上游流傳送的 Barrier 時,那麼就表明這一批資料處理完畢,Flink 會向“協調者”傳送確認訊息,表明當前的 snapshot n 完成了。當所有的 sink 運算元都確認這批資料成功處理後,那麼本次的 snapshot 被標識為完成。

這裡就會有一個問題,因為 Flink 執行在分散式環境中,一個 operator 的上游會有很多流,每個流的 barrier n 到達的時間不一致怎麼辦?這裡 Flink 採取的措施是:快流等慢流。

image (1).png

拿上圖的 barrier n 來說,其中一個流到的早,其他的流到的比較晚。當第一個 barrier n到來後,當前的 operator 會繼續等待其他流的 barrier n。直到所有的barrier n 到來後,operator 才會把所有的資料向下傳送。

非同步和增量

按照上面我們介紹的機制,每次在把快照儲存到我們的狀態後端時,如果是同步進行就會阻塞正常任務,從而引入延遲。因此 Flink 在做快照儲存時,可採用非同步方式。

此外,由於 checkpoint 是一個全域性狀態,使用者儲存的狀態可能非常大,多數達 G 或者 T 級別。在這種情況下,checkpoint 的建立會非常慢,而且執行時佔用的資源也比較多,因此 Flink 提出了增量快照的概念。也就是說,每次都是進行的全量 checkpoint,是基於上次進行更新的。

兩階段提交

上面我們講解了基於 checkpoint 的快照操作,快照機制能夠保證作業出現 fail-over 後可以從最新的快照進行恢復,即分散式快照機制可以保證 Flink 系統內部的“精確一次”處理。但是我們在實際生產系統中,Flink 會對接各種各樣的外部系統,比如 Kafka、HDFS 等,一旦 Flink 作業出現失敗,作業會重新消費舊資料,這時候就會出現重新消費的情況,也就是重複消費。

針對這種情況,Flink 1.4 版本引入了一個很重要的功能:兩階段提交,也就是 TwoPhaseCommitSinkFunction。兩階段搭配特定的 source 和 sink(特別是 0.11 版本 Kafka)使得“精確一次處理語義”成為可能。

在 Flink 中兩階段提交的實現方法被封裝到了 TwoPhaseCommitSinkFunction 這個抽象類中,我們只需要實現其中的beginTransaction、preCommit、commit、abort 四個方法就可以實現“精確一次”的處理語義,實現的方式我們可以在官網中查到:

  1. beginTransaction,在開啟事務之前,我們在目標檔案系統的臨時目錄中建立一個臨時檔案,後面在處理資料時將資料寫入此檔案;
  2. preCommit,在預提交階段,刷寫(flush)檔案,然後關閉檔案,之後就不能寫入到檔案了,我們還將為屬於下一個檢查點的任何後續寫入啟動新事務;
  3. commit,在提交階段,我們將預提交的檔案原子性移動到真正的目標目錄中,請注意,這會增加輸出資料可見性的延遲;
    abort,在中止階段,我們刪除臨時檔案。

image (2).png

如上圖所示,我們用 Kafka-Flink-Kafka 這個案例來介紹一下實現“端到端精確一次”語義的過程,整個過程包括:

  • 從 Kafka 讀取資料
  • 視窗聚合操作
  • 將資料寫回 Kafka

整個過程可以總結為下面四個階段:

  1. 一旦 Flink 開始做 checkpoint 操作,那麼就會進入 pre-commit 階段,同時 Flink JobManager 會將檢查點 Barrier 注入資料流中 ;
  2. 當所有的 barrier 在運算元中成功進行一遍傳遞,並完成快照後,則 pre-commit 階段完成;
  3. 等所有的運算元完成“預提交”,就會發起一個“提交”動作,但是任何一個“預提交”失敗都會導致 Flink 回滾到最近的 checkpoint;
  4. pre-commit 完成,必須要確保 commit 也要成功,上圖中的 Sink Operators 和 Kafka Sink 會共同來保證。

現狀

目前 Flink 支援的精確一次 Source 列表如下表所示,你可以使用對應的 connector 來實現對應的語義要求:

資料來源 語義保證 備註
Apache Kafka exactly once 需要對應的 Kafka 版本
AWS Kinesis Streams exactly once
RabbitMQ at most once (v 0.10) / exactly once (v 1.0)
Twitter Streaming API at most once
Collections exactly once
Files exactly once
Sockets at most once

如果你需要實現真正的“端到端精確一次語義”,則需要 sink 的配合。目前 Flink 支援的列表如下表所示:

寫入目標 語義保證 備註
HDFS rolling sink exactly once 依賴 Hadoop 版本
Elasticsearch at least once
Kafka producer at least once / exactly once 需要 Kafka 0.11 及以上
Cassandra sink at least once / exactly once 冪等更新
AWS Kinesis Streams at least once
File sinks at least once
Socket sinks at least once
Standard output at least once
Redis sink at least once

總結

由於強大的非同步快照機制和兩階段提交,Flink 實現了“端到端的精確一次語義”,在特定的業務場景下十分重要,我們在進行業務開發需要語義保證時,要十分熟悉目前 Flink 支援的語義特性。

這一課時的內容較為晦澀,建議你從原始碼中去看一下具體的實現。

猜你喜歡
Spark SQL知識點與實戰
Hive計算最大連續登陸天數
Hadoop 資料遷移用法詳解
數倉建模分層理論
數倉建模—寬表的設計

相關文章