Flink 原理與實現:State

1. State

State 是流計算中非常重要的一個概念。首先,我們區分下有狀態的流計算和無狀態的流計算:

  • Stateless Stream Processing(無狀態的流計算):算子僅考慮當前的輸入,而無需進一步瞭解上下文或歷史的計算信息。
  • Stateful Stream Processing(有狀態的流計算):算子存儲的過去的計算信息,會影響將來的輸入處理。

舉個例子,現在我們假設有個數據結構如下:

case class WordCount(word: String, frequency: Long = -999L)

有以下兩個需求:

  1. 過濾掉出現頻次小於 1 的 word。
  2. 統計每個 word 出現的頻次。

對於【需求 1】,只用比較當前 word 的 frequency 是否大於 0,而不用考慮歷史的計算信息。然後將 frequency 是否大於 0 的 word 發送到下游 Sink 即可。這便是 Stateless Stream Processing

對於【需求2】,算子需要用當前 word 的 frequency 加上算子中存儲的歷史計算過的該 word 出現的 frequency。因此,這是 Stateful Stream Processing

其實,State 在數據處理中是比較常見的。比如,統計每天最高溫度時,需要存儲當天歷史的最高溫度;計算 PV、UV、DAU 等等都需要 State。

在流處理任務中,在事件之間持久化的 State,我們甚至可以將其視爲編程模型中的一等公民。鑑於此,我們需要額外的系統來管理流的 State,即使會導致些許延遲。

另外,由於流計算通常會處理無界數據,會使 State 無限制地增長。因此,爲了限制 State 的大小,一般 State 只維護事件的核心概要信息,例如 count、sum 和一些簡單的數據結構。

如你所見,實現 State 會遇到以下一些挑戰:

  1. State 的管理。系統需要高效管理 State,並且保證在併發操作中的正確性。
  2. State 的分配。在並行計算中,State 可以根據 Key 劃分或者保證每個 Partition 的 State 彼此相互獨立。
  3. State 的恢復。State 需要能夠正確恢復,並且保證恢復後的計算結果仍然正確。

2. State Management

業界很多實時計算引擎都包含了 Stateful Stream Processing 特徵,比如 Google Dataflow、Spark(Structure)Streaming、Kafka Streams 都對提供了對內置 State 的支持,Flink 也不例外。這一節主要討論 Flink 支持的 State 類型,State 是怎麼存儲和維護的,以及怎麼在分佈式系統中擴展的。

大體上,任務維護的所有用於計算函數結果的數據均屬於任務狀態。你甚至可以將 State 想象成任務邏輯訪問的局部變量。圖1 展示了任務和 State 之間的交互過程。
圖1 A stateful stream processing task
在上圖中,一個任務接收輸入數據,當處理輸入數據時,會讀取 State,並且根據輸入數據和 State 計算結果,同時會更新 State。這個例子中對 State 的讀寫都很直觀。但是,高效可靠的管理 State 卻是充滿挑戰的。這包括操作大量數據、內存計算、失敗時 State 不丟失、State 的一致性,這些都是 Flink 需要考慮的,從而可以讓開發人員只需要聚焦於業務邏輯的處理上。

在 Flink 中,State 是一種特殊的算子。在使用 State 之前,算子需要註冊 State。目前,Flink 包括兩種 State:

  1. Operator State
  2. Keyed State

2.1 Operator State

Operator State 的作用範圍僅限於並行的算子。這意味着同一並行任務處理的所有記錄都可以訪問同一 State。Operator State 無法被相同或不同算子的其他任務訪問。圖2 展示了任務是如何訪問 Operator State 的。
圖2 Tasks with operator state
Operator State 有以下 3 種類型:

  1. List State
  2. Union List State
  3. Broadcast State

2.2 Keyed State

Keyed State 維護和訪問根據算子的輸入流中定義的鍵對應的 State。Flink 爲每個鍵維護一個 State 實例,並將具有相同鍵的所有數據劃分到維護該鍵 State 的算子中。因此,相同鍵的所有數據可以訪問同一 State。圖3 展示任務是如何和 Keyed State 交互的。
圖3 Tasks with keyed state
你可以將 Keyed State 看作是一個 Key-Value 的 Map。Flink 提供了一下內置的 Keyed State:

  1. Value State
  2. List State
  3. Map State
  4. Reducing State
  5. Aggregating State

