Flink之狀態管理與容錯機制

1 狀態管理的基本概念

1.1 什麼是狀態

1.1.1 無狀態的例子:消費延遲計算

消息隊列:

一個生產者持續寫入,多個消費組分別讀取,如何實時統計每個消費者落後多少條數據?

//輸入
{
	"timestamp": 1555516800,
	"offset":
	{
		"producer": 16,
		"consumer0": 10,
		"consumer1": 7,
		"consumer2": 12
	}
}
//輸出
{
	"timestamp": 1555516800,
	"lag":
	{
		"consumer0": 5,
		"consumer1": 8,
		"consumer2": 3
	}
}
  • 單條輸入包含所需的所有信息
  • 相同輸入可以得到相同輸出

1.1.2 有狀態計算的例子:訪問量統計

Nginx訪問日誌,每個請求訪問一個URL地址,如何實時統計每個地址總共被訪問了多少次?

輸入輸出:

{
	"@timestamp": "18/Apr/2019:00:00:00",
	"remote_addr": "127.0.0.1",
	"request": "GET",
	"url": "/api/a"
}
{
	"url": "/api/a",
	"count": 1	
}

{
	"@timestamp": "18/Apr/2019:00:00:00",
	"remote_addr": "127.0.0.1",
	"request": "POST",
	"url": "/api/b"
}
{
	"url": "/api/b",
	"count": 1	
}

{
	"@timestamp": "18/Apr/2019:00:00:00",
	"remote_addr": "127.0.0.1",
	"request": "GET",
	"url": "/api/a"
}
{
	"url": "/api/a",
	"count": 2
}
  • 單挑輸入僅包含所需的部分信息:當前請求信息
  • 相同輸入可能得到不同輸出:當前請求之前的累計訪問量

1.1.3 需要使用狀態的場景

  • 去重:記錄所有的主鍵
  • 窗口計算:已進入的未觸發的數據
  • 機器學習|深度學習:訓練的模型及參數
  • 訪問歷史數據:需要與昨日進行對比

1.2 狀態管理

最直接的方式:內存

  • 存儲容量限制
  • 備份與恢復
  • 橫向擴展

對流式作業的要求

  • 7*24小時運行,高可靠
  • 數據不丟不重,恰好計算一次
  • 數據實時產出,不延遲

理想的狀態管理

  • 易用:豐富的數據結構;多樣的組織形式;簡潔的擴展接口
  • 高效:讀寫快、恢復快;可以方便地橫向擴展;備份不影響處理性能
  • 可靠:持久化;不丟不重;具備容錯能力

2 狀態的類型和使用方式

2.1 Managed State & Raw State

			Managed State		Raw State
狀態管理方式            Flink Runtime管理	用戶自己管理
			- 自動存儲,自動回覆	- 需要自己序列化
			- 內存管理上有優化
狀態數據結構	        已知的數據結構		字節數組
			- value,list,map等	- byte[]
推薦使用場景		大多數情況下均可使用	自定義Operator時可使用

2.2 Keyed State & Operator State

Keyed State

  • 只能用在KeyedSteam上的算子中
  • 每個Key對應一個State
    • 一個Operator實例處理多個Key,訪問響應的多個State
  • 併發改變,State隨着Key在實例間遷移
  • 通過RuntimeContext訪問
    • Rich Function
  • 支持的數據結構
    • ValueState、ListState、ReducingState、AggeragatingState、MapState

Operator State

  • 可以用於所有算子
    • 常用於source,例如FlinkKafkaConsumer
  • 一個Operator實例對應一個State
  • 併發改變時有多重重新分配方式可選
    • 均勻分配
    • 合併後每個得到全量
  • 實現CheckpointedFunction或ListCheckpointed接口
  • 支持的數據結構
    • ListState

Keyed State使用示例

			狀態數據類型	訪問接口
ValueState		單個值		update(T)/T value()
MapState		Map		put(UK key,UV value)/putAll(Map<UK,UV> map)
					remove(UK key)
					boolean contains(UK key)/UV get(UK key)
					iterable<Map,Entry> entries()/iterator<Map,Entry> iterator()
					iterable<UK> keys()/iterable<UV> values()
ListState		List		add(T)/addAll(List<T>)
					update(List<T>)/iterable<T> get()
ReducingState		單個值		add(T)/addAll(List<T>)
					update(List<T>)/T get()
AggregatingState	單個值		add(IN)/OUT get()

2.3 Keyed State使用示例

簡單狀態機代碼示例:

https://github.com/apache/flink/blob/master/flink-examples/flink-examples-streaming/src/main/java/org/apache/flink/streaming/examples/statemachine/StateMachineExample.java​github.com

 

DataStream<Event> events = env.addSource(source);

DataStream<Alert> alerts = events
		// partition on the address to make sure equal addresses
		// end up in the same state machine flatMap function
		.keyBy(Event::sourceAddress)

		// the function that evaluates the state machine over the sequence of events
		.flatMap(new StateMachineMapper());
		
		
@SuppressWarnings("serial")
static class StateMachineMapper extends RichFlatMapFunction<Event, Alert> {

