轉載作者:寫bug的張小天
轉載地址:https://www.jianshu.com/p/e6297fac67cb
Process Function(過程函數)
ProcessFunction
是一個低層次的流處理操作,允許返回所有(無環的)流程序的基礎構建模塊:
1、事件(event)(流元素)
2、狀態(state)(容錯性,一致性,僅在keyed stream中)
3、定時器(timers)(event time和processing time, 僅在keyed stream中)
ProcessFunction
可以認爲是能夠訪問到keyed state和timers的FlatMapFunction
,輸入流中接收到的每個事件都會調用它來處理。
對於容錯性狀態,ProcessFunction可以通過RuntimeContext來訪問Flink的keyed state,方法與其他狀態性函數訪問keyed state一樣。
定時器允許應用程序對processing time
和event time
的變化做出反應,每次對processElement(...)
的調用都會得到一個Context
對象,該對象允許訪問元素事件時間的時間戳和TimeServer
。TimeServer
可以用來爲尚未發生的event-time或者processing-time註冊回調,當定時器的時間到達時,onTimer(...)方法會被調用。在這個調用期間,所有的狀態都會限定到創建定時器的鍵,並允許定時器操縱鍵控狀態(keyed states)。
注意:如果你想訪問一個鍵控狀態(keyed state)和定時器,你需要將ProcessFunction應用到一個鍵控流(keyed stream)中:
stream.keyBy(...).process(new MyProcessFunction())
低層次的Join(Low-Level Joins)
爲了在兩個輸入流中實現低層次的操作,應用程序可以使用CoProcessFunction
,這個函數綁定了兩個不同的輸入流,並通過分別調用processElement1(...)
和processElement2(...)
來獲取兩個不同輸入流中的記錄。
實現一個低層次的join通常按下面的模式進行:
1、爲一個輸入源(或者兩個都)創建一個狀態(state)
2、在接收到輸入源中的元素時更新狀態(state)
3、在接收到另一個輸入源的元素時,探查狀態併產生連接結果
例如:你可能將客戶數據連接到金融交易中,同時保存客戶信息的狀態,如果您關心在無序事件的情況下進行完整和確定性的連接,則當客戶數據流的水印已經通過交易時,您可以使用計時器來評估和發佈交易的連接。
例子
以下示例維護了每個key的計數,並且每一分鐘發出一個沒有更新key的key/count對。
1、count,key和last-modification-timestamp保存在ValueState中,該ValueState由key隱含地限定。
2、對於每條記錄,ProcessFunction增加counter的計數並設置最後更新時間戳
3、該函數還會在未來一分鐘內安排回調(基於event time)
4、在每次回調時,它會根據存儲的count的最後更新時間來檢查callback的事件時間,如果匹配的話,就會將key/count對發出來
注意:這個簡單的例子可以通過session window來實現,我們這裏使用ProcessFunction是爲了說明它提供的基本模式。
Java 代碼:
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.ProcessFunction;
import org.apache.flink.streaming.api.functions.ProcessFunction.Context;
import org.apache.flink.streaming.api.functions.ProcessFunction.OnTimerContext;
import org.apache.flink.util.Collector;
// 定義源數據流
DataStream<Tuple2<String, String>> stream = ...;
// 將 process function 應用到一個鍵控流(keyed stream)中
DataStream<Tuple2<String, Long>> result = stream
.keyBy(0)
.process(new CountWithTimeoutFunction());
/**
* The data type stored in the state
* state中保存的數據類型
*/
public class CountWithTimestamp {
public String key;
public long count;
public long lastModified;
}
/**
* The implementation of the ProcessFunction that maintains the count and timeouts
* ProcessFunction的實現,用來維護計數和超時
*/
public class CountWithTimeoutFunction extends ProcessFunction<Tuple2<String, String>, Tuple2<String, Long>> {
/** The state that is maintained by this process function */
/** process function維持的狀態 */
private ValueState<CountWithTimestamp> state;
@Override
public void open(Configuration parameters) throws Exception {
state = getRuntimeContext().getState(new ValueStateDescriptor<>("myState", CountWithTimestamp.class));
}
@Override
public void processElement(Tuple2<String, String> value, Context ctx, Collector<Tuple2<String, Long>> out)
throws Exception {
// retrieve the current count
// 獲取當前的count
CountWithTimestamp current = state.value();
if (current == null) {
current = new CountWithTimestamp();
current.key = value.f0;
}
// update the state's count
// 更新 state 的 count
current.count++;
// set the state's timestamp to the record's assigned event time timestamp
// 將state的時間戳設置爲記錄的分配事件時間戳
current.lastModified = ctx.timestamp();
// write the state back
// 將狀態寫回
state.update(current);
// schedule the next timer 60 seconds from the current event time
// 從當前事件時間開始計劃下一個60秒的定時器
ctx.timerService().registerEventTimeTimer(current.lastModified + 60000);
}
@Override
public void onTimer(long timestamp, OnTimerContext ctx, Collector<Tuple2<String, Long>> out)
throws Exception {
// get the state for the key that scheduled the timer
//獲取計劃定時器的key的狀態
CountWithTimestamp result = state.value();
// 檢查是否是過時的定時器或最新的定時器
if (timestamp == result.lastModified + 60000) {
// emit the state on timeout
out.collect(new Tuple2<String, Long>(result.key, result.count));
}
}
}
Scala 代碼:
import org.apache.flink.api.common.state.ValueState
import org.apache.flink.api.common.state.ValueStateDescriptor
import org.apache.flink.streaming.api.functions.ProcessFunction
import org.apache.flink.streaming.api.functions.ProcessFunction.Context
import org.apache.flink.streaming.api.functions.ProcessFunction.OnTimerContext
import org.apache.flink.util.Collector
// 定義源數據流
val stream: DataStream[Tuple2[String, String]] = ...
// 將 process function 應用到一個鍵控流(keyed stream)中
val result: DataStream[Tuple2[String, Long]] = stream
.keyBy(0)
.process(new CountWithTimeoutFunction())
/**
* state中保存的數據類型
*/
case class CountWithTimestamp(key: String, count: Long, lastModified: Long)
/**
* ProcessFunction的實現,用來維護計數和超時
*/
class CountWithTimeoutFunction extends ProcessFunction[(String, String), (String, Long)] {
/** process function維持的狀態 */
lazy val state: ValueState[CountWithTimestamp] = getRuntimeContext
.getState(new ValueStateDescriptor[CountWithTimestamp]("myState", classOf[CountWithTimestamp]))
override def processElement(value: (String, String), ctx: Context, out: Collector[(String, Long)]): Unit = {
// 初始化或者獲取/更新狀態
val current: CountWithTimestamp = state.value match {
case null =>
CountWithTimestamp(value._1, 1, ctx.timestamp)
case CountWithTimestamp(key, count, lastModified) =>
CountWithTimestamp(key, count + 1, ctx.timestamp)
}
// 將狀態寫回
state.update(current)
// 從當前事件時間開始計劃下一個60秒的定時器
ctx.timerService.registerEventTimeTimer(current.lastModified + 60000)
}
override def onTimer(timestamp: Long, ctx: OnTimerContext, out: Collector[(String, Long)]): Unit = {
state.value match {
case CountWithTimestamp(key, count, lastModified) if (timestamp == lastModified + 60000) =>
out.collect((key, count))
case _ =>
}
}
}
作者:寫Bug的張小天
鏈接:https://www.jianshu.com/p/e6297fac67cb
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。