14.DataStream API之Overview

flink 1.8

Flink DataStream API Programming Guide Flink DataStream API编程指南

 

Flink中的DataStream程序是在数据流上实现转换的常规程序(例如,过滤filtering、更新状态updating state、定义窗口defining windows、聚合aggregating)。数据流最初是由不同的源sources创建的(例如,消息队列message queues、套接字流socket streams、文件files)。结果通过接收器sinks返回,例如,接收器sinks可以将数据写入文件或标准输出(例如命令行终端)。Flink程序在各种上下文contexts中运行,独立运行或嵌入到其他程序中。flink程序可以在本地JVM中运行,也可以在集群上运行。

有关Flink API的基本概念basic concepts的介绍,请参阅基本概念。

为了创建您自己的Flink DataStream程序,我们建议您从剖析Flink(anatomy of a Flink Program)程序开始,并逐步添加您自己的流转换( stream transformations)。其余部分作为其他操作算子和高级特性的参考。

 

Example Program

下面的程序是一个完整的、可工作的流窗口单词计数应用程序示例,它在5秒内计算来自web套接字socket的单词。您可以复制并粘贴代码以在本地运行它。

import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.Collector;

public class WindowWordCount {

    public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        DataStream<Tuple2<String, Integer>> dataStream = env
                .socketTextStream("localhost", 9999)
                .flatMap(new Splitter())
                .keyBy(0)
                .timeWindow(Time.seconds(5))
                .sum(1);

        dataStream.print();

        env.execute("Window WordCount");
    }

    public static class Splitter implements FlatMapFunction<String, Tuple2<String, Integer>> {
        @Override
        public void flatMap(String sentence, Collector<Tuple2<String, Integer>> out) throws Exception {
            for (String word: sentence.split(" ")) {
                out.collect(new Tuple2<String, Integer>(word, 1));
            }
        }
    }

}

要运行示例程序,首先从终端用netcat启动输入流:

nc -lk 9999

只需键入一些语句就可以返回一个个新单词。这些将作为单词计数程序的输入。如果你想看到大于1的计数,请在5秒内反复键入相同的单词(如果无法快速输入,则将窗口大小从5秒增加到n秒☺)。

 

Data Sources

Sources是程序读取输入数据的地方。可以使用StreamExecutionEnvironment.addSource(sourceFunction)将source关联到程序中。Flink附带了许多预实现pre-implemented的source functions,但是您可以通过为non-parallel sources实现SourceFunction,或者通过为parallel sources实现ParallelSourceFunction接口或扩展RichParallelSourceFunction来自定义自己的source。

有几个预定义的流sources 可以从StreamExecutionEnvironment访问:

  • readTextFile(path) - 读取文本文件,也就是说,文件遵循TextInputFormat规范,逐行返回字符串。
  • readFile(fileInputFormat, path) -按照指定的文件输入格式读取(一次)文件。
  • readFile(fileInputFormat, path, watchType, interval, pathFilter, typeInfo) -这是前两个在内部调用的方法。它根据给定的fileInputFormat读取路径path中的文件。根据所提供的watchType,此source可以定期(每隔一段时间interval ms)监视新数据的路径(fileprocessingmode . process_continuous),或者一次性处理当前路径中的数据并退出(FileProcessingMode.PROCESS_ONCE)。使用该pathFilter,用户可以进一步排除正在处理的文件。
  •  

IMPLEMENTATION:实现

在底层,Flink将文件读取过程分成两个子任务sub-tasks,即目录监视directory monitoring和数据读取data reading。每个子任务sub-tasks都由一个单独的实体实现。监视由单个non-parallel(parallelism = 1)任务task实现,而读取由多个并行运行的任务task执行。后者的并行度parallelism等于作业job并行度parallelism。单个监视任务的作用是扫描目录(定期或仅扫描一次,这取决于watchType),找到要处理的文件,将它们拆分成多个部分,并将这些部分分配给下游的读取器。读取器readers将读取实际数据。每个拆分只能由一个读取器readers读取,而一个读取器可以逐个读取多个拆分。

