flink初始
flink是什么
flink是一个用于有界和无界数据流进行有状态的计算框架。
flink提供了不同级别的抽象来开发流和批处理应用程序。
- 最底层是Stateful Stream processing,只提供有状态流它 通过Process Function嵌入到DataStream API中。它允许用户自由处理来自一个或多个流的事件,并使用一致的容错状态。此外,用户可以注册事件时间和处理时间回调,允许程序实现复杂的计算。
- DataSet /DataStream,大部分都是根据DataStream API(有界/无界流)和DataSet API (有界数据集)进行编程。这些用于数据处理,提供用户的各种形式的转换,连接,聚合,窗口,状态等。
- Table API,该API提供了类似sql处理的方式,后续优化并转化成DataSet或者是DataStream进行操作表结构的数据源。
- SQL, Flink提供的最高级抽象是SQL。这种抽象在语义和表达方面类似于Table API,但是将程序表示为SQL查询表达式。在SQL抽象与表API紧密地相互作用,和SQL查询可以通过定义表来执行表API
为什么使用Flink
- 提供准确的结果,即使在无序或延迟数据的情况下也是如此
- 具有状态和容错能力,可以在保持应用状态的同时无故障地从故障中恢复
- 大规模执行,在数千个节点上运行,具有非常好的吞吐量和延迟特性
flink的基础概念
- 程序与数据流,Flink程序基本构建块是流和转换,Flink程序都是由一个或者多个数据源开始,以一个或多个接收器结束。而中间是大量的数据转换。
- 并行数据流。Flink中执行的程序每一个占用一个JVM,其中每一个任务占用一个线程。其中数据源source,数据映射map是一对一的,即保留数据最开始产生的顺序,keyBy/windown/apply是数据的重新分配,即这个时候数据会被分配不同的线程中处理。
- 视窗,数据流式一个无止境的,因此flink的聚合是有限的,以窗口为限,如每分钟中流入的数据的总和,窗口就是每一分钟,可以连续。窗口可以分为三种,翻滚窗口(无重叠),滑动窗口(有重叠),会话窗口(无重叠,由不活动间隙打断,即无数据时,窗口结束,活跃时窗口打开)
- 时间。数据可以引入不同的时间,如事件时间即数据产生时的时间戳,获取事件的时间,处理时间的时间,这三个时间特征在使用时特别有用,如flink流入的数据并不保证数据是按照数据的产生顺序流入,在处理过程时可能造成数据的混乱,在flink的机制中,可以根据数据产生的时间戳缓存一定的数据,并在特定顺序内,数据是有序的。
- 容错检查点,Flink使用流重放和检查组合实现容错。检查点与每个输入流中的特定点以及每个操作符的对应状态相关。通过恢复运算符的状态并从检查点重放事件,可以从检查点恢复流数据流,同时保持一致性。
- 流的批处理,flink执行的批处理程序作为流程序的特殊情况,其中流式有界的,而批处理有这些特点。批处理程序的容错不使用检查点,而使用完全重放流来恢复;DataSet API中的有状态操作使用简化的内存/核外数据结构,而不是键/值索引;DataSet API引入了特殊的同步(超级步骤)迭代,这些迭代只能在有界流上进行。
flink剖析
Flink程序看起来像是转换数据集合的常规程序。每个程序包含相同的基本部分
- 获取一个execution environment
- 加载/创建数据源
- 数据的转化
- 数据存储,指定接收器
- 触发并执行
获取execution environment
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
flink需要获取执行的上下文,是flink程序执行的基础。
加载创建数据源
flink的数据源的来源很丰富,文件,hadoop,kafka等都可以作为数据的来源,如DataStream<String> text = env.readTextFile("file:///path/to/file");
从文件中获取流,而后就可以进行对流操作。
flink提供的操作如下:
- readTextFile(path)- TextInputFormat逐行读取文本文件,即符合规范的文件,并将它们作为字符串返回。
- readFile(fileInputFormat, path) - 按指定的文件输入格式指定读取(一次)文件。
- readFile(fileInputFormat, path, watchType, interval, pathFilter, typeInfo) - 这是前两个内部调用的方法。它path根据给定的内容读取文件fileInputFormat。根据提供的内容watchType,此源可以定期监视(每intervalms)新数据(FileProcessingMode.PROCESS_CONTINUOUSLY)的路径,或者处理当前在路径中的数据并退出(FileProcessingMode.PROCESS_ONCE)。使用该pathFilter,用户可以进一步排除正在处理的文件。
- socketTextStream - 从套接字读取。元素可以用分隔符分隔
- fromCollection(Collection) - 从Java Java.util.Collection创建数据流。集合中的所有元素必须属于同一类型
- fromCollection(Iterator, Class) - 从迭代器创建数据流。该类指定迭代器返回的元素的数据类型。
- fromElements(T ...) - 从给定的对象序列创建数据流。所有对象必须属于同一类型
- fromParallelCollection(SplittableIterator, Class) - 并行地从迭代器创建数据流。该类指定迭代器返回的元素的数据类型。
- generateSequence(from, to) - 并行生成给定间隔中的数字序列
- addSource 自定义数据来源,例如kafka的数据来源就需要调用次方法,addSource(new FlinkKafkaConsumer08<>(...));
数据的转化
数据的转化会根据不同的API进行操作,一下仅列举DataStream部分的转换。
- map 映射 数据流向DataStream → DataStream,一对一的,一个元素转化为另一个元素。
DataStream<Integer> dataStream = //...
dataStream.map(new MapFunction<Integer, Integer>() {
@Override
public Integer map(Integer value) throws Exception {
return 2 * value;
}
});
- FlatMap, 映射 数据流向DataStream → DataStream ,一对多即将一个元素转化零个,一个或多个
dataStream.flatMap(new FlatMapFunction<String, String>() {
@Override
public void flatMap(String value, Collector<String> out)
throws Exception {
for(String word: value.split(" ")){
out.collect(word);
}
}
});
- filter 过滤 数据流向DataStream→DataStream ,过滤出自己需要数据
dataStream.filter(new FilterFunction<Integer>() {
@Override
public boolean filter(Integer value) throws Exception {
return value != 0;
}
});
- KeyBy 数据流向DataStream → KeyedStream 分组或者是数据重新分配,相同的键(相同的秘钥)分表相同的分区中。与window组合使用时,keyby分区的数据在某一时刻的聚合
dataStream.keyBy("someKey") //dataStream中元素以POJO组成
dataStream.keyBy(0) //dataStream中元素以Tuple元素组中
也可以可以调用选择器自己调用
dataStream.keyBy(new KeySelector<WikipediaEditEvent, String>() {
@Override
public String getKey(WikipediaEditEvent event) {
return event.getUser();
}
} )
数据接收器
数据接收器,数据接收器可以从数据源中,也可以到数据源中,即源的操作也可以当做数据的接收器,用于存储,从流入flink的数据也可以流入到kafka中,
- print(); 用户数据的打印
- writeAsText(String path) 数据输入到执行文件中
- addSource 自定义数据接收器
执行程序
一旦您指定的完整程序,你需要触发执行程序调用 execute()上StreamExecutionEnvironment。根据执行的类型,ExecutionEnvironment将在本地计算机上触发执行或提交程序以在群集上执行。
该execute()方法返回一个JobExecutionResult,包含执行时间和累加器结果。
程序的执行并不是从main方法开始,而是任务从调用execute开发,而前面的数据源,数据转化,都在不同的线程中执行,而后调用execute执行。
实例
一个简单的程序来描述Flink的整个过程
- 引入pom.xml
<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-java</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_2.11</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients_2.11</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-wikiedits_2.11</artifactId>
<version>${flink.version}</version>
</dependency>
</dependencies>
- 程序
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment see = StreamExecutionEnvironment.getExecutionEnvironment(); //获取执行环境
DataStream<WikipediaEditEvent> edits = see.addSource(new WikipediaEditsSource());//添加数据源
KeyedStream<WikipediaEditEvent, String> keyedEdits = edits
.keyBy(new KeySelector<WikipediaEditEvent, String>() {//获取不同秘钥的数据
@Override
public String getKey(WikipediaEditEvent event) {
return event.getUser();
}
});
DataStream<Tuple2<String, Long>> result = keyedEdits
.timeWindow(Time.seconds(5))
.fold(new Tuple2<>("", 0L), new FoldFunction<WikipediaEditEvent, Tuple2<String, Long>>() {
@Override
public Tuple2<String, Long> fold(Tuple2<String, Long> acc, WikipediaEditEvent event) {
acc.f0 = event.getUser();
acc.f1 += event.getByteDiff();
return acc;
}
});
result.print();//数据打印,用于数据调试
see.execute();//程序执行
}