	/** The state for the current key. */
	private ValueState<State> currentState;

	@Override
	public void open(Configuration conf) {
		// get access to the state object
		currentState = getRuntimeContext().getState(
					new ValueStateDescriptor<>("state", State.class));
	}

	@Override
	public void flatMap(Event evt, Collector<Alert> out) throws Exception {
		// get the current state for the key (source address)
		// if no state exists, yet, the state must be the state machine's initial state
		State state = currentState.value();
		if (state == null) {
			state = State.Initial;
		}

		// ask the state machine what state we should go to based on the given event
		State nextState = state.transition(evt.type());

		if (nextState == State.InvalidTransition) {
			// the current event resulted in an invalid transition
			// raise an alert!
			out.collect(new Alert(evt.sourceAddress(), state, evt.type()));
		}
		else if (nextState.isTerminal()) {
			// we reached a terminal state, clean up the current state
			currentState.clear();
		}
		else {
			// remember the new state
			currentState.update(nextState);
		}
	}
}

3 容錯機制與故障恢復

3.1 狀態如何保存及恢復

Checkpoint

  • 定時製作分佈式快照、對程序中的狀態進行備份
  • 發生故障時
    • 將整個作業的所有Task都回滾到最後一次成功Checkpoint中的狀態,然後從那個店開始繼續處理
  • 必要條件
    • 數據源支持重發
  • 一致性語義
    • 恰好一次
    • 至少一次
//Checkpoint設置
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.enableCheckpointing(1000);
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);
env.getCheckpointConfig().setCheckpointTimeout(60000);
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
env.getCheckpointConfig().enableExternalizedCheckpoits(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);

			Checkpoint			Savepoint
觸發管理方式		由Flink自動觸發並管理		由用戶手動觸發並管理
主要用途		在Task發生異常時快速恢復	有計劃地進行備份,使作業能停止後再恢復
			- 例如網絡抖動			- 例如修改改嗎、調整併發
特點			輕量				持久
			自動從故障中恢復		以標準格式存儲,允許代碼或配置發生改變
			在作業停止後默認清除		手動觸發從Savepoint的恢復

3.2 可選的狀態存儲方式

3.2.1 MemoryStateBackend

//構造方法
MemoryStateBackend(int maxStateSize, boolean asynchronousSnapshots)

存儲方式:

  • State:TaskManager內存
  • Checkpoint:JobManager內存

容量限制:

  • 單個State maxStateSize 默認5M
  • maxStateSize <= akka.framesize 默認10M
  • 總大小不超過JobManager的內存

推薦使用的場景

  • 本地測試;幾乎無狀態的作業,比如ETL;JobManager不容易掛,或掛掉影響不大的情況
  • 不推薦在生產使用

3.2.2 FsStateBackend

構造方法:

FsStateBackend(URI checkpointDataUri, boolean asynchronousSnapshots)

存儲方式:

  • State: TaskManager內存
  • Checkpoint: 外部文件系統(本地或HDFS)

容量限制

  • 單TaskManager上State總量不超過他的內存
  • 總大小不超過配置的文件系統容量

推薦使用的場景

  • 常規使用狀態的作業,如分鐘級窗口聚合、join;需要開啓HA的作業
  • 可以在生產環境使用

RocksDBStateBackend

//構造方法
RocksDBStateBackend(URI checkpointDataUri, boolean enableIncrementalCheckpointing)

存儲方式:

State:TaskManager上的KV數據庫(實際使用內存+磁盤)

Checkpoint:外部文件系統(本地或HDFS)

容量限制:

  • 單TaskManager上State總量不超過他的內存+磁盤
  • 單Key最大2G
  • 總大小不超過配置的文件系統容量

推薦使用場景:

  • 超大狀態的作業,如天級窗口聚合;需要開啓HA的作業;對狀態讀寫性能要求不高的作業
  • 可以在生產場景使用

相關聯的參數在conf/flink-conf.yaml

  • state.backend。如果打開,可以用以存儲operator的狀態的checkpoint。支持的後端有:
    • jobmanager In-memory state, backup to JobManager’s/ZooKeeper’s memory. Should be used only for minimal state (Kafka offsets) or testing and local debugging.
    • filesystem: State is in-memory on the TaskManagers, and state snapshots are stored in a file system. Supported are all filesystems supported by Flink, for example HDFS, S3, …
  • state.backend.fs.checkpointdir:存儲checkpoint的目錄,文件系統是flink支持的文件系統。注意:State backend必須從jobmanager可訪問,使用flie:// 只能在local搭建的情況下。
  • state.backend.rocksdb.checkpointdir
  • state.checkpoints.dir
  • state.checkpoints.num-retained

總結

爲什麼要使用狀態?

數據之間有關聯,需要通過狀態滿足業務邏輯

爲什麼要管理狀態?

實時計算作業需要7*24運行,需要應對不可靠因素帶來的影響

如何選擇狀態的類型和存儲方式?

分析自己的業務場景,比對各方案的利弊,選擇合適的,夠用即可

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