一、底层API:ProcessFunction(event,state,time)
ProcessFunction 可以处理一或两条输入数据流中的单个事件或者归入一个特定窗口内的多个事件。它提供了对于时间和状态的细粒度控制。开发者可以在其中任意地修改状态,也能够注册定时器用以在未来的某一时刻触发回调函数。因此,你可以利用 ProcessFunction 实现许多有状态的事件驱动应用所需要的基于单个事件的复杂业务逻辑。
1、实现类KeyedProcessFunction<String, Tuple2<String, String>, Tuple2<String, Long>>
对每条流入数据进行处理,可重写其中的方法。String为key类型,Tuple2<String, String>为输入数据元组类型, Tuple2<String, Long>> 为输出数据类型。是一种Richfunction。
1)public void open(Configuration conf):当第一条数据进入时需要发生的动作(如设置初始状态)
2)getRuntimeContext().getState(new ValueStateDescriptor<Long>("startTime", Long.class)):获取上下文信息并注册状态
3)public void processElement(Tuple2<String, String> in,Context ctx,Collector<Tuple2<String, Long>> out) :处理进入的每一条数据,最终存储在Collector中(out.collect(Tuple2.of(key, value))),ctx为上下文信息,如ctx.timerService().registerEventTimeTimer(ctx.timestamp() + 4 * 60 * 60 * 1000)注册定时器
4)public void onTimer(long timestamp,OnTimerContext ctx,Collector<Tuple2<String, Long>> out) :定时器到时后采取的动作
二、Core API :DataStream、DataSet(streams,window)
为许多通用的流处理操作提供了处理原语。这些操作包括窗口、逐条记录的转换操作,在处理事件时进行外部数据库查询等。DataStream API 支持 Java 和 Scala 语言,预先定义了例如map()
、reduce()
、aggregate()
等函数。可以通过扩展实现预定义接口或使用 Java、Scala 的 lambda 表达式实现自定义的函数。
1、DataStream<T>
获取数据流的保存方式,以及持续的对数据流处理,如
DataStream<Tuple2<String, Long>> result = clicks .map( new MapFunction<Click, Tuple2<String, Long>>() { @Override public Tuple2<String, Long> map(Click click) { return Tuple2.of(click.userId, 1L); } }) .keyBy(0) .window(EventTimeSessionWindows.withGap(Time.minutes(30L))) .reduce((a, b) -> Tuple2.of(a.f0, a.f1 + b.f1));
2、DataStream<T>
获取批处理数据集的保存,持续对数据集transformation
三、顶层API:Table && SQL
支持Table API 和 SQL这两种关系型的 API,都是批处理和流处理统一的 API,这意味着在无边界的实时数据流和有边界的历史记录数据流上,关系型 API 会以相同的语义执行查询,并产生相同的结果。Table API 和 SQL 借助了 Apache Calcite 来进行查询的解析,校验以及优化。它们可以与 DataStream 和 DataSet API 无缝集成,并支持用户自定义的标量函数,聚合函数以及表值函数。旨在简化数据分析、数据流水线和 ETL 应用的定义。
EnvironmentSettings settings = EnvironmentSettings.newInstance().build(); TableEnvironment tEnv = TableEnvironment.create(settings); tEnv.executeSql("CREATE TABLE transactions ( " + " account_id BIGINT, " + " amount BIGINT, " + " transaction_time TIMESTAMP(3), " + " WATERMARK FOR transaction_time AS transaction_time - INTERVAL '5' SECOND " + ") WITH ( " + " 'connector' = 'kafka', " + " 'topic' = 'transactions', " + " 'properties.bootstrap.servers' = 'kafka:9092', " + " 'format' = 'csv' " + ")"); tEnv.executeSql("CREATE TABLE spend_report ( " + " account_id BIGINT, " + " log_ts TIMESTAMP(3), " + " amount BIGINT ," + " PRIMARY KEY (account_id, log_ts) NOT ENFORCED" + ") WITH ( " + " 'connector' = 'jdbc', " + " 'url' = 'jdbc:mysql://mysql:3306/sql-demo', " + " 'table-name' = 'spend_report', " + " 'driver' = 'com.mysql.jdbc.Driver', " + " 'username' = 'sql-demo', " + " 'password' = 'demo-sql' " + ")"); Table transactions = tEnv.from("transactions"); report(transactions).executeInsert("spend_report"); //executeInsert为输出功能 public static Table report(Table rows) { return rows.select( $("account_id"), $("transaction_time").floor(TimeIntervalUnit.HOUR).as("log_ts"), $("amount")) .groupBy($("account_id"), $("log_ts")) .select( $("account_id"), $("log_ts"), $("amount").sum().as("amount"));
1)可以通过实现ScalarFunction接口自定义函数,用SQL中的call(class,col)来调用。
2)tumble类实现了窗口功能,在批处理和流处理中均可使用
public static Table report(Table rows) { return rows.window(Tumble.over(lit(1).hour()).on($("transaction_time")).as("log_ts")) .groupBy($("account_id"), $("log_ts")) .select( $("account_id"), $("log_ts").start().as("log_ts"), $("amount").sum().as("amount")); }
四、状态与时间 API
1、ValueState<T>
保存状态,包括value()、update(T)、clear()三个方法。
2、ValueStateDescriptor<T>
用来生成状态
ValueStateDescriptor<Boolean> flagDescriptor = new ValueStateDescriptor<>( "flag", Types.BOOLEAN); flagState = getRuntimeContext().getState(flagDescriptor);
3、Context类
1)context.timerService().currentProcessingTime():获取当前机器时间
2)context.timerService().registerProcessingTimeTimer(timer):注册定时器,temer为未来某一时刻
3)public void onTimer:定时器到时后的回调函数
4)ctx.timerService().deleteProcessingTimeTimer(timer):清除定时器
五、Transformation API
流处理和批处理的transformation API使用没有明显的界限,以下介绍仅为常用环境
1、DataStream的transformation
1)keyBy():根据传入的属性进行分组处理(实际上在分布式中,相同的key会被相同的线程处理,每个线程保存着所处理的key组的状态)。为了编译器更容易推断key的类型,可用 KeySelector接口的匿名类或lambda表达式传入keyBy。
2)process():传入的参数对象需实现processFunction接口,对流入的每条数据进行处理。
3)map():无状态转换,通过实现MapFunction接口,可以将数据流类型转换。
4)flatMap():可以输出你想要的任意数量的元素,也可以一个都不发。需实现FlatMapFunction接口,重写flatMap时可将任意数量元素放入out集合中。
5)maxBy():输出当前key组的最大值。
6)reduce():用来实现的自定义聚合,需实现ReduceFunction接口。reduce的第一个参数为reduce两条数据的过程,第二个参数可以是实现了ProcessWindowFunction的对象,实际上就是逐条与窗口内的数据比较。
7)connect():连接两个数据流,后面的flatMap函数需要传入继承RichCoFlatMapFunction类的函数。
8)window(TumblingEventTimeWindows.of(Time.minutes(1))):窗口聚合,后面的process处理需要继承ProcessWindowFunction类。
9)sideOutputLateData(lateTag):一般跟在window后接收晚于窗口时间到达的事件,lateTag为OutputTag类型的数据集。可以用数据流的getSideOutput功能输出。
10)allowedLateness(Time.seconds(10)):可以和上一条配合使用,允许延迟一定时间后再收集
11)partition(), rebalance(), shuffle(), broadcast()
2、DataSet的transformation
1)filter():过滤器,传入对象可实现filterFunction接口
2)project():取Tuple的某一个字段
3)join(filterRanks) .where(0).equalTo(1).projectSecond(0,1,2): 两个DataSet的join操作,where指定第一个数据集的连接字段,equalTo指定第二个数据集的连接字段,projectSecond指定链接后的Tuple的每个位置所放内容,second指的是被连接的DataSet。
4)coGroup(filterVisits).where(1).equalTo(0).with(new MyCoGroupFunction);coGroup比join更加灵活,其最终传入的实现GoGroupFunction接口的对象使用iterator更加灵活。
五、获取/输出数据源方法
Flink 的 Java 和 Scala DataStream API 可以将任何可序列化的对象转化为流。Flink 自带的序列化器有:
——基本类型,即 String、Long、Integer、Boolean、Array
——复合类型:Tuples、POJOs 和 Scala case classes
而且 Flink 会交给 Kryo 序列化其他类型。也可以将其他序列化器和 Flink 一起使用。特别是有良好支持的 Avro。Flink 的原生序列化器可以高效地操作 tuples 和 POJOs。
1、Java tuples
对于 Java,Flink 自带有 Tuple0
到 Tuple25
类型。
2、POJOs
如果满足以下条件,Flink 将数据类型识别为 POJO 类型(并允许“按名称”字段引用):
——该类是公有且独立的(没有非静态内部类)
——该类有公有的无参构造函数
——类(及父类)中所有的所有不被 static、transient 修饰的属性要么是公有的(且不被 final 修饰),要么是包含公有的 getter 和 setter 方法,这些方法遵循 Java bean 命名规范。
3、常用获取数据源方式
1)env.fromElements():参数可以为java对象,获取DataStream数据源。
2)fromCollection(Collection):可将java集合传入。
3)env.socketTextStream("localhost", 9999):通过网络socket获取数据流。
4)env.readTextFile("file:///path"):读取文件获取数据。
4、常用输出数据方式
1)addSink(new AlertSink()):输出结果。传入对象实现了SinkFunction接口。
2)print():打印到控制台上。
六、其他常用API
对每一个Function接口,Flink 同样提供了一个所谓 “rich” 的变体,如 RichFlatMapFunction,其中增加了以下方法,包括:
——open(Configuration c):仅在算子初始化时调用一次。可以用来加载一些静态数据,或者建立外部服务的链接等。
——close()
——getRuntimeContext(): 为整套潜在有趣的东西提供了一个访问途径,最明显的,它是创建和访问 Flink 状态的途径。
1、ExecutionEnvironment
创建Flink的执行环境,包括参数设置、水印设置、checkpoint设置等:
1)getExecutionEnvironment():获取environment实例的方法
2)getConfig():获取参数设置的路径
3)env.readCsvFile(filePathName).types(Class ...):第一步产生对象csvReader对象,可以对其做切分等操作;type方法将CSV数据解析为具有指定类型的字段的1元组。该方法针对每个可能的元组长度重载,获取DataStream。
4)env.execute(task name):执行任务
2、
StreamExecutionEnvironment
1)env.addSource(new TransactionSource()).name("transactions"):TransactionSource为实现了FromIteratorFunction的无限数据集。绑定到数据源上的 name
属性是为了调试方便,如果发生一些异常,我们能够通过它快速定位问题发生在哪里。
3、
filterFunction<T>接口
public boolean filter(Tuple2<String, String> value):过滤每一条数据value中的内容,可作为DataSet实例的方法filter中的传参。
4、
JoinFunction<T>接口
可以重写public Tuple2<String, Double> join(Rating rating, Tuple2<String, Double> weight),形成一个join形式。同时,以手动指导Flink优化器选择合并方式:
DataSet<Tuple2<SomeType, AnotherType> result = input1.join(input2, JoinHint.BROADCAST_HASH_FIRST) .where("id").equalTo("key");
1)OPTIMIZER_CHOOSES:相当于不提供任何提示,将选择留给系统。
2)BROADCAST_HASH_FIRST:广播第一个输入并从中构建哈希表,由第二个输入探测。如果第一个输入非常小,这是一个很好的策略。
3)BROADCAST_HASH_SECOND:广播第二个输入并从中构建一个哈希表,由第一个输入探测。如果第二个输入非常小,这是一个好策略。
4)REPARTITION_HASH_FIRST:系统对每个输入进行分区(shuffle)(除非输入已经分区)并从第一个输入构建哈希表。如果第一个输入小于第二个输入,则此策略很好,但两个输入仍然很大。( 注意:如果不能进行大小估算,并且不能重新使用预先存在的分区和排序顺序,则这是系统使用的默认回退策略。)
5)REPARTITION_HASH_SECOND:系统对每个输入进行分区(shuffle)(除非输入已经被分区)并从第二个输入构建哈希表。如果第二个输入小于第一个输入,则此策略很好,但两个输入仍然很大。
6)REPARTITION_SORT_MERGE:系统对每个输入进行分区(shuffle)(除非输入已经被分区)并对每个输入进行排序(除非它已经排序)。输入通过已排序输入的流合并来连接。如果已经对一个或两个输入进行了排序,则此策略很好。
5、GoGroupFunction<T>接口
重写public void coGroup方法,可对调用的DateSet的每个元素进行操作
6、RichCoFlatMapFunction类
包含open()、flatMap1和flatMap2功能,后两个功能分别对两个输入流做过滤处理。rich function一般用于有状态处理。(可使用自定义的算子实现 InputSelectable 接口,在两输入算子消费它的输入流时增加一些顺序上的限制。)
7、CoProcessFunctions、BroadcastProcessFunctions