Flink筆記04——一文了解State管理和恢復

前言

  • State 一般指一個具體的 Task/Operator 的狀態,State 數據默認保存在 Java 的堆內存中。
  • CheckPoint(可以理解爲 CheckPoint 是把 State 數據持久化存儲了)則表示了一個 Flink Job 在一個特定時刻的一份全局狀態快照,即包含了所有 Task/Operator 的狀態。

常用State

Flink 有兩種常見的 State 類型,分別是:

  • keyed State(鍵控狀態)
  • Operator State(算子狀態)

Keyed State(鍵控狀態)

Keyed State:基於 KeyedStream 上的狀態,這個狀態是跟特定的 Key 綁 定的。KeyedStream 流上的每一個 Key,都對應一個 State。Flink 針對 Keyed State 提供了 以下可以保存 State 的數據結構:

  • ValueState: 保存一個可以更新和檢索的值(如上所述,每個值都對應到當前的輸 入數據的 key,因此算子接收到的每個 key 都可能對應一個值)。 這個值可以通過 update(T) 進行更新,通過 T value() 進行檢索。
  • ListState: 保存一個元素的列表。可以往這個列表中追加數據,並在當前的列表上 進行檢索。可以通過 add(T) 或者 addAll(List) 進行添加元素,通過 Iterable get() 獲得整個列表。還可以通過 update(List) 覆蓋當前的列表。
  • ReducingState: 保存一個單值,表示添加到狀態的所有值的聚合。接口與 ListState 類似,但使用 add(T) 增加元素,會使用提供的 ReduceFunction 進行聚合。
  • AggregatingState<IN, OUT>: 保留一個單值,表示添加到狀態的所有值的聚合。和 ReducingState 相反的是, 聚合類型可能與 添加到狀態的元素的類型不同。 接口與 ListState 類似,但使用 add(IN) 添加的元素會用指定的 AggregateFunction 進行聚 合。
  • FoldingState<T, ACC>: 保留一個單值,表示添加到狀態的所有值的聚合。 與 ReducingState 相反,聚合類型可能與添加到狀態的元素類型不同。接口與 ListState 類似,但使用 add(T)添加的元素會用指定的 FoldFunction 摺疊成聚合值。
  • MapState<UK, UV>: 維護了一個映射列表。 你可以添加鍵值對到狀態中,也可以獲得 反映當前所有映射的迭代器。使用 put(UK,UV) 或者 putAll(Map<UK,UV>) 添加映射。 使用 get(UK) 檢索特定 key。使用 entries(),keys() 和 values() 分別檢索映射、 鍵和值的可迭代視圖。

demo: 計算每個手機的呼叫時間間隔時間

package com.flink.primary.State

import com.flink.primary.DataSource.StationLog
import org.apache.flink.api.common.functions.RichFlatMapFunction
import org.apache.flink.api.common.state.{ValueState, ValueStateDescriptor}
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.util.Collector

/**
  * 第一種方法的實現
  * 統計每個手機呼叫時間間隔
  * @author xjh 2020.4.7
  */
  
//定義一個外部類
case class StationLog(sid: String, callOut: String, callIn: String, callType: String, callTime: Long, duration: Long)

object TestKeyedState1 {
  def main(args: Array[String]): Unit = {
    val environment = StreamExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.streaming.api.scala._
    //讀取數據源
    val filePath=getClass.getResource("/station.log").getPath
    val stream = environment.readTextFile(filePath)
      .map(line => {
        val strings = line.split(",")
        new StationLog(strings(0).trim, strings(1).trim, strings(2).trim, strings(3).trim, strings(4).trim.toLong, strings(5).trim.toLong)
      })

    stream.keyBy(_.callOut) //分組
      .flatMap(new CallIntervalFunction) //flatmap中傳一個富函數類
      .print()

    environment.execute()
  }

  //自定義一個類,繼承富函數類(富函數類可在上下文環境中管理狀態)
  class CallIntervalFunction extends RichFlatMapFunction[StationLog, (String, Long)] { //輸出的一個二元組(String,Long) 手機號碼,時間間隔
    private var preCallTimeState: ValueState[Long] = _

