Flink 1.8
Flink DataSet API Programming Guide
Flink中的DataSet程序是实现数据集转换的常规程序(例如,Filter,映射,连接,分组)。数据集最初是从某些source数据源创建的(例如,通过读取文件或从本地集合创建)。结果通过接收器sink返回,例如通过sink将数据写入(分布式)文件或标准输出(例如命令行终端)。Flink程序可以在各种环境中运行,独立运行或嵌入其他程序中。执行可以在本地JVM中执行,也可以在许多计算机的集群上执行。
有关Flink API的基本概念的介绍,请参阅basic concepts。
为了创建您自己的Flink DataSet程序,我们鼓励您从剖析Flink程序开始,并逐步添加您自己的 transformations。其余部分作为其他操作和高级特性的参考。
示例程序
以下程序是WordCount的完整工作示例。您可以复制并粘贴代码以在本地运行它。您只需要在项目中包含正确的Flink库(请参见 Linking with Flink)并指定导入。那你就准备好了!
public class WordCountExample {
public static void main(String[] args) throws Exception {
final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
DataSet<String> text = env.fromElements(
"Who's there?",
"I think I hear them. Stand, ho! Who's there?");
DataSet<Tuple2<String, Integer>> wordCounts = text
.flatMap(new LineSplitter())
.groupBy(0)
.sum(1);
wordCounts.print();
}
public static class LineSplitter implements FlatMapFunction<String, Tuple2<String, Integer>> {
@Override
public void flatMap(String line, Collector<Tuple2<String, Integer>> out) {
for (String word : line.split(" ")) {
out.collect(new Tuple2<String, Integer>(word, 1));
}
}
}
}
DataSet Transformations
数据转换将一个或多个数据集转换为新的数据集。程序可以将多个转换transformations组合成复杂的程序集。
本节简要概述可用的转换transformations。transformations documentation用示例完整地描述了所有转换。
Transformation | Description |
Map | 获取一个元素并生成一个元素。 data.map(new MapFunction<String, Integer>() { public Integer map(String value) { return Integer.parseInt(value); } }); |
FlatMap | 获取一个元素并生成零个、一个或多个元素。 data.flatMap(new FlatMapFunction<String, String>() { public void flatMap(String value, Collector<String> out) { for (String s : value.split(" ")) { out.collect(s); } } }); |
MapPartition | 在单个函数调用中转换并行分区。该函数将分区作为Iterable流来获取,并且可以生成任意数量的结果值。每个分区中的数据的数量取决于并行度和先前的操作 算子。 data.mapPartition(new MapPartitionFunction<String, Long>() { public void mapPartition(Iterable<String> values, Collector<Long> out) { long c = 0; for (String s : values) { c++; } out.collect(c); } }); |
Filter | 为每个元素计算布尔函数,并保留函数返回为true的元素。 重要信息:系统假定该函数不会修改应用谓词的数据元。违反此假设可能会导致错误的结果。 data.filter(new FilterFunction<Integer>() { public boolean filter(Integer value) { return value > 1000; } }); |
Reduce | 通过将两个元素反复组合为一个元素,将一组元素组合为一个元素。Reduce可以应用于完整的数据集或分组的数据集。 data.reduce(new ReduceFunction<Integer> { public Integer reduce(Integer a, Integer b) { return a + b; } }); 如果将reduce应用于分组数据集,则可以通过向setCombineHint提供一个CombineHint来指定运行时执行reduce的组合阶段的方式。在大多数情况下,基于散列的策略应该更快,特别是当不同键的数量相对于输入元素的数量(例如,1/10)。 |
ReduceGroup | 将一组元素组合成一个或多个元素。ReduceGroup可以应用于完整的数据集,也可以应用于分组的数据集。 data.reduceGroup(new GroupReduceFunction<Integer, Integer> { public void reduce(Iterable<Integer> values, Collector<Integer> out) { int prefixSum = 0; for (Integer i : values) { prefixSum += i; out.collect(prefixSum); } } }); |
Aggregate | 将一组值聚合为单个值。聚合函数可以看作是内置的reduce函数。聚合可以应用于完整的数据集,也可以应用于分组的数据集。 Dataset<Tuple3<Integer, String, Double>> input = // [...] DataSet<Tuple3<Integer, String, Double>> output = input.aggregate(SUM, 0).and(MIN, 2); 您还可以使用简写语法进行最小,最大和总和聚合。 Dataset<Tuple3<Integer, String, Double>> input = // [...] DataSet<Tuple3<Integer, String, Double>> output = input.sum(0).andMin(2); |
Distinct | 返回数据集中不同的元素。它从输入数据集中删除与元素的所有字段或字段子集相关的重复条目。 data.distinct(); Distinct是使用reduce函数实现的。您可以通过向setCombineHint提供一个CombineHint来指定运行时执行reduce的组合阶段的方式。在大多数情况下,基于散列的策略应该更快,特别是当不同键的数量相对于输入元素的数量(例如,1/10)。 |
Join | 通过创建键上相等的所有元素对来连接两个数据集。可选地使用JoinFunction将这对元素转换为单个元素,或者使用FlatJoinFunction将这对元素转换为任意多个(包括none)元素。参见keys部分了解如何定义联接键。 result = input1.join(input2) .where(0) // key of the first input (tuple field 0) .equalTo(1); // key of the second input (tuple field 1) 您可以通过连接提示指定运行时执行连接的方式。这些提示描述了连接是通过分区还是广播进行的,以及连接是使用基于排序的算法还是基于散列的算法。有关可能的提示和示例的列表,请参考Transformations Guide。如果没有指定提示,系统将尝试估计输入大小,并根据这些估计选择最佳策略。 // This executes a join by broadcasting the first data set // using a hash table for the broadcast data result = input1.join(input2, JoinHint.BROADCAST_HASH_FIRST) .where(0).equalTo(1); 注意,连接转换只适用于等连接。其他连接类型需要使用OuterJoin或CoGroup来表示。 |
OuterJoin | 对两个数据集执行左、右或完全外连接。外部连接类似于常规(内部)连接,创建键上相等的所有元素对。此外,如果在另一侧没有找到匹配的key,则保存“外部”侧的记录数据(如果是完全连接full的,则保存左、右或两者)。将匹配的元素对(或一个元素和另一个输入的null值)提供给JoinFunction,以便将这对元素转换为单个元素,或者提供给FlatJoinFunction,以便将这对元素转换为任意多个(包括none)元素。参见keys部分 keys section了解如何定义联接键。 input1.leftOuterJoin(input2) // rightOuterJoin or fullOuterJoin for right or full outer joins .where(0) // key of the first input (tuple field 0) .equalTo(1) // key of the second input (tuple field 1) .with(new JoinFunction<String, String, String>() { public String join(String v1, String v2) { // NOTE: // - v2 might be null for leftOuterJoin // - v1 might be null for rightOuterJoin // - v1 OR v2 might be null for fullOuterJoin } }); |
CoGroup | reduce 算子操作的二维变体。将一个或多个字段上的每个输入分组,然后关联组。每对组调用转换函数。请参阅 keys section以了解如何定义coGroup键。 data1.coGroup(data2) .where(0) .equalTo(1) .with(new CoGroupFunction<String, String, String>() { public void coGroup(Iterable<String> in1, Iterable<String> in2, Collector<String> out) { out.collect(...); } }); |
Cross | 构建两个输入的笛卡尔积(交叉乘积),创建所有元素对。可选地使用CrossFunction 将一对元素转换为单个元素 DataSet<Integer> data1 = // [...] DataSet<String> data2 = // [...] DataSet<Tuple2<Integer, String>> result = data1.cross(data2); 注意:Cross可能是一个计算非常密集型的操作,它甚至可以挑战大型计算集群!建议使用crossWithTiny()和crossWithHuge()提示系统数据集的大小。 |
Union | 生成两个数据集的并集。 DataSet<String> data1 = // [...] DataSet<String> data2 = // [...] DataSet<String> result = data1.union(data2); |
Rebalance | 均匀地重新平衡数据集的并行分区,以消除数据倾斜。只有类似Map的转换可能会遵循Rebalance 转换。 DataSet<String> in = // [...] DataSet<String> result = in.rebalance() .map(new Mapper()); |
Hash-Partition | 按给定键对数据集进行哈希分区。key键可以指定为位置键、表达式键和键选择器函数(position keys, expression keys, and key selector functions)。 DataSet<Tuple2<String,Integer>> in = // [...] DataSet<Integer> result = in.partitionByHash(0) .mapPartition(new PartitionMapper()); |
Range-Partition | Range-Partition给定键上的数据集。 key键可以指定为位置键、表达式键和键选择器函数(position keys, expression keys, and key selector functions)。 DataSet<Tuple2<String,Integer>> in = // [...] DataSet<Integer> result = in.partitionByRange(0) .mapPartition(new PartitionMapper()); |
Custom Partitioning | 使用自定义分区器函数根据特定分区的键分配记录。 key键可以指定为位置键、表达式键和键选择器函数(position keys, expression keys, and key selector functions)。 注意:此方法只适用于单个字段键。 DataSet<Tuple2<String,Integer>> in = // [...] DataSet<Integer> result = in.partitionCustom(partitioner, key) .mapPartition(new PartitionMapper()); |
Sort Partition | 本地按指定顺序对指定字段上的数据集的所有分区进行排序。可以将字段指定为元组位置或字段表达式。通过链接sortPartition()调用来完成对多个字段的排序。 DataSet<Tuple2<String,Integer>> in = // [...] DataSet<Integer> result = in.sortPartition(1, Order.ASCENDING) .mapPartition(new PartitionMapper()); |
First-n | 返回数据集的前n个(任意)数据元。First-n可以应用于常规数据集,分组数据集或分组排序数据集。分组键可以指定为键选择器( key-selector)函数或字段位置键。 DataSet<Tuple2<String,Integer>> in = // [...] // regular data set DataSet<Tuple2<String,Integer>> result1 = in.first(3); // grouped data set DataSet<Tuple2<String,Integer>> result2 = in.groupBy(0) .first(3); // grouped-sorted data set DataSet<Tuple2<String,Integer>> result3 = in.groupBy(0) .sortGroup(1, Order.ASCENDING) .first(3); |
Aggregate Demo:
public class AggregateDemo {
public static void main(String[] args) {
try {
final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(2);
Tuple3<String, Integer, Integer> tuple1 = new Tuple3<>("aa", 1, -5);
Tuple3<String, Integer, Integer> tuple2 = new Tuple3<>("bb", 2, -1);
Tuple3<String, Integer, Integer> tuple3 = new Tuple3<>("cc", 3, 3);
Tuple3<String, Integer, Integer> tuple4 = new Tuple3<>("dd", 4, -2);
Tuple3<String, Integer, Integer> tuple5 = new Tuple3<>("ee", -5, 2);
Tuple3<String, Integer, Integer> tuple6 = new Tuple3<>("ff", 6, 9);
Tuple3<String, Integer, Integer> tuple7 = new Tuple3<>("hh", -1, 0);
Tuple3<String, Integer, Integer> tuple8 = new Tuple3<>("jj", -3, 6);
DataSet<Tuple3<String, Integer, Integer>> text = env.fromElements(tuple1, tuple2, tuple3, tuple4, tuple5, tuple6, tuple7, tuple8).setParallelism(2);
text.aggregate(Aggregations.SUM, 1).andMin(2).print();
// env.execute("AggregateDemo");
} catch (Exception e) {
e.printStackTrace();
}
}
}
Distinct Demo:
public class DistinctDemo {
public static void main(String[] args) {
try {
final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(2);
Tuple3<String, Integer, Integer> tuple1 = new Tuple3<>("aa", 1, -5);
Tuple3<String, Integer, Integer> tuple2 = new Tuple3<>("bb", 2, -1);
Tuple3<String, Integer, Integer> tuple3 = new Tuple3<>("bb", 2, -1);
Tuple3<String, Integer, Integer> tuple4 = new Tuple3<>("dd", 4, -2);
Tuple3<String, Integer, Integer> tuple5 = new Tuple3<>("ee", -5, 2);
Tuple3<String, Integer, Integer> tuple6 = new Tuple3<>("ff", 6, 9);
Tuple3<String, Integer, Integer> tuple7 = new Tuple3<>("hh", -1, 0);
Tuple3<String, Integer, Integer> tuple8 = new Tuple3<>("jj", -3, 6);
DataSet<Tuple3<String, Integer, Integer>> text = env.fromElements(tuple1, tuple2, tuple3, tuple4, tuple5, tuple6, tuple7, tuple8).setParallelism(2);
text.distinct().print();
// env.execute("AggregateDemo");
} catch (Exception e) {
e.printStackTrace();
}
}
}
CoGroup Demo:
package org.apache.flink.examples.java.dataBatchAPI;
import org.apache.flink.api.common.functions.CoGroupFunction;
import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.api.java.DataSet;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.tuple.Tuple3;
import org.apache.flink.util.Collector;
public class CoGroupDemo {
public static void main(String[] args) {
try {
final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
Tuple3<String, Integer, Integer> tuple1 = new Tuple3<>("aa", 1, -5);
Tuple3<String, Integer, Integer> tuple2 = new Tuple3<>("bb", 2, -1);
Tuple3<String, Integer, Integer> tuple3 = new Tuple3<>("cc1", 2, -1);
Tuple3<String, Integer, Integer> tuple4 = new Tuple3<>("ddc", 4, -2);
Tuple3<String, Integer, Integer> tuple5 = new Tuple3<>("aa", -5, 2);
Tuple3<String, Integer, Integer> tuple6 = new Tuple3<>("bb", 6, 9);
Tuple3<String, Integer, Integer> tuple7 = new Tuple3<>("cc", -1, 0);
Tuple3<String, Integer, Integer> tuple8 = new Tuple3<>("ddd", -3, 6);
Tuple3<String, Integer, Integer> tuple9 = new Tuple3<>("ddd", -3, 6);
DataSet<Tuple3<String, Integer, Integer>> text = env.fromElements(tuple1, tuple2, tuple3, tuple4);
DataSet<Tuple3<String, Integer, Integer>> text1 = env.fromElements(tuple5, tuple6, tuple7, tuple8,tuple9);
text.coGroup(text1).where(v -> v.f0).equalTo(v -> v.f0).with(
new CoGroupFunction<Tuple3<String, Integer, Integer>, Tuple3<String, Integer, Integer>, String>() {
@Override
public void coGroup(Iterable<Tuple3<String, Integer, Integer>> first, Iterable<Tuple3<String, Integer, Integer>> second, Collector<String> out) throws Exception {
System.out.println("***********************");
for (Tuple3<String, Integer, Integer> t3 : first) {
System.out.println("------" + t3.toString());
}
System.out.println("+++++++++++++++++++++++++");
for (Tuple3<String, Integer, Integer> t3 : second) {
System.out.println("-------------" + t3.toString());
}
}
}
).print();
// env.execute("AggregateDemo");
} catch (Exception e) {
e.printStackTrace();
}
}
}
以下转换可用于元组的数据集:
Transformation | Description |
Project | 从元组中选择字段的子集 DataSet<Tuple3<Integer, Double, String>> in = // [...] DataSet<Tuple2<String, Integer>> out = in.project(2,0); |
MinBy / MaxBy | 从一组元组中选择一个元组,其元组的一个或多个字段的值最小(最大)。用于比较的字段必须是有效的关键字段,即可比较。如果多个元组具有最小(最大)字段值,则返回这些元组的任意元组。MinBy(MaxBy)可以应用于完整数据集或分组数据集。 DataSet<Tuple3<Integer, Double, String>> in = // [...] // a DataSet with a single tuple with minimum values for the Integer and String fields. DataSet<Tuple3<Integer, Double, String>> out = in.minBy(0, 2); // a DataSet with one tuple for each group with the minimum value for the Double field. DataSet<Tuple3<Integer, Double, String>> out2 = in.groupBy(2) .minBy(1); |
package org.apache.flink.examples.java.dataBatchAPI;
import org.apache.flink.api.common.operators.Order;
import org.apache.flink.api.java.DataSet;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.operators.SortPartitionOperator;
import org.apache.flink.api.java.tuple.Tuple3;
public class MinByDemo {
public static void main(String[] args) {
try {
final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(2);
Tuple3<String, Integer, Integer> tuple1 = new Tuple3<>("aa", 1, -5);
Tuple3<String, Integer, Integer> tuple2 = new Tuple3<>("bb", 2, -1);
Tuple3<String, Integer, Integer> tuple3 = new Tuple3<>("cc", 3, 3);
Tuple3<String, Integer, Integer> tuple4 = new Tuple3<>("dd", 4, -2);
Tuple3<String, Integer, Integer> tuple5 = new Tuple3<>("ee", -5, 2);
Tuple3<String, Integer, Integer> tuple6 = new Tuple3<>("ff", -5, -2);
Tuple3<String, Integer, Integer> tuple7 = new Tuple3<>("hh", -1, 0);
Tuple3<String, Integer, Integer> tuple8 = new Tuple3<>("jj", -3, 6);
DataSet<Tuple3<String, Integer, Integer>> text = env.fromElements(tuple1, tuple2, tuple3, tuple4, tuple5, tuple6, tuple7, tuple8);
text.minBy(1,2).print();
} catch (Exception e) {
e.printStackTrace();
}
}
}
转换的并行性可以由setParallelism(int)定义,而name(String)将自定义名称分配给转换,这有助于调试。对于数据源和数据接收器也是一样的。
withParameters(Configuration)传递配置对象,这些对象可以从user函数中的open()方法访问。
Data Sources
数据源创建初始数据集,例如来自文件或Java集合的数据集。在InputFormat后面抽象了创建数据集的一般机制。Flink提供了几种内置格式,可以从常见的文件格式创建数据集。其中许多在ExecutionEnvironment上有快捷方法。
File-based:
- readTextFile(path)/ TextInputFormat- 按行读取文件并将其作为字符串返回。
- readTextFileWithValue(path)/ TextValueInputFormat- 按行读取文件并将它们作为StringValues返回。StringValues是可变字符串。
- readCsvFile(path)/ CsvInputFormat- 解析逗号(或其他字符)分隔字段的文件。返回元组或POJO的DataSet。支持基本java类型及其Value对应作为字段类型。
- readFileOfPrimitives(path, Class)/ PrimitiveInputFormat- 解析新行(或其他字符序列)分隔的原始数据类型(如String或Integer)的文件。
- readFileOfPrimitives(path, delimiter, Class)/ PrimitiveInputFormat- 解析新行(或其他字符序列)分隔的原始数据类型的文件,例如String或Integer使用给定的分隔符。
Collection-based:
- fromCollection(Collection) - 从Java Java.util.Collection创建数据集。集合中的所有数据元必须属于同一类型。
- fromCollection(Iterator, Class) - 从迭代器创建数据集。该类指定迭代器返回的数据元的数据类型。
- fromElements(T ...) - 根据给定的对象序列创建数据集。所有对象必须属于同一类型。
- fromParallelCollection(SplittableIterator, Class) - 并行地从迭代器创建数据集。该类指定迭代器返回的数据元的数据类型。
- generateSequence(from, to) - 并行生成给定间隔中的数字序列。
Generic:
- readFile(inputFormat, path)/ FileInputFormat- 接受文件输入格式。
- createInput(inputFormat)/ InputFormat- 接受通用输入格式。
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
// 按行读取本地文件
DataSet<String> localLines = env.readTextFile("file:///path/to/my/textfile");
// 从运行在nnHost:nnPort上的HDFS读取文本文件
DataSet<String> hdfsLines = env.readTextFile(
"hdfs://nnHost:nnPort/path/to/my/textfile");
// 读取包含三个字段的CSV文件
DataSet<Tuple3<Integer, String, Double>> csvInput
= env.readCsvFile("hdfs:///the/CSV/file").types
(Integer.class, String.class, Double.class);
// 读取一个包含五个字段的CSV文件,只取其中的两个字段
DataSet<Tuple2<String, Double>> csvInput =
env.readCsvFile("hdfs:///the/CSV/file")
.includeFields("10010") // take the first and the fourth field
.types(String.class, Double.class);
// 将包含三个字段的CSV文件读入包含相应字段的POJO (Person.class)
DataSet<Person>> csvInput = env.readCsvFile("hdfs:///the/CSV/file")
.pojoType(Person.class, "name", "age", "zipcode");
//从SequenceFileInputFormat类型的指定路径读取文件
DataSet<Tuple2<IntWritable, Text>> tuples =
env.createInput(HadoopInputs.readSequenceFile(IntWritable.class,
Text.class, "hdfs://nnHost:nnPort/path/to/file"));
// 从一些给定的元素创建一个集合
DataSet<String> value = env.fromElements("Foo", "bar", "foobar", "fubar");
// 生成一个数字序列
DataSet<Long> numbers = env.generateSequence(1, 10000000);
// 使用JDBC输入格式从关系数据库读取数据
DataSet<Tuple2<String, Integer> dbData =
env.createInput(
JDBCInputFormat.buildJDBCInputFormat()
.setDrivername("org.apache.derby.jdbc.EmbeddedDriver")
.setDBUrl("jdbc:derby:memory:persons")
.setQuery("select name, age from persons")
.setRowTypeInfo(new RowTypeInfo(BasicTypeInfo.
STRING_TYPE_INFO, BasicTypeInfo.INT_TYPE_INFO))
.finish()
);
//注意:Flink的程序编译器需要推断InputFormat返回的数据项的数据类型。
//如果不能自动推断此信息,则需要手动提供如上例所示的类型信息。
配置CSV解析
Flink为CSV解析提供了许多配置选项:
- types(Class ... types)指定要解析的字段的类型。必须配置已解析字段的类型。 在类型为Boolean.class的情况下,“True”(不区分大小写),“False”(不区分大小写),“1”和“0”被视为布尔值。
- lineDelimiter(String del)指定单个记录的分隔符。默认行分隔符是换行符'\n'。
- fieldDelimiter(String del)指定用于分隔记录字段的分隔符。默认字段分隔符是逗号字符','。
- includeFields(boolean ... flag),includeFields(String mask)或includeFields(long bitMask)定义从输入文件中读取哪些字段(以及要忽略的字段)。默认情况下,将解析前n个字段(由types()调用中的类型数定义)。
- parseQuotedStrings(char quoteChar)启用带引号的字符串解析。如果字符串字段的第一个字符是引号字符(字符串的前后有空字符串不会被trim),则字符串将被解析为带引号的字符串。引用字符串中的字段分隔符将被忽略。如果带引号的字符串字段的最后一个字符不是引号字符,或者引号字符出现在某个不是引用字符串字段的开头或结尾的点上(除非引号字符使用''转义,否则引用字符串解析失败)。如果启用了带引号的字符串解析并且该字段的第一个字符不是引用字符串,则该字符串将被解析为不带引号的字符串。默认情况下,禁用带引号的字符串解析。
- ignoreComments(String commentPrefix)指定注释前缀。所有以指定注释前缀开头的行都不会被解析和忽略。默认情况下,不会忽略任何行。
- ignoreInvalidLines()启用宽松解析,即忽略无法正确解析的行。默认情况下,禁用宽松解析,无效行引发异常。
- ignoreFirstLine()配置InputFormat以忽略输入文件的第一行。默认情况下,不会忽略任何行。
Recursive Traversal of the Input Path Directory递归遍历输入路径目录
对于基于文件的输入,当输入路径是目录时,默认情况下不会枚举嵌套文件。相反,只读取基目录中的文件,而忽略嵌套文件。可以通过recursive.file.enumeration配置参数启用嵌套文件的递归枚举,如下例所示。
// 启用嵌套输入文件的递归枚举
val env = ExecutionEnvironment.getExecutionEnvironment
//创建配置对象
val parameters = new Configuration
// 设置递归枚举参数
parameters.setBoolean("recursive.file.enumeration", true)
// 将配置传递给数据源
env.readTextFile("file:///path/with.nested/files").withParameters(parameters)
Read Compressed Files读压缩文件
如果用适当的文件扩展名标记输入文件,Flink目前支持透明的输入文件解压缩。特别是,这意味着不需要进一步配置输入格式,任何FileInputFormat都支持压缩,包括自定义输入格式。请注意,压缩文件可能无法并行读取,从而影响作业的可伸缩性。
下表列出了当前支持的压缩方法。
压缩方法 | 文件扩展名 | 可并行 |
DEFLATE | .deflate | no / not |
GZip | .gz, .gzip | no / not |
Bzip2 | .bz2 | no / not |
XZ | .xz | no / not |
Data Sinks
Data sinks使用数据集DataSets,并用于存储或返回数据集。数据接收操作使用OutputFormat进行描述。Flink自带多种内置输出格式,封装在数据集操作之后:
- writeAsText()/ TextOutputFormat-将元素按行写入字符串。通过调用每个元素的toString()方法获得字符串。
- writeAsFormattedText()/ TextOutputFormat-将元素按行写成字符串。通过为每个元素调用用户定义的format()方法来获得字符串。
- writeAsCsv(...)/ CsvOutputFormat- 将元组写为逗号分隔值文件。行和字段分隔符是可配置的。每个字段的值来自对象的toString()方法。
- print()/ printToErr()/ print(String msg)/ printToErr(String msg)- 在标准输出/标准错误流上打印每个元素的toString()值。此外,还可以提供前缀(msg)作为输出的前缀。这有助于区分不同的打印调用。如果并行度大于1,输出也将以生成输出的任务的标识符作为前缀。
- write()/ FileOutputFormat- 自定义文件输出的方法和基类。支持自定义对象到字节的转换。
- output()/ OutputFormat- 大多数通用输出方法,用于非基于文件的数据接收器data sinks(例如将结果存储在数据库中)。
可以将DataSet输入到多个 算子操作。程序可以编写或打印数据集,同时对其运行额外的转换。
Examples
标准数据接收方法:
// 文本数据
DataSet<String> textData = // [...]
// 将数据集写入本地文件系统上的文件
textData.writeAsText("file:///my/result/on/localFS");
// 将DataSet写入HDFS上的文件,并在nnHost:nnPort上运行namenode
textData.writeAsText("hdfs://nnHost:nnPort/my/result/on/localFS");
// 将数据集写入文件,如果文件存在,则重写该文件
textData.writeAsText("file:///my/result/on/localFS", WriteMode.OVERWRITE);
// 元组作为行,管道作为分隔符“a|b|c”
DataSet<Tuple3<String, Integer, Double>> values = // [...]
values.writeAsCsv("file:///path/to/the/result/file", "\n", "|");
// 这将在文本格式“(a, b, c)”中写入元组。而不是CSV行
values.writeAsText("file:///path/to/the/result/file");
// 将使用用户定义的TextFormatter对象将值写入字符串
values.writeAsFormattedText("file:///path/to/the/result/file",
new TextFormatter<Tuple2<Integer, Integer>>() {
public String format (Tuple2<Integer, Integer> value) {
return value.f1 + " - " + value.f0;
}
});
使用自定义输出格式:
public class CustomOutputDemo {
public static void main(String[] args) {
try {
final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(2);
// Tuple3<String, Integer, Integer> tuple1 = new Tuple3<>("aa", 1, -5);
// Tuple3<String, Integer, Integer> tuple2 = new Tuple3<>("bb", 2, -1);
// Tuple3<String, Integer, Integer> tuple3 = new Tuple3<>("cc", 3, 3);
// Tuple3<String, Integer, Integer> tuple4 = new Tuple3<>("dd", 4, -2);
// Tuple3<String, Integer, Integer> tuple5 = new Tuple3<>("ee", -5, 2);
// Tuple3<String, Integer, Integer> tuple6 = new Tuple3<>("ff", -5, -2);
// Tuple3<String, Integer, Integer> tuple7 = new Tuple3<>("hh", -1, 0);
// Tuple3<String, Integer, Integer> tuple8 = new Tuple3<>("jj", -3, 6);
Row row = new Row(2);
row.setField(0, 1);
row.setField(1, 2);
Row row1 = new Row(2);
row1.setField(0, 3);
row1.setField(1, 4);
Row row2 = new Row(2);
row2.setField(0, 5);
row2.setField(1, 6);
DataSet<Row> text = env.fromElements(row, row1, row2);
//
text.output(
// build and configure OutputFormat
JDBCOutputFormat.buildJDBCOutputFormat()
.setDrivername("org.apache.derby.jdbc.EmbeddedDriver")
.setDBUrl("jdbc:derby:memory:persons")
.setQuery("insert into persons (name, age, height) values (?,?,?)")
.finish()
);
} catch (Exception e) {
e.printStackTrace();
}
}
}
本地排序输出
数据data sink的输出可以使用元组字段位置或字段表达式( tuple field positions or field expressions.)按指定顺序对指定字段进行本地排序。这适用于每种输出格式。
下面的例子展示了如何使用这个功能:
DataSet<Tuple3<Integer, String, Double>> tData = // [...]
DataSet<Tuple2<BookPojo, Double>> pData = // [...]
DataSet<String> sData = // [...]
// sort output on String field in ascending order
tData.sortPartition(1, Order.ASCENDING).print();
// sort output on Double field in descending and Integer field in ascending order
tData.sortPartition(2, Order.DESCENDING).sortPartition(0, Order.ASCENDING).print();
// sort output on the "author" field of nested BookPojo in descending order
pData.sortPartition("f0.author", Order.DESCENDING).writeAsText(...);
// sort output on the full tuple in ascending order
tData.sortPartition("*", Order.ASCENDING).writeAsCsv(...);
// sort atomic type (String) output in descending order
sData.sortPartition("*", Order.DESCENDING).writeAsText(...);
尚不支持全局排序的输出。
Iteration Operators
迭代在Flink程序中实现循环。迭代算子封装程序的一部分并重复执行,将一个迭代的结果(部分解决方案)反馈给下一个迭代中。在Flink中有两种类型的迭代:BulkIteration和DeltaIteration。
本节提供有关如何使用这两个算子的快速示例。查看“ Introduction to Iterations”页面以获取更详细的介绍。
Bulk Iterations 批量迭代
要创建BulkIteration,请调用数据集的iterate(int)方法,迭代应该从该方法开始。这将返回一个IterativeDataSet,它可以使用常规操作符进行转换。iterate调用的单个参数指定迭代的最大次数。
要指定迭代的结束,请调用IterativeDataSet上的closeWith(DataSet)方法,以指定应该将哪个转换反馈给下一个迭代。您可以选择使用closeWith(DataSet, DataSet)指定终止条件,如果该数据集为空,则该终止条件将计算第二个数据集并终止迭代。如果没有指定终止条件,迭代将在给定的最大迭代次数之后终止。
下面的示例迭代地估计Pi的值。目标计算落入单位圆的随机点个数。在每个迭代中,随机选择一个点。如果这个点在单位圆内,我们增加计数。然后Pi被估计为结果计数除以迭代次数乘以4。
final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
// Create initial IterativeDataSet
IterativeDataSet<Integer> initial = env.fromElements(0).iterate(10000);
DataSet<Integer> iteration = initial.map(new MapFunction<Integer, Integer>() {
@Override
public Integer map(Integer i) throws Exception {
double x = Math.random();
double y = Math.random();
return i + ((x * x + y * y < 1) ? 1 : 0);
}
});
// Iteratively transform the IterativeDataSet
DataSet<Integer> count = initial.closeWith(iteration);
count.map(new MapFunction<Integer, Double>() {
@Override
public Double map(Integer count) throws Exception {
return count / (double) 10000 * 4;
}
}).print();
env.execute("Iterative Pi Example");
您还可以查看K-Means示例,该示例使用BulkIteration来聚集一组未标记的点。
Delta Iterations 增量迭代
Delta迭代利用了某些算法在每次迭代中不会更改解决方案的每个数据点的事实。
除了在每次迭代中反馈的部分解决方案(called workset:称为工作集)之外,delta迭代还在迭代中维护状态(called solution set:称为解决方案集),可以通过增量更新。迭代计算的结果是最后一次迭代之后的状态。有关delta迭代的基本原理的概述,请参阅Introduction to Iterations。
定义DeltaIteration类似于定义BulkIteration。对于delta迭代,两个数据集构成每次迭代的输入(workset and solution set:工作集和解决方案集),并且在每次迭代中生成两个数据集作为结果(new workset, solution set delta:新工作集,解决方案集delta)。
要创建一个DeltaIteration调用iterateDelta(DataSet, int, int)(或iterateDelta(DataSet, int, int[]))。该方法在初始解集上调用,参数为初始增量集、最大迭代次数和关键位置。利用返回的DeltaIteration对象调用iteration.getWorkset()和iteration.getSolutionSet()方法来访问表示工作集workset和解决方案集workset的数据集。
下面是增量迭代的语法示例:
// read the initial data sets
DataSet<Tuple2<Long, Double>> initialSolutionSet = // [...]
DataSet<Tuple2<Long, Double>> initialDeltaSet = // [...]
int maxIterations = 100;
int keyPosition = 0;
DeltaIteration<Tuple2<Long, Double>, Tuple2<Long, Double>> iteration = initialSolutionSet
.iterateDelta(initialDeltaSet, maxIterations, keyPosition);
DataSet<Tuple2<Long, Double>> candidateUpdates = iteration.getWorkset()
.groupBy(1)
.reduceGroup(new ComputeCandidateChanges());
DataSet<Tuple2<Long, Double>> deltas = candidateUpdates
.join(iteration.getSolutionSet())
.where(0)
.equalTo(0)
.with(new CompareChangesToCurrent());
DataSet<Tuple2<Long, Double>> nextWorkset = deltas
.filter(new FilterByThreshold());
iteration.closeWith(deltas, nextWorkset)
.writeAsCsv(outputPath);
Operating on data objects in functions操作函数中的数据对象
Flink的运行时以Java对象的形式与用户函数交换数据。函数从运行时接收输入对象作为方法参数,并返回输出对象作为结果。由于这些对象是由用户函数和运行时代码访问的,因此理解并遵循有关用户代码如何访问(即读取和修改)这些对象的规则非常重要。
用户函数从Flink的运行时接收对象,作为常规方法参数(如MapFunction)或通过Iterable参数(如GroupReduceFunction)。我们将运行时传递给用户函数的对象称为输入对象。用户函数可以将对象作为方法返回值(如MapFunction)或通过Collector(如FlatMapFunction)发送到Flink运行时。我们将用户函数发出的对象称为输出对象。
Flink的DataSet API具有两种模式,这些模式在Flink的运行时创建或重用输入对象方面有所不同。此行为会影响用户函数如何与输入和输出对象进行交互的保证和约束。以下部分定义了这些规则,并给出了编写安全用户函数代码的编码指南。
禁用对象重用(DEFAULT)
默认情况下,Flink在禁用对象重用模式下运行。此模式可确保函数始终在函数调用中接收新的输入对象。禁用对象重用模式可提供更好的保证,并且使用起来更安全。但是,它带来了一定的处理开销,可能会导致更高的Java垃圾回收活动。下表说明了用户函数如何在禁用对象重用模式下访问输入和输出对象。
Operation | 保证和限制 |
读取输入对象 | 在方法调用之间,保证输入对象的值不变。这包括由迭代器提供的对象。例如,在List或Map中收集由迭代器Iterable提供的输入对象是安全的。注意,对象可以在方法调用结束后修改。在函数调用之间保存对象是不安全的。 |
修改输入对象 | 您可以修改输入对象。 |
发射输入对象 | 您可以发出输入对象。输入对象的值在发出后可能发生了更改。在输入对象发出之后读取它是不安全的。 |
读取输出对象 | 提供给Collector 或作为方法结果返回的对象可能已更改其值。读取输出对象是不安全的。 |
修改输出对象 | 您可以在对象发出之后修改它并再次发出它。 |
禁用对象重用(默认)模式的编码准则:
-
- 不要在方法调用之间保存和读取输入对象。
- 在发出对象之后,不要读取它们。
启用对象重用
在启用对象重用模式下,Flink的运行时最小化了对象实例化的数量。这可以提高性能并降低Java垃圾收集的压力。通过调用ExecutionConfig.enableObjectReuse()激活启用对象重用的模式。下表解释了用户函数如何在启用对象重用模式下访问输入和输出对象。
Operation | 保证和限制 |
读取作为常规方法参数接收的输入对象 | 在常规方法参数中接收的输入对象不会在函数调用中修改。对象可以在方法调用结束后修改。在函数调用之间保存对象是不安全的。 |
读取从Iterable参数接收的输入对象 | 从Iterable接收的输入对象仅在调用next()方法之前有效。Iterable或Iterator可以多次为同一个对象实例提供服务。保存从Iterable接收的输入对象是不安全的,例如,将它们放在List或Map中。 |
修改输入对象 | 除了MapFunction,FlatMapFunction,MapPartitionFunction,GroupReduceFunction,GroupCombineFunction,CoGroupFunction和InputFormat.next(重用)的输入对象外,其他的算子不能修改输入对象。 |
发射输入对象 | 除了MapFunction,FlatMapFunction,MapPartitionFunction,GroupReduceFunction,GroupCombineFunction,CoGroupFunction和InputFormat.next(重用)的输入对象外,其他的算子不能修改输入对象。 |
读取输出对象 | 提供给收集器或作为方法结果返回的对象可能已更改其值。读取输出对象是不安全的。 |
修改输出对象 | 您可以修改输出对象并再次发出它。 |
支持对象重用的编码准则:
- 不要保存从迭代器Iterable接收的输入对象。
- 不要在方法调用之间保存和读取输入对象。
- 除了MapFunction、FlatMapFunction、MapPartitionFunction、GroupReduceFunction、GroupCombineFunction、CoGroupFunction和InputFormat.next(重用)的输入对象之外,其他算子不能修改或发出输入对象。
- 为了减少对象实例化,可以发出一个专用的输出对象,该对象能够被反复修改,但从不读取它。
Debugging
在分布式集群中的大型数据集上运行数据分析程序之前,最好确保实现的算法能够按预期工作。因此,运行数据分析程序通常是一个检查结果、调试和改进的增量过程。
Flink提供了一些很好的特性,通过支持IDE中的本地调试,测试数据的注入和结果数据的收集,显着简化数据分析程序的开发过程。本节提供了一些如何简化Flink程序开发的提示。
Local Execution Environment
LocalEnvironment在创建Flink系统的JVM进程中启动Flink系统。如果从IDE启动LocalEnvironment,可以在代码中设置断点并轻松调试程序。
本地环境的创建和使用如下:
final ExecutionEnvironment env = ExecutionEnvironment.createLocalEnvironment();
DataSet<String> lines = env.readTextFile(pathToTextFile);
// build your program
env.execute();
Collection Data Sources and Sinks收集数据源和接收器
在创建输入文件和读取输出文件时,为分析程序提供输入并检查其输出是很麻烦的。Flink提供由Java集合支持的特殊数据源source和接收器sink,以简化测试。一旦程序经过测试,源和接收器就可以很容易地替换为从外部数据存储(如HDFS)读写数据的源和接收器。
集合数据源的使用方法如下:
final ExecutionEnvironment env = ExecutionEnvironment.createLocalEnvironment();
// Create a DataSet from a list of elements
DataSet<Integer> myInts = env.fromElements(1, 2, 3, 4, 5);
// Create a DataSet from any Java collection
List<Tuple2<String, Integer>> data = ...
DataSet<Tuple2<String, Integer>> myTuples = env.fromCollection(data);
// Create a DataSet from an Iterator
Iterator<Long> longIt = ...
DataSet<Long> myLongs = env.fromCollection(longIt, Long.class);
A collection data sink is specified as follows:
DataSet<Tuple2<String, Integer>> myResult = ...
List<Tuple2<String, Integer>> outData = new ArrayList<Tuple2<String, Integer>>();
myResult.output(new LocalCollectionOutputFormat(outData));
注意:目前,集合collection数据接收器仅限于本地执行,作为调试工具。
注意:目前,集合collection数据源要求数据类型和迭代器实现Serializable。此外,集合collection数据源不能并行执行(parallelism = 1)。
Semantic Annotations语义注释
语义注释可用于提供关于函数行为的Flink提示。它们告诉系统函数读取和计算哪些输入字段,以及未修改的字段从其输入转发到输出。语义注释是加速执行的一种有力手段,因为它允许系统在多个操作算子之间重用排序顺序或分区。使用语义注释最终可以使程序免于不必要的数据混洗或不必要的排序,并显着提高程序的性能。
注意:语义注释的使用是可选的。但是,提供语义注释时保守是绝对至关重要的!不正确的语义注释会导致Flink对您的程序做出错误的假设,并最终可能导致错误的结果。如果操作算子的行为不能清楚地预测,则不应提供注释。请仔细阅读文件。
目前支持以下语义注释。
转发字段注释
转发字段信息声明输入字段,这些输入字段未被修改,由函数转发到相同位置或输出中的另一个位置。优化程序使用此信息来推断函数是否保存了数据属性(如排序或分区)。对于操作输入元素组(如GroupReduce、GroupCombine、CoGroup和MapPartition)的函数,被定义为转发字段的所有字段必须始终从相同的输入元素进行转发。由分组函数发出的每个元素的转发字段可以源自函数输入组的不同元素。
字段转发信息是使用字段表达式 field expressions指定的。被转发到输出中相同位置的字段可以通过它们的位置指定。指定的位置必须对输入和输出数据类型有效,并且具有相同的类型。例如,String "f2"声明Java输入元组的第三个字段总是等于输出元组中的第三个字段。
通过指定输入中的源字段和输出中的目标字段作为字段表达式来声明未修改转发到输出中的另一个位置的字段。String“f0->f2”表示Java输入元组的第一个字段不变地复制到Java输出元组的第三个字段。通配符表达式*可用于引用整个输入或输出类型,即“f0->*”表示函数的输出总是等于其Java输入元组的第一个字段。
可以在单个String中声明多个转发字段,方法是将它们用分号分隔为"f0; f2->f1; f3->f2"(推荐),或者在单独的字符串"f0", "f2->f1", "f3->f2"。指定转发字段时,不要求声明所有转发字段,但所有声明必须正确。
可以通过在函数类定义上附加Java注释或在调用上的函数后将它们作为操作算子参数传递来声明转发的字段信息,如下所示。
转发的字段信息可以通过在函数类定义上附加Java注释来声明,或者在调用数据集DataSet上的函数后将它们作为操作算子参数传递来声明转发的字段信息,如下所示。
函数类的注释
- @ForwardedFields 用于单输入函数,例如Map和Reduce。
- @ForwardedFieldsFirst 用于具有两个输入的函数的第一个输入,例如Join和CoGroup。
- @ForwardedFieldsSecond 用于具有两个输入的函数的第二个输入,例如Join和CoGroup。
算子参数
- data.map(myMapFnc).withForwardedFields() 用于单输入函数,例如Map和Reduce。
- data1.join(data2).where().equalTo().with(myJoinFnc).withForwardFieldsFirst() 对于具有两个输入(例如Join和CoGroup)的函数的第一个输入。
- data1.join(data2).where().equalTo().with(myJoinFnc).withForwardFieldsSecond() 对于具有两个输入的函数的第二个输入,例如Join和CoGroup。
请注意,不能覆盖由操作算子参数指定为类注释的字段转发信息。
下面的例子展示了如何使用函数类注释声明转发的字段信息:
@ForwardedFields("f0->f2")
public class MyMap implements
MapFunction<Tuple2<Integer, Integer>, Tuple3<String, Integer, Integer>> {
@Override
public Tuple3<String, Integer, Integer> map(Tuple2<Integer, Integer> val) {
//将Java输入元组的第一个字段不变地复制到Java输出元组的第三个字段
return new Tuple3<String, Integer, Integer>("foo", val.f1 / 2, val.f0);
}
}
非转发字段
非转发字段信息声明所有未保存在函数输出中相同位置的字段。所有其他字段的值都被视为保存在输出中的相同位置。因此,非转发字段信息与转发字段信息相反。对于分组方式算子(如GroupReduce、GroupCombine、CoGroup和MapPartition)的非转发字段信息必须满足与转发字段信息相同的要求。
重要信息:非转发字段信息的规范是可选的。但如果使用, 必须全部指定非转发字段,因为所有其他字段都被认为是已转发的。将转发字段声明为非转发字段是安全的。
非转发字段被指定为字段表达式 field expressions列表。该列表可以作为单个字符串给出,字段表达式用分号分隔,也可以作为多个字符串。例如"f1; f3"表示:"f1", "f3"声明Java元组的第二个和第四个字段没有保留在适当的位置,所有其他字段都保留在适当的位置。非转发字段信息只能为具有相同输入和输出类型的函数指定。
未转发的字段信息使用以下注释指定为函数类注释:
- @NonForwardedFields 用于单输入函数,例如Map和Reduce。
- @NonForwardedFieldsFirst 对于具有两个输入的函数的第一个输入,例如Join和CoGroup。
- @NonForwardedFieldsSecond 对于具有两个输入的函数的第二个输入,例如Join和CoGroup。
下面的例子展示了如何声明非转发字段信息:
@NonForwardedFields("f1") // second field is not forwarded
public class MyMap implements
MapFunction<Tuple2<Integer, Integer>, Tuple2<Integer, Integer>> {
@Override
public Tuple2<Integer, Integer> map(Tuple2<Integer, Integer> val) {
return new Tuple2<Integer, Integer>(val.f0, val.f1 / 2);
}
}
Read Fields读取字段
读取字段信息声明由函数访问和计算的所有字段,即函数用于计算其结果的所有字段。例如,在指定读取字段信息时,必须将在条件语句中计算或用于计算的字段标记为已读。只有未经修改的字段转发到输出,而没有计算它们的值,或者根本没有访问的字段,这些字段不被认为是可读的。
重要提示:读取字段信息的规范是可选的。但如果使用, 必须全部指定读取字段。将非读取字段声明为读取是安全的。
读取字段被指定为字段表达式field expressions列表。该列表可以作为单个字符串给出,字段表达式用分号分隔,也可以作为多个字符串。例如同时"f1; f3"和"f1", "f3"声明一个Java元组的第二和第四场由函数读取和计算。
使用以下注释将读取字段信息指定为函数类注释:
- @ReadFields 用于单输入函数,例如Map和Reduce。
- @ReadFieldsFirst 对于具有两个输入的函数的第一个输入,例如Join和CoGroup。。
- @ReadFieldsSecond 对于具有两个输入的函数的第二个输入,例如Join和CoGroup。
下面的例子展示了如何声明读取字段信息:
@ReadFields("f0; f3") // f0 and f3 are read and evaluated by the function.
public class MyMap implements
MapFunction<Tuple4<Integer, Integer, Integer, Integer>,
Tuple2<Integer, Integer>> {
@Override
public Tuple2<Integer, Integer> map(Tuple4<Integer, Integer, Integer, Integer> val) {
if(val.f0 == 42) {
return new Tuple2<Integer, Integer>(val.f0, val.f1);
} else {
return new Tuple2<Integer, Integer>(val.f3+10, val.f1);
}
}
}
Broadcast Variables广播变量
除了常规的操作算子输入之外,广播变量允许将数据集提供给操作算子的所有并行实例。这对于辅助数据集或与数据相关的参数化非常有用。然后,算子可以将数据集作为集合进行访问。
- 广播:广播集通过名称注册withBroadcastSet(DataSet, String),和
- 访问:可以通过目标操作算子的getRuntimeContext(). getbroadcastvariable (String)进行访问。
// 1. 要广播的数据集
DataSet<Integer> toBroadcast = env.fromElements(1, 2, 3);
DataSet<String> data = env.fromElements("a", "b");
data.map(new RichMapFunction<String, String>() {
@Override
public void open(Configuration parameters) throws Exception {
// 3. Access the broadcast DataSet as a Collection
Collection<Integer> broadcastSet = getRuntimeContext().getBroadcastVariable("broadcastSetName");
}
@Override
public String map(String value) throws Exception {
...
}
}).withBroadcastSet(toBroadcast, "broadcastSetName"); // 2. 广播数据集
访问广播数据集时,确保名称(上例中的broadcastSetName)匹配。对于完整的示例程序,请查看K-Means算法。
注意:由于广播变量的内容保存在每个节点的内存中,所以它不应该变得太大。对于标量值等更简单的东西,您可以简单地将参数作为函数闭包的一部分,或者使用withParameters(…)方法传入配置。
Distributed Cache分布式缓存
Flink提供了一个分布式缓存,类似于Apache Hadoop,可以让本地文件访问用户函数的并行实例。此函数可用于共享包含静态外部数据的文件,如字典或机器学习的回归模型。
缓存的工作原理如下:程序在其执行环境ExecutionEnvironment 中以特定名称注册本地或远程文件系统(如HDFS或S3)的文件或目录作为缓存文件。执行程序时,Flink会自动将文件或目录复制到所有工作程序的本地文件系统。用户函数可以查找指定名称下的文件或目录,并从worker的本地文件系统访问它。
分布式缓存的使用方法如下:
在ExecutionEnvironment中注册文件或目录。
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
// register a file from HDFS
env.registerCachedFile("hdfs:///path/to/your/file", "hdfsFile")
// register a local executable file (script, executable, ...)
env.registerCachedFile("file:///path/to/exec/file", "localExecFile", true)
// define your program and execute
...
DataSet<String> input = ...
DataSet<Integer> result = input.map(new MyMapper());
...
env.execute();
访问用户函数(这里是MapFunction)中缓存的文件或目录。函数必须扩展RichFunction类,因为它需要访问RuntimeContext。
// extend a RichFunction to have access to the RuntimeContext
public final class MyMapper extends RichMapFunction<String, Integer> {
@Override
public void open(Configuration config) {
// access cached file via RuntimeContext and DistributedCache
File myFile = getRuntimeContext().getDistributedCache().getFile("hdfsFile");
// read the file (or navigate the directory)
...
}
@Override
public Integer map(String value) throws Exception {
// use content of cached file
...
}
}
Passing Parameters to Functions将参数传递给函数
可以使用构造函数或withParameters(配置)方法将参数传递给函数。参数被序列化为函数对象的一部分,并传送到所有并行任务实例。
还要检查关于如何将命令行参数传递给函数的最佳实践指南https://ci.apache.org/projects/flink/flink-docs-release-1.8/dev/best_practices.html#parsing-command-line-arguments-and-passing-them-around-in-your-flink-application。
通过构造函数 :
DataSet<Integer> toFilter = env.fromElements(1, 2, 3);
toFilter.filter(new MyFilter(2));
private static class MyFilter implements FilterFunction<Integer> {
private final int limit;
public MyFilter(int limit) {
this.limit = limit;
}
@Override
public boolean filter(Integer value) throws Exception {
return value > limit;
}
}
通过 withParameters(Configuration):
此方法将Configuration对象作为参数,将其传递给 rich function’s函数的open() 方法。Configuration对象是从String key键到不同值类型的映射Map。
DataSet<Integer> toFilter = env.fromElements(1, 2, 3);
Configuration config = new Configuration();
config.setInteger("limit", 2);
toFilter.filter(new RichFilterFunction<Integer>() {
private int limit;
@Override
public void open(Configuration parameters) throws Exception {
limit = parameters.getInteger("limit", 0);
}
@Override
public boolean filter(Integer value) throws Exception {
return value > limit;
}
}).withParameters(config);
通过ExecutionConfig全局执行:
Flink还允许将自定义配置值传递到ExecutionConfig环境Environment的接口。由于执行配置可在所有(rich)用户函数中访问,因此自定义配置将在所有函数中全局可用。
设置自定义全局配置
Configuration conf = new Configuration();
conf.setString("mykey","myvalue");
final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
env.getConfig().setGlobalJobParameters(conf);
请注意,您还可以将自定义ExecutionConfig.GlobalJobParameters类作为全局job参数传递给执行配置。该接口允许实现Map<String, String> toMap()方法,该方法将依次显示来自Web前端中配置的值。
从全局配置中访问值
全局作业参数中的对象可在系统中的许多位置访问。实现RichFunction接口的所有用户函数都可以通过运行时上下文 (runtime context)访问。
public static final class Tokenizer extends RichFlatMapFunction<String, Tuple2<String, Integer>> {
private String mykey;
@Override
public void open(Configuration parameters) throws Exception {
super.open(parameters);
ExecutionConfig.GlobalJobParameters globalParams = getRuntimeContext().getExecutionConfig().getGlobalJobParameters();
Configuration globConf = (Configuration) globalParams;
mykey = globConf.getString("mykey", null);
}
// ... more here ...
https://flink.sojb.cn/dev/batch/
https://ci.apache.org/projects/flink/flink-docs-release-1.8/dev/batch/