目录
(8)KeyedBroadcastProcessFunction
2.1定时器(Timer)和定时服务(TimerService)
前言
无论是基本的转换、聚合,还是更为复杂的窗口操作,其实就是基于DataStream进行转换的,所以可以统称为DataStream API。
在Flink更底层,我们可以不定义任何具体的算子(比如map,filter或window),而只是提炼出一个统一的“处理”(process)操作------它是所有转换算子的一个概括性的表达,可以自定义处理逻辑,所以这一层接口就被叫作“处理函数”(process function)。

一、基本处理函数
1.1处理函数的功能和使用
1.1.1功能
在很多应用需求中,要求我们对时间有更精细的控制,需要能够获取水位线,甚至要“把控时间”、定义什么时候做什么事,这就不是基本的时间窗口能够实现的了。这时就需要使用底层的处理函数。
处理函数提供了一个“定时服务”(TimerService),我们可以通过它访问流中的事件(event)、时间戳(timestamp)、水位线(watermark),甚至可以注册“定时事件”。而且处理函数继承了AbstractRichFunction抽象类,所以拥有富有函数类的所有特征,同时可以访问状态(state)和其他运行时的信息。
1.1.2 使用
处理函数的使用与基本的转换操作类似,只需要直接基于DataStream调用.process()方法就可以了。方法需要传入一个ProcessFunction作为参数,用来定义处理逻辑。
stream.process(new MyProcessFunction())
1.2 ProcessFunction解析
public abstract class ProcessFunction<I, O> extends AbstractRichFunction {
...
public abstract void processElement(I value, Context ctx, Collector<O> out) throws Exception;
public void onTimer(long timestamp, OnTimerContext ctx, Collector<O> out) throws Exception {}
...
}
在源码中我们可以看到,抽象类ProcessFunction继承了AbstractRichFunction,有两个泛型类型参数:I表示Input,也就是输入的数据类型;O表示Output,也就是处理完成之后输出的数据类型。
内部单独定义了两个方法:一个是必须要实现的抽象方法.processElement();另一个是非抽象方法.onTimer()。
1.2.1抽象方法 .processElement()
用于“处理元素”,定义了处理的核心逻辑。这个方法对于流中的每个元素都会调用一次,参数包括三个:输入数据值value,上下文ctx,以及“收集器”(Collector)out。方法没有返回值,处理之后的输出数据是通过收集器out来定义的。
- value:当前流中的输入元素,也就是正在处理的数据,类型与流中数据类型一致。
- ctx:类型是ProcessFunction中定义的内部抽象类Context,表示当前运行的上下文,可以获取到当前的时间戳,并提供了用于查询时间和注册定时器的“定时服务”(TimerService),以及可以将数据发送到“侧输出流”(side output)的方法.output()
- out:“收集器”(类型为Collector),用于返回输出数据。使用方式与flatMap算子中的收集器完全一样,直接调用out.collect()方法就可以向下游发出一个数据。这个方法可以多次调用,也可以不调用。
通过几个参数的分析不难发现,ProcessFunction可以轻松实现flatMap、map、filter这样的基本转换功能;而通过富函数提供的获取上下文方法.getRuntimeContext(),也可以自定义状态(state)进行处理,这也就能实现聚合操作的功能了。
java:
public class ProcessExample {
public static void main(String[] args) throws Exception {
// 设置执行环境
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 创建数据源
DataStream<Tuple2<String, Integer>> dataStream = env.fromElements(
new Tuple2<>("Alice", 1),
new Tuple2<>("Bob", 2),
new Tuple2<>("Alice", 3)
);
// 应用 processElement 函数
DataStream<String> result = dataStream.process(new ProcessFunction<Tuple2<String, Integer>, String>() {
@Override
public void processElement(Tuple2<String, Integer> value, ReadOnlyContext ctx, Collector<String> out) throws Exception {
// 处理每个元素,这里只是简单地将元素的值转换为字符串并输出
out.collect(value.f0 + ": " + value.f1);
}
});
// 打印结果到控制台
result.print();
// 执行任务
env.execute("Process Element Example");
}
}
scala:
class MyKeyedProcessFunction extends KeyedProcessFunction[String, Tuple2[String, Int], Tuple3[String, String, String]] {
override def processElement(value: Tuple2[String, Int],
ctx: KeyedProcessFunction[String, Tuple2[String, Int], Tuple3[String, String, String]]#Context,
out: Collector[Tuple3[String, String, String]]): Unit = {
// 处理每个元素,这里只是简单地将元素的值翻倍并输出
val key = value.f0
val valueToProcess = value.f1
val processedValue = valueToProcess * 2
val output = Tuple3(key, "processed", processedValue.toString)
out.collect(output)
}
}
/**
在这个示例中,我们定义了一个名为 MyKeyedProcessFunction 的类,它继承自 KeyedProcessFunction。我们使用 Tuple2 类型作为输入元素,其中第一个字段是字符串类型的 key,第二个字段是整数值。输出使用 Tuple3 类型,其中第一个字段是 key,第二个字段是字符串 "processed",第三个字段是处理后的整数值。
在 processElement() 方法中,我们首先获取元素的 key 和值,然后对值进行翻倍处理。最后,我们将处理后的结果封装为 Tuple3 类型的输出,并通过 out.collect() 方法将其发送到下游操作。
请注意,这只是一个简单的示例,你可以根据自己的需求调整 processElement() 方法的逻辑来处理实际数据。
*/

最低0.47元/天 解锁文章
1228

被折叠的 条评论
为什么被折叠?



