我們知道flink已經(jīng)封裝了很多高級(jí)的api供用戶訪問(wèn)使用,但是有時(shí)候我們可能根據(jù)不同的需求,發(fā)現(xiàn)提供的高級(jí)api不能滿足我們的需求,這個(gè)時(shí)候flink也為我們提供了low-level層面的api,比如processFunction,通過(guò)processFunction函數(shù),我們可以訪問(wèn)state,進(jìn)行注冊(cè)process ,event time定時(shí)器來(lái)幫助我們完成一項(xiàng)復(fù)雜的操作。在我們使用process 函數(shù)的時(shí)候,有一個(gè)前提就是要求我們必須使用在keyedStream上,有兩個(gè)原因,一個(gè)是getRuntimeContext 得到的StreamingRuntimeContext 只提供了KeyedStateStore的訪問(wèn)權(quán)限,所以只能訪問(wèn)keyd state,另外一個(gè)是我們?cè)谧?cè)定時(shí)器的時(shí)候,需要有三個(gè)維度,namespace,key,time,所以要求我們有key,這就是在ProcessFunction中只能在keyedStream做定時(shí)器注冊(cè),在flink1.8.0版本中,有ProcessFunction 和KeyedProcessFunction 這個(gè)類(lèi)面向用戶的api,但是在ProcessFunction 類(lèi)我們無(wú)法注冊(cè)定時(shí)器,在ProcessOperator源碼中我們發(fā)現(xiàn)注冊(cè)是拋出異常
為什么KeyedProcessFunction可以調(diào)用RuntimeContext對(duì)象,通過(guò)源碼看一下
KeyedProcessFunction是一個(gè)抽象類(lèi),繼承了AbstractRichFunction抽象類(lèi)
@PublicEvolving
public abstract class KeyedProcessFunction<K, I, O> extends AbstractRichFunction
進(jìn)入AbstractRichFunction類(lèi),可以看到,該類(lèi)實(shí)現(xiàn)了實(shí)現(xiàn)了RichFunction,和Serializable接口
RichFunction中定義了getRuntimeContext方法,在AbstractRichFunction中實(shí)現(xiàn)了該方法
@Public
public abstract class AbstractRichFunction implements RichFunction, Serializable
我們調(diào)用getRuntimeContext方法時(shí),便可以獲取RuntimeContext對(duì)象,對(duì)狀態(tài)等進(jìn)行操作
private transient RuntimeContext runtimeContext;
@Override
public void setRuntimeContext(RuntimeContext t) {
this.runtimeContext = t;
}
@Override
public RuntimeContext getRuntimeContext() {
if (this.runtimeContext != null) {
return this.runtimeContext;
} else {
throw new IllegalStateException("The runtime context has not been initialized.");
}
}
現(xiàn)在開(kāi)始看process算子的實(shí)現(xiàn)
process算子需要傳入的值,傳入值分為兩種processFunc和KeyedProcessFunc,但不建議使用ProcessFunction了,建議使用KeyedProcessFunction,所以主要看KeyedProcessFunction
數(shù)據(jù)流在經(jīng)過(guò)keyBy之后會(huì)轉(zhuǎn)換成KeyedStream,先看一下KeyStream中的procss方法
KeyedStream是DataStream的實(shí)現(xiàn)
public class KeyedStream<T, KEY> extends DataStream<T>
可以看到process需要傳入一個(gè)keyedProcessFunction (用編寫(xiě)的),如果用戶不指定輸出類(lèi)型,會(huì)獲取默認(rèn)類(lèi)型
@Internal
public <R> SingleOutputStreamOperator<R> process(
KeyedProcessFunction<KEY, T, R> keyedProcessFunction,
TypeInformation<R> outputType) {
KeyedProcessOperator<KEY, T, R> operator = new KeyedProcessOperator<>(clean(keyedProcessFunction));
return transform("KeyedProcess", outputType, operator);
}
可以看出將函數(shù)封裝成了一個(gè)KeyedProcessOperator類(lèi)型,這個(gè)類(lèi)繼承了AbstractUdfStreamOperator類(lèi)和實(shí)現(xiàn)了OneInputStreamOperato接口和Triggerable接口
public class KeyedProcessOperator<K, IN, OUT>
extends AbstractUdfStreamOperator<OUT, KeyedProcessFunction<K, IN, OUT>>
implements OneInputStreamOperator<IN, OUT>, Triggerable<K, VoidNamespace>
該類(lèi)重寫(xiě)了 父類(lèi)的open方法,實(shí)現(xiàn)了AbstractUdfStreamOperator的processElement方法和Triggerable的onEventTime和onProcessingTime方法, 現(xiàn)在看一下實(shí)現(xiàn)的邏輯
open方法
在方法中,首先調(diào)用父類(lèi)Open方法進(jìn)行初始化操作, 然后初始化本類(lèi)服務(wù),
@Override
public void open() throws Exception {
//調(diào)用父類(lèi)open方法 進(jìn)行初始化
super.open();
//創(chuàng)建一個(gè) timestampedCollector 來(lái)給定Flink output ----英翻 時(shí)間戳收集器
collector = new TimestampedCollector<>(output);
//定義內(nèi)部定時(shí)服務(wù)
InternalTimerService<VoidNamespace> internalTimerService =
getInternalTimerService("user-timers", VoidNamespaceSerializer.INSTANCE, this);
//internalTimerService 封裝到 TimeService中
//獲取timerSErvice SimpleTimerService 內(nèi)部使用了 internalTimerService
TimerService timerService = new SimpleTimerService(internalTimerService);
//傳入 userFun 和 定時(shí)器 返回context對(duì)象
context = new ContextImpl(userFunction, timerService);
//同上 返回定時(shí)器onTimerContext對(duì)象
onTimerContext = new OnTimerContextImpl(userFunction, timerService);
}
這里重要的是這行代碼context = new ContextImpl(userFunction, timerService);
現(xiàn)在看下他的內(nèi)部實(shí)現(xiàn), 這個(gè)是內(nèi)部類(lèi),他繼承了KeyedProcessFunction的Context類(lèi)
在該類(lèi)中實(shí)現(xiàn)了Countext對(duì)象,對(duì)我們提供上下文服務(wù)
private class ContextImpl extends KeyedProcessFunction<K, IN, OUT>.Context {
private final TimerService timerService;
private StreamRecord<IN> element;
ContextImpl(KeyedProcessFunction<K, IN, OUT> function, TimerService timerService) {
function.super();
this.timerService = checkNotNull(timerService);
}
@Override
public Long timestamp() {
checkState(element != null);
if (element.hasTimestamp()) {
return element.getTimestamp();
} else {
return null;
}
}
@Override
public TimerService timerService() {
return timerService;
}
@Override
public <X> void output(OutputTag<X> outputTag, X value) {
if (outputTag == null) {
throw new IllegalArgumentException("OutputTag must not be null.");
}
output.collect(outputTag, new StreamRecord<>(value, element.getTimestamp()));
}
@Override
@SuppressWarnings("unchecked")
public K getCurrentKey() {
return (K) KeyedProcessOperator.this.getCurrentKey();
}
}
在看一下 processElement 方法,主要調(diào)用用戶邏輯
這里userFunc調(diào)用processElement方法,該方法為用戶定義的內(nèi)容
@Override
public void processElement(StreamRecord<IN> element) throws Exception {
collector.setTimestamp(element);
//賦值element
context.element = element;
//將context對(duì)象 和collector 傳入 userFunc中
//為用戶層級(jí)提供了訪問(wèn)時(shí)間和注冊(cè)定時(shí)器的入口
userFunction.processElement(element.getValue(), context, collector);
//賦值調(diào)用完后 清空
context.element = null;
}
當(dāng)用戶通過(guò)ctx.timerService().registerProcessingTimeTimer(); 設(shè)置定時(shí)器后,定時(shí)器觸發(fā)會(huì)走KeyedProcessOperator的onEventTime或onProcessingTime方法 這里看下onEventTime的實(shí)現(xiàn)
在EventTime計(jì)時(shí)器觸發(fā)時(shí)調(diào)用,在方法中 調(diào)用了 invokeUserFunction方法
@Override
public void onEventTime(InternalTimer<K, VoidNamespace> timer) throws Exception {
collector.setAbsoluteTimestamp(timer.getTimestamp());
invokeUserFunction(TimeDomain.EVENT_TIME, timer);
}
我們跟隨invokeUserFunction進(jìn)入方法 看下實(shí)現(xiàn),這個(gè)方法會(huì)調(diào)用 用戶的onTime方法,執(zhí)行里面邏輯
private void invokeUserFunction(
TimeDomain timeDomain,
InternalTimer<K, VoidNamespace> timer) throws Exception {
onTimerContext.timeDomain = timeDomain;
onTimerContext.timer = timer;
//這個(gè)時(shí)候也就是調(diào)用了我們自定義類(lèi)K\eyedProcessFunction中的onTimer,
//調(diào)用時(shí)傳入了OnTimerContextImpl對(duì)象,其持有IntervalTimeService服務(wù),也可以注冊(cè)定時(shí)器操作。
userFunction.onTimer(timer.getTimestamp(), onTimerContext, collector);
onTimerContext.timeDomain = null;
onTimerContext.timer = null;
}
最終 將用戶的Func 包裝成KeyedProcessOperator對(duì)象 調(diào)用transform方法,最終返回轉(zhuǎn)換后的DataStream
@Internal
public <R> SingleOutputStreamOperator<R> process(
KeyedProcessFunction<KEY, T, R> keyedProcessFunction,
TypeInformation<R> outputType) {
KeyedProcessOperator<KEY, T, R> operator = new KeyedProcessOperator<>(clean(keyedProcessFunction));
return transform("KeyedProcess", outputType, operator);
}
現(xiàn)在我們追蹤進(jìn)去看,最終調(diào)用了doTransform方法,經(jīng)過(guò)一系列的轉(zhuǎn)換,將將operator添加到拓補(bǔ)圖中,最終將operator轉(zhuǎn)換成SingleOutputStreamOperator對(duì)象,該類(lèi)繼承DataStream,進(jìn)行返回
protected <R> SingleOutputStreamOperator<R> doTransform(
String operatorName,
TypeInformation<R> outTypeInfo,
StreamOperatorFactory<R> operatorFactory) {
// read the output type of the input Transform to coax out errors about MissingTypeInfo
// 檢查輸出類(lèi)型是否為MissingTypeInfo,如果是拋出異常,
transformation.getOutputType();
//創(chuàng)建OneInputTransformation
OneInputTransformation<T, R> resultTransform = new OneInputTransformation<>(
transformation, //input --上游的 transformation
operatorName,
operatorFactory, //需要進(jìn)行轉(zhuǎn)換操作的
outTypeInfo,
environment.getParallelism());
@SuppressWarnings({"unchecked", "rawtypes"})
SingleOutputStreamOperator<R> returnStream = new SingleOutputStreamOperator(environment, resultTransform);
//多個(gè)級(jí)聯(lián)的map和filter操作會(huì)被transform成為一連串的OneInputTransformation。
// 后一個(gè)transformation的input指向前一個(gè)transformation
getExecutionEnvironment().addOperator(resultTransform);
return returnStream;
}
到此整個(gè)process算子調(diào)用完成
如有錯(cuò)誤,歡迎指正!