    override def open(parameters: Configuration): Unit = {
      preCallTimeState = getRuntimeContext.getState(new ValueStateDescriptor[Long]("pre", classOf[Long]))
    }

    override def flatMap(value: StationLog, out: Collector[(String, Long)]): Unit = {
      //從狀態中取得前一次的時間
      var preCallTime = preCallTimeState.value()
      if (preCallTime == null || preCallTime == 0) {
        //狀態中沒有,當前爲第一次呼叫
        preCallTimeState.update(value.callTime)
      } else {
        //狀態中有數據,則要計算時間間隔
        var interval = Math.abs(value.callTime - preCallTime)
        out.collect((value.callOut,interval)) //返回一個二元組
      }
    }
  }
}

Operator State(算子狀態)

Operator State 與 Key 無關,而是與 Operator 綁定,整個 Operator 只對應一個 State。比如:Flink 中的 Kafka Connector 就使用了 Operator State,它會在每個 Connector實例中,保存該實例消費Topic 的所有(partition, offset)映射。

CheckPoint

當程序出現問題需要恢復 State 數據的時候,只有程序提供支持纔可以實現 State 的容錯。State 的容錯需要依靠 CheckPoint 機制,這樣纔可以保證 Exactly-once 這種語義,但是注意,它只能保證 Flink系統內的 Exactly-once,比如 Flink 內置支持的算子。針對 Source和Sink 組件,如果想要保證Exactly-once的話,則這些組件本身應支持這種語義

1.CheckPoint 原理
Flink 中基於異步輕量級的分佈式快照技術提供了 Checkpoints 容錯機制,分佈式快照可以將同一時間點Task/Operator 的狀態數據全局統一快照處理,包括前面提到的 Keyed State 和 Operator State。Flink 會在輸入的數據集上間隔性地生成 checkpoint barrier,通過柵欄(barrier)將間隔時間段內的數據劃分到相應的 checkpoint 中。如下圖:
在這裏插入圖片描述
2.CheckPoint參數和設置
默認情況下 Flink 不開啓檢查點的,用戶需要在程序中通過調用方法配置和開啓檢查 點,另外還可以調整其他相關參數:

  • Checkpoint 開啓和時間間隔指定: 開啓檢查點並且指定檢查點時間間隔爲 1000ms,根據實際情況自行選擇,如果狀態比較大,則建議適當增加該值。
    streamEnv.enableCheckpointing(1000);
  • exactly-ance 和 at-least-once 語義選擇: 選擇 exactly-once 語義保證整個應用內端到端的數據一致性,這種情況比較適合於數據要求比較高,不允許出現丟數據或者數據重複,與此同時,Flink 的性能也相對較弱,而 at-least-once 語義更適合於時廷和吞吐量要求非常高但對數據的一致性要求不高的場景。 如 下 通 過 setCheckpointingMode() 方 法 來 設 定 語 義 模 式 , 默 認 情 況 下 使 用 的 是 exactly-once 模式。
    streamEnv.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACT LY_ONCE);
    //或者 
    streamEnv.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.AT_LE AST_ONCE) 
    
  • Checkpoint 超時時間: 超時時間指定了每次 Checkpoint 執行過程中的上限時間範圍,一旦 Checkpoint 執行時間超過該閾值,Flink 將會中斷Checkpoint 過程,並按照超時處理。該指標可以通過 setCheckpointTimeout 方法設定,默認爲 10 分鐘。 streamEnv.getCheckpointConfig.setCheckpointTimeout(50000)
  • 檢查點之間最小時間間隔: 該參數主要目的是設定兩個 Checkpoint 之間的最小時間間隔,防止出現例如狀態數據 過大而導致 Checkpoint 執行時間過長,從而導致 Checkpoint 積壓過多,最終 Flink 應用密 集地觸發 Checkpoint 操作,會佔用了大量計算資源而影響到整個應用的性能。streamEnv.getCheckpointConfig.setMinPauseBetweenCheckpoints(600)
  • 最大並行執行的檢查點數量: 通過 setMaxConcurrentCheckpoints()方法設定能夠最大同時執行的 Checkpoint 數量。 在默認情況下只有一個檢查點可以運行,根據用戶指定的數量可以同時觸發多個 Checkpoint,進而提升 Checkpoint 整體的效率。streamEnv.getCheckpointConfig.setMaxConcurrentCheckpoints(1)
  • 是否刪除 Checkpoint 中保存的數據: 設置爲 RETAIN_ON_CANCELLATION:表示一旦 Flink 處理程序被 cancel 後,會保留 CheckPoint 數據,以便根據實際需要恢復到指定的 CheckPoint。 設置爲 DELETE_ON_CANCELLATION:表示一旦 Flink 處理程序被 cancel 後,會刪除 CheckPoint 數據,只有 Job 執行失敗的時候纔會保存 CheckPoint.
    //刪除 
    streamEnv.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedCheckp ointCleanup.DELETE_ON_CANCELLATION) 
    //保留
    streamEnv.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedCheckp ointCleanup.RETAIN_ON_CANCELLATION)
    
  • TolerableCheckpointFailureNumber: 設置可以容忍的檢查的失敗數,超過這個數量則系統自動關閉和停止任務。 streamEnv.getCheckpointConfig.setTolerableCheckpointFailureNumber(1)
    注意這些參數設置,不用花時間去記,熟能生巧。

