Flink 流处理流程 API详解
流处理API的衍变
Storm:TopologyBuilder 构建图的工具,然后往图中添加节点,指定节点与节点之间的有向边是什么。构建完成后就可以将这个图提交到远程的集群或者本地的集群运行。
Flink:不同之处是面向数据本身的,会把DataStream 抽象成一个本地集合,通过面向集合流的编程方式进行代码编写。两者没有好坏之分,Storm比较灵活自由。更好的控制。在工业界Flink会更好点。开发起来比较简单、高效。经过一些列优化、转化最终也会像Storm一样回到底层的抽象。Strom API 是面向操作的,偏向底层。Flink 面向数据,相对高层次一些。
流处理的简单流程
其他分布式处理引擎一样,Flink应用程序也遵循着一定的编程模式。不管是使用 DataStream API还是 DataSet API基本具有相同的程序结构,如下代码清单所示。通过流式计算的方式实现对文本文件中的单词数量进行统计,然后将结果输出在给定路径中。
1 public class FlinkWordCount { 2 public static void main(String[] args) throws Exception { 3 // 1、获取运行环境 4 final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 5 // 2、通过socket获取源数据 6 DataStreamSource<String> sourceData = env.socketTextStream("192.168.52.12", 9000); 7 /** 8 * 3、数据源进行处理 9 * flatMap方法与spark一样,对数据进行扁平化处理 10 * 将每行的单词处理为<word,1> 11 */ 12 DataStream<Tuple2<String, Integer>> dataStream = sourceData.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() { 13 public void flatMap(String s, Collector<Tuple2<String, Integer>> collector) throws Exception { 14 String[] words = s.split(" "); 15 for (String word : words) { 16 collector.collect(new Tuple2<String, Integer>(word, 1)); 17 } 18 } 19 }) 20 // 相同的单词进行分组 21 .keyBy(0) 22 // 聚合数据 23 .sum(1); 24 // 4、将数据流打印到控制台 25 dataStream.print(); 26 /** 27 * 5、Flink与Spark相似,通过action进行出发任务执行,其他的步骤均为lazy模式 28 * 这里env.execute就是一个action操作,触发任务执行 29 */ 30 env.execute("streaming word count"); 31 } 32 }
整个Flink程序一共分为5步,分别为设定Flink执行环境、创建和加载数据集、对数据集指定转换操作逻辑、指定计算结果输出位置、调用execute方法触发程序执行。对于所有的Flink应用程序基本都含有这5个步骤,下面将详细介绍每个步骤。
操作概览
如果给你一串数据你会怎么去处理它?
DataStream 基本转换
Environment 执行环境
【1】getExecutionEnvironment:创建一个执行环境,表示当前执行程序的上下文。如果程序是独立调用的,则此方法返回本地执行环境;如果从命令行客户端调用程序以提交到集群,则此方法返回此集群的执行环境,与就是说,执行环境决定了程序执行在什么环境 getExecutionEnvironment 会根据查询运行的方式返回不同的运行环境,是最常用的一种创建执行环境的方式。批量处理作业和流式处理作业分别使用的是不同的ExecutionEnvironment。例如 StreamExecutionEnvironment是用来做流式数据处理环境,ExecutionEnvironment是批量数据处理环境。
1 //流处理 2 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 3 //块梳理 4 ExecutionEnvironment executionEnvironment = ExecutionEnvironment.getExecutionEnvironment();
如果没有设置并行度,会以 flink-conf.yaml 中的配置为准,默认为1:parallelism.default:1
1 //可以设置并行度(优先级最高) 2 env.setParallelism(1);
如果是本地执行环境底层调用的是 createLocalEnvironment:需要在调用时指定默认的并行度
val env = StreamExecutionEnvironment.createLocalEnvironment(1)
如果是集群执行环境 createRemoteEnvironment:将 Jar 提交到远程服务器,需要在调用时指定 JobManager 的 IP和端口号,并指定要在集群中运行的Jar包。flink 将这两种都进行了包装,方便我们使用。
var env = ExecutionEnvironment.createRemoteEnvironment("jobmanager-hostname",6123,"YOURPATH//wordcount.jar")
Source 初始化数据
创建完成 ExecutionEnvironment后,需要将数据引入到 Flink系统中。ExecutionEnvironment 提供不同的数据接入接口完成数据的初始化,将外部数据转换成 DataStream或 DataSet数据集。如以下代码所示,通过调用 readTextFile()方法读取file:///pathfile路径中的数据并转换成 DataStream数据集。我们可以吧 streamSource 看做一个集合进行处理。
1 //readTextFile读取文本文件的连接器 streamSource 可以想象成一个集合 2 DataStreamSource<String> streamSource = env.readTextFile("file:///path/file"); 3 //从集合中读取数据 scala 4 val stream1: DataStream[类] = env.fromCollection(list(类,类)) 5 //socket文本流 使用的比较少 6 val stream3 = env.socketTextStream("localhost",777); 7 //直接传数据,测试用,可以传入任何数据类型,最终会转化为 TypeInformation 8 val stream5 = env.fromElements(1,4,"333"); 9 /**重要,常见的是从 kafka 中读取,需要引入插件。 10 *<!-- https://mvnrepository.com/artifact/org.apache.flink/flink-connector-kafka-0.11 --> 11 *<dependency> 12 * <groupId>org.apache.flink</groupId> 13 * <artifactId>flink-connector-kafka-0.11_2.12</artifactId> 14 * <version>1.10.0</version> 15 *</dependency> 16 */ 17 // kafkaConsumer 需要的配置参数 18 val props = new Properties() 19 // 定义kakfa 服务的地址,不需要将所有broker指定上 20 props.put("bootstrap.servers", "hadoop1:9092") 21 // 制定consumer group 22 props.put("group.id", "test") 23 // 是否自动确认offset 24 props.put("enable.auto.commit", "true") 25 // 自动确认offset的时间间隔 26 props.put("auto.commit.interval.ms", "1000") 27 // key的序列化类 28 props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer") 29 // value的序列化类 30 props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer") 31 //从kafka读取数据,需要实现 SourceFunction 他给我们提供了一个 32 env.addSource(new FlinkKafkaConsumer011[String]("sensor",new SimpleStringSchema(),props));
Flink输出至Reids、Flink输出至ES、Flink 输入输出至Kafka;
通过读取文件并转换为 DataStream[String]数据集,这样就完成了从本地文件到分布式数据集的转换,同时在 Flink中提供了多种从外部读取数据的连接器,包括批量和实时的数据连接器,能够将Flink系统和其他第三方系统连接,直接获取外部数据。批处理读取文件的时候,是读取完之后进行输出的。流处理是读一个处理一个。Topic 测试:启动zk、kafka 并创建Topic="sensor"
1 [root@hadoop3 kafka_2.11-2.2.2]# ./bin/kafka-console-producer.sh --broker-list hadoop2:9092 --topic sensor 2 >333
Idea 项目启动后,就会接收到传送过来的信息:
1 package com.zzx.flink 2 3 import java.util.Properties 4 5 import org.apache.flink.api.common.serialization.SimpleStringSchema 6 import org.apache.flink.api.java.utils.ParameterTool 7 import org.apache.flink.streaming.api.functions.source.SourceFunction 8 import org.apache.flink.streaming.api.scala._ 9 import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer011 10 11 import scala.util.Random 12 13 object StreamWordCount { 14 def main(args: Array[String]): Unit = { 15 // 创建一个流处理执行环境 16 val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment 17 // 添加用户定义的数据源 18 val stream5 = env.addSource(new MySensorSource()) 19 stream5.print(); 20 21 //上面的只是定义了处理流程,同时定义一个名称。不会让任务结束 22 env.execute("stream word count word") 23 } 24 25 //实现一个自定义的 SourceFunction,自动生成测试数据 26 class MySensorSource() extends SourceFunction[SensorReading]{ 27 //定义一个 flag,表示数据源是否正常运行 28 var running: Boolean = true; 29 30 31 //运行,不停的通过 ctx 发出需要流式处理的数据,现在我们直接在内部生成 32 override def run(sourceContext: SourceFunction.SourceContext[SensorReading]): Unit = { 33 //定义一个随机数发生器 34 val rand = new Random() 35 //随机生成10个传感器的问题值,并且不断在之前温度基础上更新(随机上下波动) 36 //首先生成10个传感器的初始温度 37 var curTemps = 1.to(10).map( 38 i => ("sensor_"+i,60 + rand.nextGaussian()*10) 39 ) 40 41 //无线循环,生成随机数据流 42 while(running){ 43 //在当前文段基础上,随机生成微小波动 44 curTemps = curTemps.map( 45 data => (data._1,data._2+rand.nextGaussian()) 46 ) 47 //获取当前系统时间 48 val curTs = System.currentTimeMillis() 49 //包装成样例,用 ctx发出数据 50 curTemps.foreach( 51 data => sourceContext.collect(SensorReading(data._1,curTs,data._2)) 52 ) 53 //定义间隔时间 54 Thread.sleep(1000); 55 } 56 } 57 58 //停止 59 override def cancel(): Unit = running = false 60 } 61 62 case class SensorReading(id: String, timestamp: Long, temperature: Double) 63 }
输出结果展示:
Transform 执行转换操作
Transform 可以理解为从 source开始到 sink输出之间的所有操作都是 Transform。数据从外部系统读取并转换成 DataStream或者 DataSet数据集后,下一步就将对数据集进行各种转换操作。Flink 中的 Transformation操作都是通过不同的 Operator来实现,每个 Operator内部通过实现 Function接口完成数据处理逻辑的定义。在DataStream API 和 DataSet API提供了大量的转换算子,例如map(一个输入一个输出转换)、flatMap(将数据打散,一个输入多个输出)、filter(添加过滤条件)、keyBy等,用户只需要定义每种算子执行的函数逻辑,然后应用在数据转换操作 Dperator接口中即可。如下代码实现了对输入的文本数据集通过 FlatMap算子转换成数组,然后过滤非空字段,将每个单词进行统计,得到最后的词频统计结果。
1 DataStream<Tuple2<String, Integer>> dataStream = sourceData.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() { 2 public void flatMap(String s, Collector<Tuple2<String, Integer>> collector) throws Exception { 3 String[] words = s.split(" "); 4 for (String word : words) { 5 collector.collect(new Tuple2<String, Integer>(word, 1)); 6 } 7 } 8 // keyBy 相同的单词进行分组,sum聚合数据 9 }).keyBy(0).sum(1);
在上述代码中,通过 Java接口处理数据,极大地简化数据处理逻辑的定义,只需要通过传入相应 Lambada计算表达式,就能完成 Function定义。特殊情况下用户也可以通过实现 Function接口来完成定义数据处理逻辑。然后将定义好的 Function应用在对应的算子中即可。Flink中定义 Funciton的计算逻辑可以通过如下几种方式完成定义。
【1】通过创建 Class实现 Funciton接口
Flink中提供了大量的函数供用户使用,例如以下代码通过定义MyMapFunction Class实现MapFunction接口,然后调用DataStream的map()方法将 MyMapFunction实现类传入,完成对实现将数据集中字符串记录转换成大写的数据处理。
1 public class FlinkWordCount { 2 public static void main(String[] args) throws Exception { 3 DataStreamSource<String> sourceData = env.socketTextStream("192.168.52.12", 9000); 4 //...... 5 //数据源进行处理 6 sourceData.map(new MyMapFunciton()); 7 //...... 8 } 9 } 10 11 class MyMapFunciton implements MapFunction<String, String> { 12 13 @Override 14 public String map(String s) throws Exception { 15 return s.toUpperCase(); 16 } 17 }
【2】通过创建匿名类实现 Funciton接口
除了以上单独定义 Class来实现 Function接口之处,也可以直接在 map()方法中创建匿名实现类的方式定义函数计算逻辑。
1 DataStreamSource<String> sourceData = env.socketTextStream("192.168.52.12", 9000); 2 //通过创建 MapFunction 匿名函数来定义 Map 函数计算逻辑 3 sourceData.map(new MapFunction<String, String>() { 4 @Override 5 public String map(String s) throws Exception { 6 //实现字符串大写转换 7 return s.toUpperCase(); 8 } 9 });
【3】通过实现 RichFunciton接口
前面提到的转换操作都实现了Function接口,例如 MapFunction和 FlatMapFunction接口,在 Flink中同时提供了RichFunction接口,主要用于比较高级的数据处理场景,RichFunction接口中有open、close、getRuntimeContext和setRuntimeContext等方法来获取状态,缓存等系统内部数据。和 MapFunction相似,RichFunction子类中也有 RichMapFunction,如下代码通过实现RichMapFunction定义数据处理逻辑。
1 sourceData.map(new RichFunction() { 2 @Override 3 public void open(Configuration configuration) throws Exception { 4 5 } 6 7 @Override 8 public void close() throws Exception { 9 10 } 11 12 @Override 13 public RuntimeContext getRuntimeContext() { 14 return null; 15 } 16 17 @Override 18 public IterationRuntimeContext getIterationRuntimeContext() { 19 return null; 20 } 21 22 @Override 23 public void setRuntimeContext(RuntimeContext runtimeContext) { 24 25 } 26 });
分区Key指定:在 DataStream数据经过不同的算子转换过程中,某些算子需要根据指定的key进行转换,常见的有 join、coGroup、groupBy类算子,需要先将 DataStream或 DataSet数据集转换成对应的 KeyedStream和 GroupedDataSet,主要目的是将相同 key值的数据路由到相同的 Pipeline中,然后进行下一步的计算操作。需要注意的是,在 Flink中这种操作并不是真正意义上将数据集转换成 Key-Value结构,而是一种虚拟的 key,目的仅仅是帮助后面的基于 Key的算子使用,分区人 Key可以通过两种方式指定:
【1】根据字段位置指定
在DataStream API中通过 keyBy()方法将 DataStream数据集根据指定的 key转换成重新分区的 KeyedStream,如以下代码所示,对数据集按照相同 key进行 sum()聚合操作。
1 // 根据第一个字段进行重分区,相同的单词进行分组。第二个字段进行求和运算 2 dataStream.keyBy(0).sum(1);
在DataSet API中,如果对数据根据某一条件聚合数据,对数据进行聚合时候,也需要对数据进行重新分区。如以下代码所示,使用DataSet API对数据集根据第一个字段作为 GroupBy的 key,然后对第二个字段进行求和运算。
1 // 根据第一个字段进行重分区,相同的单词进行分组。max 求相同key下的最大值 2 dataStream.groupBy(0).max(1);
【2】根据字段名称指定
KeyBy 和GroupBy 的Key 除了能够通过字段位置来指定之外,也可以根据字段的名称来指定。使用字段名称需要DataStream 中的数据结构类型必须是 Tuple类或者 POJOs类的。如以下代码所示,通过指定name字段名称来确定groupby 的 key字段。
1 DataStreamSource<Persion> sourceData = env.fromElements(new Persion("zzx", 18)); 2 //使用 name 属性来确定 keyBy 3 sourceData.keyBy("name").sum("age");
如果程序中使用 Tuple数据类型,通常情况下字段名称从1开始计算,字段位置索引从0开始计算,以下代码中两种方式是等价的。
1 //通过位置指定第一个字段 2 dataStream.keyBy(0).sum(1); 3 //通过名称指定第一个字段名称 4 dataStream.keyBy("_1").sum("_2");
【3】通过Key选择器指定
另外一种方式是通过定义Key Selector来选择数据集中的Key,如下代码所示,定义KeySelector,然后复写 getKey方法,从Person对象中获取 name为指定的Key。
1 DataStreamSource<Persion> persionData = env.fromElements(new Persion("zzx", 18)); 2 persionData.keyBy("name").sum("age"); 3 persionData.keyBy(new KeySelector<Persion, Object>() { 4 @Override 5 public Object getKey(Persion persion) throws Exception { 6 return persion.getName(); 7 } 8 });
理解 KeyedStream
基于 key的HashCode重分区,同一个key只能在同一个分区内处理,一个分区内可以有不同的key。DataStream -> KeyedStream:逻辑地将一个key
1 object StreamWordCount { 2 def main(args: Array[String]): Unit = { 3 // 创建一个流处理执行环境 4 val env = StreamExecutionEnvironment.getExecutionEnvironment 5 //从文件中读取数据并转换为 类 6 val inputStreamFromFile: DataStream[String] = env.readTextFile("E:\\Project\\flink\\src\\main\\resources\\wordcount.txt") 7 //转换 8 val dataStream: DataStream[SensorReading] = inputStreamFromFile 9 .map( data => { 10 var dataArray = data.split(",") 11 SensorReading(dataArray(0),dataArray(1).toLong,dataArray(2).toDouble) 12 }) 13 .keyBy(new MyIdSeletector()) 14 // .sum("temperature") 15 //reduce:传入一个函数,函数类型都一样,每一次都是在之前的基础上结合当前新输入的数据得到一个进一步聚合的结果 16 //需求:输出最大的timestamp 最小的温度值 类型不能变 17 .reduce(new MyReduce) 18 /* .reduce((curRes,newData) => 19 SensorReading(curRes.id,curRes.timestamp.max(newData.timestamp),curRes.temperature.min(newData.temperature)))*/ 20 //aggregate:都是private类型,所有的滚动算子都会调到 aggregate。 21 22 //上面的只是定义了处理流程,同时定义一个名称。不会让任务结束 23 env.execute("stream word count word") 24 } 25 } 26 case class SensorReading(id: String, timestamp: Long, temperature: Double) 27 //自定义函数类,key选择器 输入类型SensorReading 返回 String 28 class MyIdSeletector() extends KeySelector[SensorReading,String] { 29 override def getKey(in: SensorReading): String = in.id 30 } 31 //自定义 Reduce 32 class MyReduce extends ReduceFunction[SensorReading] { 33 override def reduce(t: SensorReading, t1: SensorReading): SensorReading = { 34 SensorReading(t.id,t.timestamp.max(t1.timestamp),t.temperature.min(t1.temperature)) 35 } 36 }
结果展示:分布式处理,可能得到的最后一条时间戳不是最大的。
Split 分流操作
DataStream->SplitStream 根据某些特征把一个 DataStream 拆分成两个或者多个 DataStream。但它并不是一个完整的分流操作,只是从逻辑上按照某种特征进行分词了。
Select
SplitStream->DataStream:从一个 SplitStream 中获取一个或者多个 DataStream。
1 object StreamWordCount { 2 def main(args: Array[String]): Unit = { 3 // 创建一个流处理执行环境 4 val env = StreamExecutionEnvironment.getExecutionEnvironment 5 //从文件中读取数据并转换为 类 6 val inputStreamFromFile: DataStream[String] = env.readTextFile("E:\\Project\\flink\\src\\main\\resources\\wordcount.txt") 7 //转换 8 val dataStream: DataStream[SensorReading] = inputStreamFromFile 9 .map( data => { 10 var dataArray = data.split(",") 11 SensorReading(dataArray(0),dataArray(1).toLong,dataArray(2).toDouble) 12 }) 13 .keyBy(new MyIdSeletector()) 14 .sum("temperature") 15 16 //分流 17 val splitStream = dataStream.split( 18 data => { 19 if (data.temperature > 30) 20 Seq("high") 21 else 22 Seq("low") 23 }) 24 val highTempStream: DataStream[SensorReading] = splitStream.select("high") 25 val lowTempStream: DataStream[SensorReading] = splitStream.select("low") 26 val allTempStream: DataStream[SensorReading] = splitStream.select("low","high") 27 highTempStream.print("highTempStream") 28 lowTempStream.print("lowTempStream") 29 allTempStream.print("allTempStream") 30 //上面的只是定义了处理流程,同时定义一个名称。不会让任务结束 31 env.execute("stream word count word") 32 }
输出结果展示:
Connect 合流操作
DataStream->ConnectedStreams:连接两个保持他们类型的数据流,两个数据流被 Connect之后,只是被放在了一个同一个流中,内部依然保持各自的数据和形式不发生任何变化,两个流相互独立。
CoMap CoFlatMap
ConnectedStreams->DataStream:作用于ConnectedStream 上,功能与 map和 flatMap一样,对 ConnectedStreams 中的每一个 Stream分别进行 map和 flatMap处理。
1 object StreamWordCount { 2 def main(args: Array[String]): Unit = { 3 // 创建一个流处理执行环境 4 val env = StreamExecutionEnvironment.getExecutionEnvironment 5 //从文件中读取数据并转换为 类 6 val inputStreamFromFile: DataStream[String] = env.readTextFile("E:\\Project\\flink\\src\\main\\resources\\wordcount.txt") 7 //转换 8 val dataStream: DataStream[SensorReading] = inputStreamFromFile 9 .map( data => { 10 var dataArray = data.split(",") 11 SensorReading(dataArray(0),dataArray(1).toLong,dataArray(2).toDouble) 12 }) 13 .keyBy(new MyIdSeletector()) 14 // .sum("temperature") 15 //reduce:传入一个函数,函数类型都一样,每一次都是在之前的基础上结合当前新输入的数据得到一个进一步聚合的结果 16 //需求:输出最大的timestamp 最小的温度值 类型不能变 17 .reduce(new MyReduce) 18 //分流 19 val splitStream = dataStream.split( 20 data => { 21 if (data.temperature > 60) 22 Seq("high") 23 else 24 Seq("low") 25 }) 26 val lowTempStream: DataStream[SensorReading] = splitStream.select("low") 27 28 //合流 29 val warningStream: DataStream[(String,Double)] = highTempStream.map( 30 data => (data.id,data.temperature) 31 ) 32 33 val connectedStreams: ConnectedStreams[(String,Double),SensorReading] 34 = warningStream.connect(lowTempStream) 35 val reslutStream: DataStream[Object] = connectedStreams.map( 36 warningData => (warningData._1,warningData._2,"high temp waring"), 37 lowTempSata => (lowTempSata.id,"normal") 38 ) 39 reslutStream.print("result"); 40 //上面的只是定义了处理流程,同时定义一个名称。不会让任务结束 41 env.execute("stream word count word") 42 } 43 }
输出结果:
Union
DataStream->DataStream:对两个或者两个以上的 DataStream 进行 union 操作,产生一个包含所有 DataStream 元素的新 DataStream。不能把类型不匹配的流合并在一起,可以Union两个或两个之上的流。
val highTempStream: DataStream[SensorReading] = splitStream.select("high") val lowTempStream: DataStream[SensorReading] = splitStream.select("low") val allTempStream: DataStream[SensorReading] = splitStream.select("low","high") //union val unionStream: DataStream[SensorReading] = highTempStream.union(lowTempStream).union(allTempStream)
sink 输出结果
数据集经过转换操作之后,形成最终的结果数据集,一般需要将数据集输出在外部系统中或者输出在控制台之上。在Flink DataStream 和 DataSet接口中定义了基本的数据输出方法,例如基于文件输出 writeAsText(),基于控制台输出print()等。同时Flink在系统中定义了大量的 Connector,方便用户和外部系统交互,用户可以直接通过调用 addSink()添加输出系统定义的DataSink类算子,这样就能将数据输出到外部系统。以下实例调用 DataStream API中的 writeAsText()和 print()方法将数据集输出在文件和客户端中。
1 //将数据流打印到控制台 2 dataStream.print(); 3 4 //将数据输出到文件中 5 dataStream.writeAsText("file://path/to/savenfile"); 6 7 //将数据输出到socket 8 reslutStream.writeToSocket(hostname : _root_.scala.Predef.String, port : java.lang.Integer, schema : org.apache.flink.api.common.serialization.SerializationSchema[T]) : org.apache.flink.streaming.api.datastream.DataStreamSink[T] = { /* compiled code */ }) 9 10 //批处理才能使用,即将被弃用 11 reslutStream.writeAsCsv(path : _root_.scala.Predef.String, writeMode : org.apache.flink.core.fs.FileSystem.WriteMode) 12 13 //需要传入自定义的 Sink 写入文件 14 inputStreamFromFile.addSink( 15 StreamingFileSink.forRowFormat( 16 new Path("D:\\de"), 17 new SimpleStringEncoder[String]("UTF-8")) 18 .build()) 19 20 //写出 kafka 21 dataStream.addSink(new FlinkKafkaProducer011[String]("localhost:9092","sinkTest",new SimpleStringSchema()))
execute 程序触发
所有的计算逻辑全部操作定义好之后,需要调用 ExecutionEnvironment的 execute()方法来触发应用程序的执行,因为flink在执行前会先构建执行图,再执行。其中 execute()方法返回的结果类型为 JobExecutionResult,里面包含了程序执行的时间和累加器等指标。需要注意的是,execute方法调用会因为应用的类型有所不同,DataStream流式应用需要显性地指定 execute()方法运行程序,如果不调用则 Flink流式程序不会执行,但对于 DataSet API输出算子中已经包含对 execute()方法的调用,则不需要显性调用 execute()方法,否则会出现程序异常。
1 //调用 StreamExecutionEnvironment 的 execute 方法执行流式应用程序 2 env.execute("App Name");
物理分组
类型系统
Flink 它里面的抽象都是强类型的,与它自身的序列化和反序列化机制有关。这个引擎对类型信息知道的越多,就可以对数据进行更充足的优化,序列化与反序列化就会越快。每一个DataStream 里面都需要有一个明确的类型和TypeInformation,Flink内置了如下类型,都提供了对应的 TypeInfomation。
API 原理