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使用示例
簡單狀態機代碼示例:
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運行,需要應對不可靠因素帶來的影響
如何選擇狀態的類型和存儲方式?
分析自己的業務場景,比對各方案的利弊,選擇合適的,夠用即可