Flink-处理函数以及TopN运用案例
创始人
2024-03-03 20:11:07
0

7 处理函数

7.1 概述

在这里插入图片描述

更底层的操作,直接对流进行操作,直接调用处理函数

7.2 基本处理函数ProcessFunction

  1. 分析
  • ProcessFunction的来源
    在这里插入图片描述

处理函数继承了AbstractRichFunction富函数抽象类,因此就具有访问状态(state)和其他运行时环境

在这里插入图片描述

在这里插入图片描述

例如AbstractRichFunction类中有getRuntimeContext()这个方法返回的是RuntimeContext类

  • 内部分析

在这里插入图片描述
在这里插入图片描述

里面有个抽象方法processElements()处理元素,参数分别是:输入,上下文,用于输出的collector

以及onTimer()方法类似一个触发回调机制,主要用于定时器的回调,就是TimerService使用registerProcessingTimeTime()注册定时器[详见下面,有写],仅仅是这次,如果需要调用,那么调用就是onTimer()方法调用,但是仅仅限于keyby后的KeyedStream的定时器操作

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

上下文Context就有可以获取时间戳timestamp(),可以做测输出流output(),以及TimerService接口中有获取处理时间currentProcessingTime()的方法以及获取时间/水位线时间的currentWatermark()方法,以及注册定时器的registerProcessingTimeTime()的方法这边有问题

  • 关系图

在这里插入图片描述

  1. 使用
stream.process(new MyProcessFunction())
  1. 分类
  • 初步分析

在这里插入图片描述

stream.keyBy得到KeyedStream后调用.process函数,传进去的就是KeyedProcessFunction

在这里插入图片描述

ProcessFuntion只是一个,还有KeyedProcessFunction,两者没有继承关系,两者都继承了AbstractRichFunction富函数抽象类

在这里插入图片描述

stream.keyBy().window().process()

亦或者stream.keyBy得到KeyedStream后调用window方法,得到WindowedStream,再调用.process方法,此时传入的就是ProcessWindowFunction函数

  • 分类

Flink 提供了 8 个不同的处理函数:

(1)ProcessFunction 最基本的处理函数,基于 DataStream 直接调用.process()时作为参数传入。

(2)KeyedProcessFunction 对流按键分区后的处理函数,基于 KeyedStream 调用.process()时作为参数传入。要想使用 定时器,比如基于 KeyedStream。

(3)ProcessWindowFunction 开窗之后的处理函数,也是全窗口函数的代表。基于 WindowedStream 调用.process()时作 为参数传入。

(4)ProcessAllWindowFunction 同样是开窗之后的处理函数,基于 AllWindowedStream 调用.process()时作为参数传入。

(5)CoProcessFunction 188 合并(connect)两条流之后的处理函数,基于 ConnectedStreams 调用.process()时作为参 数传入。关于流的连接合并操作,我们会在后续章节详细介绍。

(6)ProcessJoinFunction 间隔连接(interval join)两条流之后的处理函数,基于 IntervalJoined 调用.process()时作为 参数传入。 (7)BroadcastProcessFunction 广播连接流处理函数,基于 BroadcastConnectedStream 调用.process()时作为参数传入。这 里的“广播连接流”BroadcastConnectedStream,是一个未 keyBy 的普通 DataStream 与一个广 播流(BroadcastStream)做连接(conncet)之后的产物。关于广播流的相关操作,我们会在后 续章节详细介绍。

(8)KeyedBroadcastProcessFunction 按键分区的广播连接流处理函数,同样是基于 BroadcastConnectedStream 调用.process()时 作为参数传入。与 BroadcastProcessFunction 不同的是,这时的广播连接流,是一个 KeyedStream 与广播流(BroadcastStream)做连接之后的产物。

  1. 代码