(2020.6.17更新)

Checkpoint 執行機制詳解

這一小節轉載自Apache Flink 進階教程(三):Checkpoint 的應用實踐 作者 | 唐雲(茶幹)
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

保存機制 StateBackend(狀態後端)

默認情況下,State 會保存在 TaskManager 的內存中,CheckPoint 會存儲在 JobManager 的內存中。State 和 CheckPoint 的存儲位置取決於 StateBackend 的配置。Flink 一共提供 了 3 種 StateBackend 。 包 括 基 於 內 存 的 MemoryStateBackend 、 基 於 文 件 系 統 的 FsStateBackend,以及基於 RockDB 作爲存儲介質的 RocksDBState-Backend

  1. MemoryStateBackend
    基於內存的狀態管理具有非常快速和高效的特點,但也具有非常多的限制,最主要的是內存的容量限制,一旦存儲的狀態數據過多就會導致系統內存溢出等問題,從而影響整個應用的正常運行。同時如果機器出現問題,整個主機內存中的狀態數據都會丟失,進而無法恢復任務中的狀態數據。因此從數據安全的角度建議用戶儘可能地避免在生產環境中使用 MemoryStateBackend。 streamEnv.setStateBackend(new MemoryStateBackend(10*1024*1024))

  2. FsStateBackend
    和 MemoryStateBackend 有所不同,FsStateBackend 是基於文件系統的一種狀態管理器, 這裏的文件系統可以是本地文件系統,也可以是 HDFS 分佈式文件系統。FsStateBackend 更 適合任務狀態非常大的情況,例如應用中含有時間範圍非常長的窗口計算,或 Key/value State 狀態數據量非常大的場景。

     streamEnv.setStateBackend(new FsStateBackend("hdfs://hadoop101:9000/checkpoint/cp1")) 
    
  3. RocksDBStateBackend RocksDBStateBackend 是 Flink 中內置的第三方狀態管理器,和前面的狀態管理器不同, RocksDBStateBackend 需要單獨引入相關的依賴包到工程中。

    <dependency> 
    	<groupId>org.apache.flink</groupId> 
    	<artifactId>flink-statebackend-rocksdb_2.11</artifactId> 
    	<version>1.9.1</version> 
    </dependency> 
    

    RocksDBStateBackend 採用異步的方式進行狀態數據的 Snapshot,任務中的狀態數據首 先被寫入本地 RockDB 中,這樣在 RockDB 僅會存儲正在進行計算的熱數據,而需要進行 CheckPoint 的時候,會把本地的數據直接複製到遠端的 FileSystem 中。
    與 FsStateBackend 相比,RocksDBStateBackend 在性能上要比 FsStateBackend 高一些,主要是因爲藉助於 RocksDB 在本地存儲了最新熱數據,然後通過異步的方式再同步到文件系 統中,但 RocksDBStateBackend 和 MemoryStateBackend 相比性能就會較弱一些。RocksDB 克服了 State 受內存限制的缺點,同時又能夠持久化到遠端文件系統中,推薦在生產中使用。

    streamEnv.setStateBackend( new RocksDBStateBackend ("hdfs://hadoop101:9000/checkpoint/cp2"))
    
  4. 全局配置 StateBackend
    上述都是單 job 配置狀態後端,我們也可以全局配置狀態後端,這需要修改 flink-conf.yaml 配置文件: state.backend: filesystem
    其中: filesystem 表示使用 FsStateBackend;jobmanager 表示使用 MemoryStateBackend; rocksdb 表示使用 RocksDBStateBackend。

    例如我們設置:state.checkpoints.dir: hdfs://hadoop101:9000/checkpoints
    默認情況下,如果設置了 CheckPoint 選項,則 Flink 只保留最近成功生成的 1 個 CheckPoint,而當 Flink 程序失敗時,可以通過最近的 CheckPoint 來進行恢復。但是,如 果希望保留多個 CheckPoint,並能夠根據實際需要選擇其中一個進行恢復,就會更加靈活。 添加如下配置,指定最多可以保存的 CheckPoint 的個數。 state.checkpoints.num-retained: 2