IMPORTANT NOTES:注意事项

  1. 如果watchType设置为FileProcessingMode.PROCESS_CONTINUOUSLY,当一个文件被修改时,它的内容被完全重新处理。这可能会破坏“exactly-once”语义,因为在文件末添加数据将导致重新处理所有内容。
  2. 如果watchType设置为FileProcessingMode.PROCESS_ONCE,源source扫描路径一次并退出,而不会等待读取器readers完成文件内容的读取。当然,读取器readers将继续读取数据,直到所有的文件内容都被读取完成为止。关闭源source将导致在该点point之后不再有检查点checkpoints。这可能导致节点故障后恢复时较慢,因为作业job将从最后一个检查点恢复读取文件内容。

 

Socket-based:

  • socketTextStream - 从套接字读取。元素可以用分隔符分隔。

 

Collection-based:

  • fromCollection(Collection) - 从Java Java.util. collection创建数据流。集合中的所有元素必须具有相同的类型。
  • fromCollection(Iterator, Class) -从迭代器创建数据流。该类指定迭代器返回的元素的数据类型。
  • fromElements(T ...) - 从给定的对象序列创建数据流。所有对象必须具有相同的类型。
  • fromParallelCollection(SplittableIterator, Class) - 并行地从迭代器创建数据流。该类指定迭代器返回的元素的数据类型。
  • generateSequence(from, to) -并行地生成给定区间内的数字序列。

注意:集合数据源不能并行执行。

 

Custom:

  • addSource -添加一个新的source函数。例如,要从Apache Kafka读取数据,可以使用addSource(new FlinkKafkaConsumer08<>(…))。有关详细信息,请参见连接器connectors

 

总结:

readTextFile(path)和readFile(fileInputFormat, path)底层都是调用readFile(fileInputFormat, path, watchType, interval, pathFilter, typeInfo):

参数介绍:

 

watchType:

当watchType=FileProcessingMode.PROCESS_CONTINUOUSLY:

flink程序的输入源source可以定期监视(每隔ms)某路径下新数据变化情况;

缺点:该模式下,当文件被修改时,该文件的数据会完全再次被重新处理,这将无法保存数据仅被处理一次(exactly-once )。

当watchType=FileProcessingMode.PROCESS_ONCE:

该模式下,flink程序对当前路径中的数据仅处理一次然后退出。监视器对路径中的数据扫描一次然后退出,无需等待读取器完成对文件内容的读取。当然,读取器会继续读取,直到读取所文件内容。

缺点:当该路径下的某个文件读取完成,然后关闭数据源时不会生成检查点checkpoint,这可能会导致节点失败后恢复速度变慢,因为作业将从最后一个检查点恢复读取。

 

pathFilter:

pathFilter文件路径过滤器,根据这个过滤器对输出的文件进行过滤。

 

Flink将文件读取过程分为两个子任务:即监视文件目录的子线程和文件数据读取子线程。这些子任务都由单独的实体实现的。其中,监视线程并发度为parallelism =1,而文件数据读取线程是并发执行的并发度parallelism >=1(等于job的并行度)。监视文件目录的子线程的作用是扫描目录(根据watchType定期或仅扫描一次),找到要处理的文件并进行分割,然后将这些分割的文件分配给下游的读取器。每个分割的文件仅可以被一个读取器读取,而一个读取器可以逐个读取多个分割的文件。

 

DataStream TransformationsDataStream数据转换

有关可用流转换的概述,请参见操作算子operators 。

 

Data Sinks

数据接收器sinks使用数据流DataStreams 并将它们发送到文件files、套接字sockets、外部系统external systems或打印它们 print them。Flink自带多种内置输出格式,这些格式被封装在DataStreams操作的中:

  • writeAsText() / TextOutputFormat - 将元素作为字符串Strings按行写入到文件中。通过调用每个元素的toString()方法获得字符串Strings。
  • writeAsCsv(...) / CsvOutputFormat -将元组的值以逗号分隔的形式写入到文件中。行和字段分隔符是可配置的。每个字段的值来自对象的toString()方法。
  • print() / printToErr() -在标准输出out/标准错误error流上打印每个元素的toString()值。此外,还可以提供前缀(msg)作为输出的前缀。这有助于区分不同的打印调用。如果并行度大于1,为了便于区分,会把生成输出任务的标识符作为输出前缀。
  • writeUsingOutputFormat() / FileOutputFormat - 自定义文件输出的方法和基类。支持自定义对象到字节的转换。
  • writeToSocket -根据SerializationSchema将元素写入套接字socket。
  • addSink - 调用自定义sink function函数。Flink与其他系统(如Apache Kafka)的连接器绑定在一起,这些连接器作为接收器函数sink functions实现。

