Flink狀態管理(一) 原理和數據持久化

前言

Flink documentation 中 “work with state” 中提到了Flink的狀態管理機制。實現思想來源於Chandy-Lamport的分佈式快照算法。分別對理論和源碼瞭解後,發現Flink其實是算法的一個極簡實現。具體來說一下怎麼來簡化實現的。

Chandy-Lamport 分佈式快照算法熟肉版

文章中通過Token傳遞的兩個典型場景來分析分佈式快照應該遵循的法則:

  1. Sender Record狀態時,已發送的數量和Channel Record時已經接受數量一致;
  2. Receiver Record狀態時,已經接受的數量和Channel Record時已經發送的數量一致;

關於模型本身不多說,適用於典型的數據有向傳輸場景。基於以上的規則,C-L給出瞭解決辦法:

  1. Sender record狀態前,發送一個Marker;
  2. Receiver 除了record自己的狀態,還record Channel的狀態,cause Channel沒有實體承載計算邏輯。 具體細分兩種場景:
    1. 收到marker時,還沒有record,則記錄channel爲空,觸發receiver record;
    2. 收到marker時,已經record過了,則記錄channel的狀態爲從Record後到marker的所有記錄,本質上是補充了一份record時刻Channel的狀態;

論文本身後面都是在證明結果時有效的,不贅述,趕緊結合Flink來看看。

Flink的簡化實現

首先Flink肯定保留了marker的思想,具體對應於Flink中的barrier。其次,我們可以從C-L算法中可以figure out Receiver是可以自主觸發record的,Flink中各Operator是不能自主觸發Record的。這是關鍵。看帶來的效果:

  1. 在非主動觸發的情況下,我們可以不用記錄Channel的狀態,按照C-L的算法,Channel中必然是空的。
  2. Operator到底需要記錄什麼?這個答案開始明確,需要記錄的是Operator中持有的State,如果不帶State完,其實啥都不需要記錄。
  3. Source的State略有不同。因爲Source更關注的是整流Restart後從哪個點讀取數據,例如記錄kafka的offset。

Flink State數據持久化

First of all,區分兩個概念:State和 StateBackend:

  1. State,表示我們記錄的狀態數據;
  2. StateBackend,表示State具體的存儲方式;

明白記錄的是什麼,怎麼記錄之後,就可以很happy的去擼源碼了。日常State有哪些類型?ValueState、ListState、MapState、ReduceState、OperatorState。其實可以直接去找State的寫入,也就是在update是怎麼做的。

HeapValueState的存儲

拿最簡單的Heap方式記錄來看,對應的是HeapValueState

// From  HeapValueState.java
@Override
public void update(V value) {
	if (value == null) {
		clear();
		return;
	}
	stateTable.put(currentNamespace, value);
}

stateTable一般是在RichOperator open的時候註冊上的,看這個類。KeyedStateFactory.createInternalState,根據存儲類型不同會有不同的實現。貼個調用棧吧:
創建State的調用棧
可以看到HeapValue的存儲,就簡單的用了內存的數據結構,關於StateTable的實現,先挖坑(已填坑,Flink狀態管理(二)狀態數據結構和註冊流程)。繼續看看RocksDB的玩法

RocksDBValueState 數據持久化

HeapValueState將數據放在內存中,HeapRocksDB則是通過RocksDB的接口將數據寫入到RocksDB中。

// From RocksDBValueState
@Override
public void update(V value) {
	if (value == null) {
		clear();
		return;
	}
	DataOutputViewStreamWrapper out = new DataOutputViewStreamWrapper(keySerializationStream);
	try {
		writeCurrentKeyWithGroupAndNamespace();
		byte[] key = keySerializationStream.toByteArray();
		keySerializationStream.reset();
		valueSerializer.serialize(value, out);
		backend.db.put(columnFamily, writeOptions, key, keySerializationStream.toByteArray());
	} catch (Exception e) {
		throw new FlinkRuntimeException("Error while adding data to RocksDB", e);
	}
}

和HeapValueState的區別在於,當State做Update操作的時候,需要將數據序列化之後存儲到RocksDB中去。

雷點

採用RocksDB的方式,如果更新很頻繁,會帶來大量的序列化動作,喫CPU的叻。後面也看到了RocksDB的增量更新等玩法。同時,也心知肚明要想存儲到外部系統,序列化的動作少不了。怎麼辦?首先你要指定好序列化器,給TypeHint,再者,用Tuples代替POJO是序列化相關包治百病的處理方式。再者,我其實沒有用過RocksDB存儲狀態啦,歡迎測試過的小夥伴給我點結果。

Last

本文基於最簡單的ValueState遷出了State的實現。於自己於他人,都是拋磚引玉。知道ValueState怎麼玩的了,其他的都是一個玩法。曾今以爲Flink大名鼎鼎的“有狀態”和ValueState等是兩個概念,在這裏終於匯合。

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