ProcessFunction 解析

抽象类 ProcessFunction 继承了AbstractRichFunction,有两个泛型类型参数:
- I 表示 Input,也就是输入的数据类型;
- O 表示Output,也就是处理完成之后输出的数据类型。
内部单独定义了两个方法:一个是必须要实现的抽象方法.processElement();另一个是非抽象方法.onTimer()。
抽象方法.processElement()
用于“处理元素”,定义了处理的核心逻辑。这个方法对于流中的每个元素都会调用一次, 参数包括三个:输入数据值 value,上下文 ctx,以及“收集器”(Collector)out。方法没有返回值,处理之后的输出数据是通过收集器 out 来定义的。
- value:当前流中的输入元素,也就是正在处理的数据,类型与流中数0据类 型一致。
- ctx:类型是 ProcessFunction 中定义的内部抽象类Context,表示当前运行的上下文,可以获取到当前的时间戳,并提供了用于查询时间和注册定时器的“定时服务”(TimerService),以及可以将数据发送到“侧输出流”(side output)的方法.output()。
Context 抽象类定义如下:
public abstract class Context { public abstract Long timestamp(); /** A {@link TimerService} for querying time and registering timers. */ public abstract TimerService timerService(); public abstract <X> void output(OutputTag<X> outputTag, X value); }
ut:“收集器”(类型为 Collector),用于返回输出数据。使用方式与 flatMap算子中的收集器完全一样,直接调用 out.collect()方法就可以向下游发出一个数据。这个方法可以多次调用,也可以不调用。
.onTimer()中定义的, 就是闹钟响的时候要做的事。所以它本质上是一个基于时间的“回调”(callback)方法,通过时间的进展来触发;在事件时间语义下就是由水位线(watermark)来触发了。
定时方法.onTimer()也有三个参数:时间戳(timestamp),上下文(ctx),以及收集器(out)。这里的 timestamp 是指设定好的触发时间,事件时间语义下当然就是水位线了。另外这里同样有上下文和收集器,所以也可以调用定时服务(TimerService),以及任意输出处理之后的数据。
ProcessAllWindowFunction:处理每个窗口内的所有元素;
ProcessWindowFunction:处理指定key的每个窗口内的所有元素;ProcessWindowFunction 还可以获取到一个“上下文对象”(Context)。这个上下文对象非常强大,不仅能够获取窗口信息,还可以访问当前的时间和状态信息。这里的时间就包括了处理时间(processing time)和事件时间水位线(event time watermark),作为一 个全窗口 函数 ,ProcessWindowFunction 同样需要将所有数据缓存下来、等到窗口触发计算时才使用。它其实就是一个增强版的WindowFunction。
ProcessAllWindowFunction
ProcessAllWindowFunction和ProcessFunction类似,都是用来对上游过来的元素做处理,ProcessFunction是每个元素执行一次processElement方法,ProcessAllWindowFunction是每个窗口执行一次process方法(方法内可以遍历该窗口内的所有元素);
ProcessWindowFunction
ProcessWindowFunction和KeyedProcessFunction类似,都是处理分区的数据,KeyedProcessFunction是每个元素执行一次processElement方法,而ProcessWindowFunction是每个窗口执行一次process方法(方法内可以遍历该key当前窗口内的所有元素)
全量聚合: 窗口需要维护全部原始数据,窗口触发进行全量聚合
ProcessWindowFunction获得一个包含窗口所有元素的可迭代器以及一个具有时间和状态信息访问权的上下文对象,使得它比其他窗口函数提供更大的灵活性。是以性能和资源消耗为代价的,因为元素不能增量地聚合,而是需要在内部缓冲,直到认为窗口可以处理为止。
public class ProcessWinFunctionOnWindow { public static final Tuple3[] MATH = new Tuple3[]{ Tuple3.of("class1", "jack", 100), Tuple3.of("class1", "Lucy", 80), Tuple3.of("class1", "jim", 90), Tuple3.of("class1", "tom", 110), Tuple3.of("class2", "wang", 90), Tuple3.of("class2", "li", 80), Tuple3.of("class2", "li2", 80), }; public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStream<Tuple3<String, String, Integer>> input = env.fromElements(MATH); DataStream<Double> avgScore = input.keyBy(new KeySelector<Tuple3<String, String, Integer>, String>() { @Override public String getKey(Tuple3<String, String, Integer> value) throws Exception { return value.f0; } }).countWindow(2) .process(new ProcessWindowFunction<Tuple3<String, String, Integer>, Double, String, GlobalWindow>() { @Override public void process(String s, Context context, Iterable<Tuple3<String, String, Integer>> iterable, Collector<Double> collector) throws Exception { int sum = 0; int count = 0; for (Tuple3<String, String, Integer> in : iterable) { sum += in.f2; count++; } collector.collect(Double.valueOf(sum / count)); } }); avgScore.print(); env.execute("process function"); } }
增量聚合和全窗口函数的结合使用
对一组数据求和。大量的数据连续不断到来,全窗口函数只是把它们收集缓存起来,并没有处理;到了窗口要关闭、输出结果的时候,再遍历所有数据依次叠加,得到最终结果。而如果我们采用增量聚合的方式,那么只需要保存一个当前和的状态,每个数据到来时就会做一次加法,更新状态;到了要输出结果的时候,只要将当前状态直接拿出来就可以了。增量聚合相当于把计算量“均摊”到了窗口收集数据的过程中,自然就会比全窗口聚合更加高效、输出更加实时。
而全窗口函数的优势在于提供了更多的信息,可以认为是更加“通用”的窗口操作。它只负责收集数据、提供上下文相关信息,把所有的原材料都准备好,至于拿来做什么我们完全可以任意发挥。这就使得窗口计算更加灵活,功能更加强大。
所以在实际应用中,我们往往希望兼具这两者的优点,把它们结合在一起使用。Flink 的Window API 就给我们实现了这样的用法。
// ReduceFunction 与 WindowFunction 结合 public <R> SingleOutputStreamOperator<R> reduce( ReduceFunction<T> reduceFunction, WindowFunction<T, R, K, W> function) // ReduceFunction 与 ProcessWindowFunction 结合 public <R> SingleOutputStreamOperator<R> reduce( ReduceFunction<T> reduceFunction, ProcessWindowFunction<T, R, K, W> function) // AggregateFunction 与 WindowFunction 结合 public <ACC, V, R> SingleOutputStreamOperator<R> aggregate( AggregateFunction<T, ACC, V> aggFunction, WindowFunction<V, R, K, W> windowFunction) // AggregateFunction 与 ProcessWindowFunction 结合 public <ACC, V, R> SingleOutputStreamOperator<R> aggregate( AggregateFunction<T, ACC, V> aggFunction, ProcessWindowFunction<V, R, K, W> windowFunction)
处理机制是:基于第一个参数(增量聚合函数)来处理窗口数据,每来一个数据就做一次聚合;等到窗口需要触发计算时,则调用第二个参数(全窗口函数)的处理逻辑输出结果。需要注意的是,这里的全窗口函数就不再缓存所有数据了,而是直接将增量聚合函数的结果拿来当作了Iterable 类型的输入。一般情况下,这时的可迭代集合中就只有一个元素了。
在网站的各种统计指标中,一个很重要的统计指标就是热门的链接;想要得到热门的 url,前提是得到每个链接的“热门度”。一般情况下,可以用url 的浏览量(点击量)表示热门度。统计 10 秒钟的 url 浏览量,每 5 秒钟更新一次; 另外为了更加清晰地展示,还应该把窗口的起始结束时间一起输出。我们可以定义滑动窗口, 并结合增量聚合函数和全窗口函数来得到统计结果。
浙公网安备 33010602011771号