注意,DataStream上的write*()方法主要用于调试。它们不包含在Flink的检查点中,这意味着这些函数通常仅支持at-least-once语义。将数据刷新flushing到目标系统取决于OutputFormat的实现。这意味着并非所有发送到OutputFormat的元素都立即出现在目标系统中。此外,在失败的情况下,这些记录可能会丢失。

为了可靠、准确地(exactly-once )将数据流发送到文件系统中,请使用flink-connector-filesystem。此外,通过. addsink(…)方法的自定义实现,将状态写入到Flink检查点checkpointing中,以获得exactly-once语义。

 

注意:

(1) write*() 方法一般主要用于调试,因为没有checkpoint,所以只能保证at-least-once语义;

(2)目标系统的数据刷新取决于OutputFormat的实现,这意味着并非所有发送到OutputFormat的元素都能立即写入显示到目标系统中,此外,在失败的情况下,这些记录可能会丢失;

(3)为了保证消息被exactly-once处理,请使用flink-connector-filesystem:addSink();

 

Iterations

迭代流程序实现了一个step函数,并将其嵌入到IterativeStream中。由于DataStream程序可能永远不会完成,所以没有最大迭代次数。相反,您需要指定流的哪一部分被反馈(fed back)给迭代,以及哪一部分使用split 转换或filter被转发到下游。这里,我们展示一个使用过滤器filters的例子。首先,我们定义一个IterativeStream:

IterativeStream<Integer> iteration = input.iterate();

然后,我们指定将在循环中使用一系列转换执行的逻辑(这里是一个简单的map转换)

DataStream<Integer> iterationBody = iteration.map(/* this is executed many times */);

要关闭迭代和定义迭代尾部tail,请调用IterativeStream的closeWith(feedbackStream)方法。给closeWith函数的数据流将反馈给迭代头head。一种常见的模式是使用过滤器来分离返回流的一部分和转发流的一部分。例如,这些过滤器可以定义“终止”逻辑,其中允许元素向下传输而不是返回。

iteration.closeWith(iterationBody.filter(/* one part of the stream */));
DataStream<Integer> output = iterationBody.filter(/* some other part of the stream */);

例如,这里有一个程序,它不断地从一系列整数中减去1,直到它们达到0:

DataStream<Long> someIntegers = env.generateSequence(0, 1000);

IterativeStream<Long> iteration = someIntegers.iterate();

DataStream<Long> minusOne = iteration.map(new MapFunction<Long, Long>() {
  @Override
  public Long map(Long value) throws Exception {
    return value - 1 ;
  }
});

DataStream<Long> stillGreaterThanZero = minusOne.filter(new FilterFunction<Long>() {
  @Override
  public boolean filter(Long value) throws Exception {
    return (value > 0);
  }
});

iteration.closeWith(stillGreaterThanZero);

DataStream<Long> lessThanZero = minusOne.filter(new FilterFunction<Long>() {
  @Override
  public boolean filter(Long value) throws Exception {
    return (value <= 0);
  }
});

总结:

迭代器数据流没有最大上限,调用closeWith(feedbackStream)可以关闭迭代器数据流,filter会过滤迭代器不满足条件的数据。

 

package org.apache.flink.examples.java.lpDataStream.iterations;
import org.apache.flink.api.common.functions.FilterFunction;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.IterativeStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

public class DemoTest1 {
	public static void main(String[] args) throws Exception {

		StreamExecutionEnvironment env = StreamExecutionEnvironment.
  getExecutionEnvironment();
		env.setParallelism(1);
		DataStream<Long> someIntegers = env.generateSequence(0, 10);

		IterativeStream<Long> iteration = someIntegers.iterate();

		DataStream<Long> minusOne = iteration.map(new MapFunction<Long, Long>() {
			@Override
			public Long map(Long value) throws Exception {
				System.out.println("--------map---------: " + value);
				return value - 1;
			}
		});

		DataStream<Long> stillGreaterThanZero = minusOne.filter(new FilterFunction<Long>() {
			@Override
			public boolean filter(Long value) throws Exception {
				System.out.println("--------filter---------: " + value);
				return (value > 0 );
			}
		});

		iteration.closeWith(stillGreaterThanZero);

//		DataStream<Long> lessThanZero = minusOne.filter(new FilterFunction<Long>() {
//			@Override
//			public boolean filter(Long value) throws Exception {
//				return (value <= 0);
//			}
//		});
		iteration.print();
		env.execute("iterationDemo");
	}
}