2.3 State Backends

由於 Stateful Stream Processing 需要爲每一條數據讀取和更新 State,因此,低延遲的訪問 State 是至關重要的。如何存儲、訪問和維護 State 的這個組件就是 State BackendState Backend 的職責有兩個:

  1. 管理本地 State;
  2. Checkpoint State 到遠程。

關於管理本地 State,Flink 提供了兩種 State Backend

  1. 內存存儲。這種 State Backend 通過把 State 對象放到 JVM Heap 的內存中,可以實現非常快的 State 訪問,但受限於內存的大小;
  2. RocksDB 存儲。這種 State Backend 會把 State 對象序列化之後,存到 RocksDB中,由 RocksDB 寫入到本地磁盤。這種方案雖然會使 State 訪問更慢些,但是卻可以支持更大的 State。

State Checkpointing 對於 Flink 這樣的分佈式系統而言,是非常重要的,因爲 State 僅在本地維護。由於 TaskManager 可能由於各種原因失敗,所以,State 的存儲必須考慮易失性。如此,遠程存儲必須是一個分佈式文件系統或者數據庫系統。但是,不同的 State Backend 在 Checkpoint 的方式上會有所不同。例如,採用 RocksDB State Backend 可以支持增量 Checkpoint,這樣可以減小 State 大小的瓶頸(這部分筆者會用專門的文章討論)。

2.4 Scaling Stateful Operators

Flink 作爲一款分佈式系統,無疑面對橫向擴展,算子也會面臨並行度的增減。調整 Stateful 算子的並行度是一個很大的挑戰,因爲這些算子的 State 需要 Repartition,以分配更多或更少的並行任務。Fink 對於不同的 State 類型,採用的 4 種不同的策略。

Scaling Keyed State

Keyed State 算子(operators with keyed state) 擴展的時候不是針對每個單獨的 Key 進行 Repartition。但是,爲了提高效率,Flink 將這些 Key 組織成 key-group。一個 key-group 是這些 Key 的一個分區,而 Flink 也是按照 key-group 去 Repartition 的。圖 4 展示了 Keyed State 算子 的擴展過程。
圖4 scaling an operator with keyed state out and in

Scaling Operator List State

Operator List State 算子(operators with operator list state) 是將 List 中的 State 實體都重新分配。換言之,所有並行算子的 List 中的 State 實體被收集起來,然後重新分佈到更多或更少的任務中去。如果 List 中的 State 實體數目少於新的並行算子,那麼有些任務將會填充空的 State。圖5 展示了 Operator List State 算子 的重分佈過程。
圖5 scaling an operator with operator list state out and in

Scaling Operator Union List State

Operator Union List State 算子(operator with operator union list state) 擴展采用廣播的方式,它將所有 List 的 State 實體廣播給每個任務,讓任務自己去裁決使用或廢棄哪些 State。圖6 表示 Operator Union List State 算子 的 Repartition 過程。
圖6 scaling an operator with operator union list state out and in

Scaling Operator Broadcast State

Operator Broadcast State 算子(operator with operator broadcast state) 採用複製給所有新的任務複製 State 的策略實現擴展。這樣做的原因是廣播 State 可以確保每個任務都有相同的 State。防止任務擴展過程中 State 丟失。Operator Broadcast State 算子 的擴展過程可以參考圖7。
圖7 scaling an operator with operator broadcast state out and in

3. 總結

Stream Processing 分爲 Stateless Stream ProcessingStateful Stream Processing 兩種。Stateful Stream Processing 是當前業界流程的流計算引擎的重要特徵之一,Flink 也不例外。Flink 支持 Operator StateKeyed State 兩大類 State,而且各自又分爲若干種 State。Flink 使用 State Backend 對 State 進行管理——存儲、訪問和維護,同時 State Backend 有多種,需要根據具體需求權衡選擇。Scaling State 具有很大的挑戰,Flink 採用了 4 種不同的策略,分別對不同類型的 State 進行 Scaling。

掃碼關注公衆號:冰山烈焰的黑板報
在這裏插入圖片描述

發佈了101 篇原創文章 · 獲贊 122 · 訪問量 44萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章