public class ProcessFunctionTest {public static void main(String[] args) throws Exception {StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();env.setParallelism(1);SingleOutputStreamOperator stream = env.addSource(new ClickSource()).assignTimestampsAndWatermarks(WatermarkStrategy.forBoundedOutOfOrderness(Duration.ZERO).withTimestampAssigner(new SerializableTimestampAssigner() {@Overridepublic long extractTimestamp(Event element, long recordTimestamp) {return element.timestamp;}}));stream.process(new ProcessFunction() {@Override//onTimer()方法不一定实现了,参数输入,上下文,输出public void processElement(Event value, Context ctx, Collector out) throws Exception {if(value.user.equals("Mary")){out.collect(value.user+"clicks"+value.url);}else if(value.user.equals("Bob")){//可以输出多条out.collect(value.user);out.collect(value.user);}out.collect(value.toString());//通过上下文获取当前的timestampSystem.out.println("timestamp"+ctx.timestamp());System.out.println("watermark"+ctx.timerService().currentWatermark());//使用富函数System.out.println(getRuntimeContext().getIndexOfThisSubtask());//getRuntimeContext().getState()获取状态}}).print();env.execute();}
}

输出

Bob
Bob
Event{user='Bob', url='./prod?id=100', timestamp=2022-11-23 21:56:12.444}
timestamp1669211772444
watermark-9223372036854775808
0
Event{user='Alice', url='./home', timestamp=2022-11-23 21:56:13.46}
timestamp1669211773460
watermark1669211772443
0
Maryclicks./cart
Event{user='Mary', url='./cart', timestamp=2022-11-23 21:56:14.474}
timestamp1669211774474
watermark1669211773459
0
Event{user='Alice', url='./fav', timestamp=2022-11-23 21:56:15.487}
timestamp1669211775487
watermark1669211774473
0

7.3 按键分区处理函数(KeyedProcessFunction)

7.3.1 概述

主要有以下几个方法

在这里插入图片描述

7.3.2 使用

stream.keyBy(t->t.f0).process(new MyKeyedProcessFunction())
  1. 处理时间定时器
  • 代码
public class ProcessingTimeTimerTest {public static void main(String[] args) throws Exception {StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();env.setParallelism(1);DataStreamSource stream = env.addSource(new ClickSource());stream.keyBy(data -> data.user)//参数是.process(new KeyedProcessFunction() {@Overridepublic void processElement(Event value, Context ctx, Collector out) {Long currTs = ctx.timerService().currentProcessingTime();//获取系统时间out.collect(ctx.getCurrentKey()+ "数据到达,到达时间:"+new Timestamp(currTs));//注册一个10秒后的定时器,传入的是毫秒数ctx.timerService().registerProcessingTimeTimer(currTs+10*1000L);}@Override//参数:定时器时间,OnTimerContext继承Context,public void onTimer(long timestamp,OnTimerContext ctx, Collector out) throws Exception {out.collect(ctx.getCurrentKey()+"定时器触发,触发时间:"+new Timestamp(timestamp));}}).print();env.execute();}
}
  • 结果
  1. 事件时间定时器
  • 代码
public class EventTimeTimerTest {public static void main(String[] args) throws Exception {StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();env.setParallelism(1);SingleOutputStreamOperator stream = env.addSource(new ClickSource()).assignTimestampsAndWatermarks(WatermarkStrategy.forMonotonousTimestamps().withTimestampAssigner(new SerializableTimestampAssigner() {@Overridepublic long extractTimestamp(Event element, long recordTimestamp) {return element.timestamp;}}));stream.keyBy(data -> data.user)//参数是.process(new KeyedProcessFunction() {@Overridepublic void processElement(Event value, Context ctx, Collector out) throws Exception {Long currTs = ctx.timestamp();//获取事件时间,直接掉ctx的timestamp(),不掉service中的了out.collect(ctx.getCurrentKey()+ "数据到达,时间戳:"+new Timestamp(currTs)+"watermark:"+ctx.timerService().currentWatermark());//注册一个10秒后的定时器,传入的是毫秒数ctx.timerService().registerEventTimeTimer(currTs+10*1000L);}@Override//参数:定时器时间,OnTimerContext继承Context,public void onTimer(long timestamp,OnTimerContext ctx, Collector out) throws Exception {out.collect(ctx.getCurrentKey()+"定时器触发,触发时间:"+new Timestamp(timestamp)+ctx.timerService().currentWatermark());}}).print();env.execute();}
}
  • 结果

在这里插入图片描述

在这里插入图片描述

结果分析

  • 时间戳带来watermark数据的改变,当定时器2021-08-20 16:50:23.427触发的时候定时器才到22秒512,因此定时器在16:50:23,524之后,触发在水位线之后

  • 代码

public class EventTimeTimerTest {public static void main(String[] args) throws Exception {StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();env.setParallelism(1);SingleOutputStreamOperator stream = env.addSource(new CustomSource()).assignTimestampsAndWatermarks(WatermarkStrategy.forMonotonousTimestamps().withTimestampAssigner(new SerializableTimestampAssigner() {@Overridepublic long extractTimestamp(Event element, long recordTimestamp) {return element.timestamp;}}));stream.keyBy(data -> data.user)//参数是.process(new KeyedProcessFunction() {@Overridepublic void processElement(Event value, Context ctx, Collector out) throws Exception {Long currTs = ctx.timestamp();//获取事件时间,直接掉ctx的timestamp(),不掉service中的了out.collect(ctx.getCurrentKey()+ "数据到达,时间戳:"+new Timestamp(currTs)+"watermark:"+ctx.timerService().currentWatermark());//注册一个10秒后的定时器,传入的是毫秒数ctx.timerService().registerEventTimeTimer(currTs+10*1000L);}@Override//参数:定时器时间,OnTimerContext继承Context,public void onTimer(long timestamp,OnTimerContext ctx, Collector out) throws Exception {out.collect(ctx.getCurrentKey()+"定时器触发,触发时间:"+new Timestamp(timestamp)+ctx.timerService().currentWatermark());}}).print();env.execute();}//自定义测试数据源public static class CustomSource implements SourceFunction{@Overridepublic void run(SourceContext ctx) throws Exception {//直接发出测试数据ctx.collect(new Event("Mary","./homne",1000L));Thread.sleep(5000L);ctx.collect(new Event( "Alice","./homne",11000L));Thread.sleep(5000L);}@Overridepublic void cancel() {}}
}
  • 结果
Mary数据到达,时间戳:1970-01-01 08:00:01.0watermark:-9223372036854775808
Alice数据到达,时间戳:1970-01-01 08:00:11.0watermark:999
Mary定时器触发,触发时间:1970-01-01 08:00:11.09223372036854775807
Alice定时器触发,触发时间:1970-01-01 08:00:21.09223372036854775807

结果分析

如果输入数据已经结束,数据集处理完毕,那么事件时间语义会把watermark推到最大,因此定时器会被全部触发

7.4 窗口处理函数(ProcessWindowFunction )

  1. 分析

在这里插入图片描述

继承的富函数类

在这里插入图片描述

里面是process方法,参数分别是key,上下文,收集数据的集合,输出

在这里插入图片描述

Context中可以获取窗口,当前处理时间,当前的watermark,以及测输出流,发现跟之前相比没有TimeService,就不能注册定时器,但是可以通过定时器获取

7.5 应用案例-TopN

7.5.1 使用ProcessAllWindowFunction

  1. 场景

例如,需要统计最近10秒内最热门的两个url链接,并且每5秒

  1. 思路
  • 使用全窗口函数ProcessAllWindowFunction开窗处理,使用HashMap来保存每个url的访问次数(通过遍历)
  • 然后转成ArrayList,然后进行排序,取前两名输出即可
  1. 代码
  • 代码
public class TopNExample_ProcessAllWindowFunction {public static void main(String[] args) throws Exception {StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();env.setParallelism(1);//读取数据SingleOutputStreamOperator stream = env.addSource(new ClickSource())//乱序种延迟0,相当于-1毫秒而已.assignTimestampsAndWatermarks(WatermarkStrategy.forBoundedOutOfOrderness(Duration.ZERO).withTimestampAssigner(new SerializableTimestampAssigner() {@Overridepublic long extractTimestamp(Event element, long recordTimestamp) {return element.timestamp;}}));//直接开窗,收集数据排序stream.map(data->data.url)//得到String类型的Stream.windowAll(SlidingEventTimeWindows.of(Time.seconds(10),Time.seconds(5)))//直接开窗.aggregate(new UrlHashMapCountAgg(),new UrlAllWindowResult()).print();env.execute();}//实现自定义的增量聚合函数public static class UrlHashMapCountAgg implements AggregateFunction, ArrayList>> {@Overridepublic HashMap createAccumulator() {return new HashMap<>();}@Overridepublic HashMap add(String value, HashMap accumulator) {if(accumulator.containsKey(value)){Long count = accumulator.get(value);accumulator.put(value,count+1);}else {accumulator.put(value,1L);}return accumulator;}//就HashMap转成ArrayList>的操作@Overridepublic ArrayList> getResult(HashMap accumulator) {ArrayList> result = new ArrayList<>();for(String key:accumulator.keySet()){result.add(Tuple2.of(key,accumulator.get(key)));}//排序result.sort(new Comparator>() {@Override//降序,后减前public int compare(Tuple2 o1, Tuple2 o2) {return o2.f1.intValue()-o1.f1.intValue();}});return result;}@Overridepublic HashMap merge(HashMap a, HashMap b) {return null;}}//实现自定义全窗口函数,包装信息输出结果public static class UrlAllWindowResult extends ProcessAllWindowFunction>, String, TimeWindow> {@Overridepublic void process(Context context, Iterable>> elements, Collector out) throws Exception {//先拿出来ArrayList> list = elements.iterator().next();StringBuilder result = new StringBuilder();result.append("---------------\n");//获取窗口信息result.append("窗口结束时间:"+new Timestamp(context.window().getEnd())+"\n");//取List排过序后的前两个,包装信息输出for(int i = 0;i<2;i++){Tuple2 currTuple = list.get(i);String info = "No."+(i+1)+" "+"url:"+currTuple.f0+" "+"访问量"+currTuple.f1+"\n ";result.append(info);}result.append("--------------------\n");out.collect(result.toString());}}
}
  • 结果
窗口结束时间:2022-11-25 21:58:35.0
No.1 url:./fav 访问量1No.2 url:./home 访问量1-----------------------------------
窗口结束时间:2022-11-25 21:58:40.0
No.1 url:./home 访问量3No.2 url:./prod?id=100 访问量3-----------------------------------
窗口结束时间:2022-11-25 21:58:45.0
No.1 url:./prod?id=100 访问量4No.2 url:./cart 访问量2-----------------------------------
窗口结束时间:2022-11-25 21:58:50.0
No.1 url:./prod?id=100 访问量4No.2 url:./fav 访问量3--------------------
  1. 评价

用这个方法思路易懂,但是使用了windowAll的全窗口函数,stream直接开窗,所有数据收集到窗口中,导致无分区也就是并行度会变成1大数据场景下内存估计会炸掉,OOM警告

7.5.2 使用 KeyedProcessFunction

  1. 场景

例如,需要统计最近10秒内最热门的两个url链接,并且每5秒

  1. 思路
  • 触发

    • 参照窗口的流式处理原理,将数据汇聚一段时间后输出,就可以使用定时器

    • 窗口结束时间+1豪秒使得watermark触发,即数据到齐

  • 收集

    • 定义一个列表把所有数据保存下来
    • 使用状态,根据之前keyby按键分组的状态
  • 输出

    • 排序
    • 输出
  1. 代码

跟上面差不多,多了状态设置,可以理解urlViewCountListState这个就是用来存有状态的数据的

  • 代码
public class TopNExample {public static void main(String[] args) throws Exception{StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();env.setParallelism(1);//读取数据SingleOutputStreamOperator stream = env.addSource(new ClickSource())//乱序种延迟0,相当于-1毫秒而已.assignTimestampsAndWatermarks(WatermarkStrategy.forBoundedOutOfOrderness(Duration.ZERO).withTimestampAssigner(new SerializableTimestampAssigner() {@Overridepublic long extractTimestamp(Event element, long recordTimestamp) {return element.timestamp;}}));//1.按照url分组,统计窗口内每个url的访问量SingleOutputStreamOperator urlCountStream = stream.keyBy(data -> data.url).window(SlidingEventTimeWindows.of(Time.seconds(10), Time.seconds(5))).aggregate(new UrlCountViewExample.UrlViewCountAgg(), new UrlCountViewExample.UrlViewCountResult());urlCountStream.print("url count");//2.对一同一窗口统计出的访问量,进行手机和排序(以聚合过的结果按照窗口间隔不间断流式输出)urlCountStream.keyBy(data->data.windowEnd).process(new TopNProcessResult(2)).print();env.execute();}//实现自定义的KeyProcessFunctionpublic static class TopNProcessResult extends KeyedProcessFunction {//定义一个属性nprivate Integer n;//1.定义列表状态private ListState urlViewCountListState;public TopNProcessResult(Integer n) {this.n = n;}//2.管理状态,在环境中获取状态,使用生命周期方法获取@Overridepublic void open(Configuration parameters) throws Exception {urlViewCountListState= getRuntimeContext().getListState(//传入描述器//两个参数:一个名字,一个类型new ListStateDescriptor("url-count-list", Types.POJO(UrlViewCount.class)));}@Overridepublic void processElement(UrlViewCount value,Context ctx, Collector out) throws Exception {//3.将数据保存到状态中urlViewCountListState.add(value);//4.注册windowEnd+1ms的定时器ctx.timerService().registerEventTimeTimer(ctx.getCurrentKey()+1);}//5.用来触发定时器//将状态拿出来,保存成ArrayList//输出包装@Overridepublic void onTimer(long timestamp, OnTimerContext ctx, Collector out) throws Exception {ArrayList urlViewCountArrayList = new ArrayList<>();for(UrlViewCount urlViewCount:urlViewCountListState.get())//得到OUT是一个iterable类型urlViewCountArrayList.add(urlViewCount);//排序urlViewCountArrayList.sort(new Comparator() {@Overridepublic int compare(UrlViewCount o1, UrlViewCount o2) {return o2.count.intValue()-o1.count.intValue();}});//6.包装信息打印输出StringBuilder result = new StringBuilder();result.append("---------------\n");//获取窗口信息result.append("窗口结束时间:"+new Timestamp(ctx.getCurrentKey())+"\n");//包装信息输出for(int i = 0;i<2;i++){UrlViewCount currTuple = urlViewCountArrayList.get(i);String info = "No."+(i+1)+" "+"url:"+currTuple.url+" "+"访问量"+currTuple.count+"\n ";result.append(info);}result.append("--------------------\n");out.collect(result.toString());}}
}
  • 结果
url count> UrlViewCount{url='./home', count=1, windowStart=2022-11-25 22:42:30.0, windowEnd=2022-11-25 22:42:40.0}
url count> UrlViewCount{url='./cart', count=2, windowStart=2022-11-25 22:42:30.0, windowEnd=2022-11-25 22:42:40.0}
---------------
窗口结束时间:2022-11-25 22:42:40.0
No.1 url:./cart 访问量2No.2 url:./home 访问量1--------------------url count> UrlViewCount{url='./home', count=2, windowStart=2022-11-25 22:42:35.0, windowEnd=2022-11-25 22:42:45.0}
url count> UrlViewCount{url='./prod?id=100', count=2, windowStart=2022-11-25 22:42:35.0, windowEnd=2022-11-25 22:42:45.0}
url count> UrlViewCount{url='./cart', count=4, windowStart=2022-11-25 22:42:35.0, windowEnd=2022-11-25 22:42:45.0}
---------------
窗口结束时间:2022-11-25 22:42:45.0
No.1 url:./cart 访问量4No.2 url:./home 访问量2--------------------
  • 评价

可以做并行计算

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...