SavePoint

Savepoints 是檢查點的一種特殊實現,底層實現其實也是使用 Checkpoints 的機制。 Savepoints 是用戶以手工命令的方式觸發 Checkpoint,並將結果持久化到指定的存儲路徑 中,其主要目的是幫助用戶在升級和維護集羣過程中保存系統中的狀態數據,避免因爲停機 運維或者升級應用等正常終止應用的操作而導致系統無法恢復到原有的計算狀態的情況,從 而無法實現從端到端的 Excatly-Once 語義保證。

  1. 配置 Savepoints 的存儲路徑 在 flink-conf.yaml 中配置 SavePoint 存儲的位置,設置後,如果要創建指定 Job 的 SavePoint,可以不用在手動執行命令時指定 SavePoint 的位置。
    state.savepoints.dir: hdfs:/hadoop101:9000/savepoints

  2. 在代碼中設置算子 ID 爲了能夠在作業的不同版本之間以及 Flink 的不同版本之間順利升級,強烈推薦程序員 通過手動給算子賦予 ID,這些 ID 將用於確定每一個算子的狀態範圍。如果不手動給各算子 指定 ID,則會由 Flink 自動給每個算子生成一個 ID。而這些自動生成的 ID 依賴於程序的結 構,並且對代碼的更改是很敏感的。因此,強烈建議用戶手動設置 ID。

    object TestSavepoints { 
    	def main(args: Array[String]): Unit = { 
    	//初始化Flink的Streaming(流計算)上下文執行環境 
    	val streamEnv: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment 
    	import org.apache.flink.streaming.api.scala._ 
    	//讀取數據得到DataStream 
    		val stream: DataStream[String] = streamEnv.socketTextStream("hadoop101",8888) .uid("mySource-001") 
    		stream.flatMap(_.split(" ")) 
    		.uid("flatMap-001") 
    		.map((_,1)) 
    		.uid("map-001")
    		 .keyBy(0) 
    		 .sum(1) 
    		 .uid("sum-001") 
    		 .print() 
    	streamEnv.execute("wc") //啓動流計算 
    	} 
    }
    
  3. 觸發 SavePoint

    //先啓動Job 
    [root@hadoop101 bin]# ./flink run -c com.bjsxt.flink.state.TestSavepoints -d /home/Flink-Demo-1.0-SNAPSHOT.jar 
    //再取消Job ,觸發SavePoint 
    [root@hadoop101 bin]# ./flink savepoint 6ecb8cfda5a5200016ca6b01260b94ce 
    [root@hadoop101 bin]# ./flink cancel 6ecb8cfda5a5200016ca6b01260b94ce
    

在這裏插入圖片描述在這裏插入圖片描述
4) 從 SavePoint 啓動 Job

[root@hadoop101 bin]# ./flink run -s hdfs://hadoop101:9000/savepoints/savepoint-6ecb8c-e56ccb88576a -c com.bjsxt.flink.state.TestSavepoints -d /home/Flink-Demo-1.0-SNAPSHOT.jar

也可以通過 Web UI 啓動 Job:
在這裏插入圖片描述

最後,一張圖簡述CheckPoint和Savepoint的區別:
在這裏插入圖片描述
參考資料:
1.尚學堂大數據技術Flink教案

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章