Execution Parameters

StreamExecutionEnvironment包含ExecutionConfig,它允许为运行时设置特定于作业的配置值。

有关更多参数说明,请参阅执行配置execution configuration。这些参数属于DataStream API:

  • setAutoWatermarkInterval(long milliseconds):设置自动水印发射的时间间隔。可以使用长getAutoWatermarkInterval()获取当前值

 

Fault Tolerance

状态和检查点State & Checkpointing描述了如何启用和配置Flink的检查点机制checkpointing mechanism。

 

Controlling Latency

默认情况下,元素不会在网络上逐个传输(这会导致不必要的网络流量),而是进行缓冲。缓冲区的大小(实际上是在机器之间传输的)可以在Flink配置文件中设置。虽然这种方法很适合优化吞吐量,但是当传入流不够快时,它可能会导致延迟问题。要控制吞吐量和延迟,可以在执行环境(或单个操作算子operators)上使用env.setBufferTimeout(timeoutMillis)设置缓冲区缓存的最大等待时间。在此之后,即使缓冲区没有满,也会自动发送缓冲区。此超时的默认值为100 ms。

LocalStreamEnvironment env = StreamExecutionEnvironment.createLocalEnvironment();
env.setBufferTimeout(timeoutMillis);

env.generateSequence(1,10).map(new MyMapper()).setBufferTimeout(timeoutMillis);

为了最大限度地提高吞吐量,设置setBufferTimeout(-1),它将删除超时设置,-1表示只有当缓冲区已满时才会刷新flushed缓冲区。为了延迟最小化,将超时设置为接近0的值(例如5或10 ms)。应该避免缓冲区超时为0,因为这会导致严重的性能下降。

 

Debugging

在分布式集群中运行流程序之前,最好确保所实现的算法按预期工作。因此,实现数据分析程序通常是一个检查结果、调试和改进的增量过程。

通过支持IDE中的本地调试、测试数据的注入和结果数据的收集,Flink提供了一些特性,可以显著简化数据分析程序的开发过程。本节给出了一些如何简化Flink程序开发的提示。

 

Local Execution Environment

LocalStreamEnvironment在创建它的同一JVM进程中启动Flink系统。如果从IDE启动LocalEnvironment,则可以在代码中设置断点并轻松调试程序。

本地环境的创建和使用如下所示:

final StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironment();

DataStream<String> lines = env.addSource(/* some source */);
// build your program

env.execute();

 

Collection Data Sources

为了便于简化测试,Flink提供了由Java集合支持的特殊数据源sources 。一旦程序经过测试,源sources和接收器sinks就可以很容易地替换为从外部系统读/写的源sources和接收器sinks。

集合数据源的使用方法如下:

final StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironment();

// Create a DataStream from a list of elements
DataStream<Integer> myInts = env.fromElements(1, 2, 3, 4, 5);

// Create a DataStream from any Java collection
List<Tuple2<String, Integer>> data = ...
DataStream<Tuple2<String, Integer>> myTuples = env.fromCollection(data);

// Create a DataStream from an Iterator
Iterator<Long> longIt = ...
DataStream<Long> myLongs = env.fromCollection(longIt, Long.class);

注意:目前,集合数据源要求数据类型和迭代器实现Serializable。此外,集合数据源不能并行执行(parallelism = 1),即并行度只能是1。

 

Iterator Data Sink

Flink还提供了一个接收器sink来获取数据流DataStream结果,用于测试和调试。它的用途如下:

import org.apache.flink.streaming.experimental.DataStreamUtils

DataStream<Tuple2<String, Integer>> myResult = ...
Iterator<Tuple2<String, Integer>> myOutput = DataStreamUtils.collect(myResult)

注意: flink-streaming-contrib模块已从Flink 1.5.0中删除。它的类已经迁移到flink-streaming-java和flink-streaming-scala中。

 

Where to go next?

 

https://ci.apache.org/projects/flink/flink-docs-release-1.8/dev/datastream_api.html

https://flink.sojb.cn/dev/datastream_api.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值