Flink 中定义了DataStream API让用户灵活且高效地编写Flink流式应用。DataStream API主要可分为三个部分,DataSource模块、Transformation模块以及DataSink模块。其中DataSource模块主要定义了数据接入功能,主要是将各种外部数据接入至Flink系统中,并将接入数据转换成对应的DataStream数据集。在Transformation模块定义了对DataStream数据集的各种转换操作,例如进行map、filter、windows等操作。最后,将结果数据通过DataSink模块写出到外部存储介质中,例如将数据输出到文件或kafka消息中间件等。
本文详细讲述DataStream的三大模块:
DataSource模块定义了DataStream API中的数据输入操作,Flink将数据源分为内置数据源和第三方数据源两种类型。
1、内置数据源
(1)文件数据源
基于文件创建DataStream主要有两种方式:readTextFile和readFile。
可以使用readTextFile方法直接读取文件,readTextFile提供了两个重载方法:
readFile通过指定的InputFormat来读取指定数据类型的文件。对于指定路径文件,我们可以使用不同的处理模式来处理,FileProcessingMode.PROCESS_ONCE
模式只会处理文件数据一次,而FileProcessingMode.PROCESS_CONTINUOUSLY
会监控数据源文件是否有新数据,如果有新数据则会继续处理。
(2)socket数据源
Flink支持从Socket端口中接入数据,在StreamExecutionEnvironment调用socketTextStream方法。该方法参数分别为Ip地址和端口,也可以同时传入字符串切割符delimiter和最大尝试次数maxRetry,其中delimiter负责将数据切割成Records数据格式;maxRetry在端口异常的情况,通过指定次数进行重连,如果设定为0,则Flink程序直接停止,不再尝试和端口进行重连。如下代码是使用socketTextStream方法实现了将数据从本地9999端口中接入数据并转换成DataStream数据集的操作。
val socketDataStream = env.socketTextStream("localhost", 9999)
(3)集合数据源
Flink可以直接将java或Scala程序中集合类(collection)转换为DataStream数据集,本质上是将本地集合中的数据分发到远端并并行执行的节点中。目前Flink支持从Java.util.Collection和java.util.Iterator序列中转换为DataStream数据集。需要注意的是,集合中的数据结构类型必须要一致,否则可能会出现数据转换异常。
val dataStream = env.fromElements(Tuple2(1L, 3L), Tuple2(1L, 5L), Tuple2(1L, 7L), Tuple2(1L, 4L))
String[] elements = new String[]{"hello", "flink"}
DataStream dataStream = env.fromCollection(Arrays.asList(elements))
List arrayList = new ArrayList<>()
arrayList.add("hello flink")
DataStream dataList = env.fromCollection(arrayList)
2、外部数据源
(1)数据源连接器
Flink通过实现SourceFunction定义了非常丰富的第三方数据连接器,基本覆盖了大部分的高性能存储介质以及中间件等,其中一部分连接器是仅支持读取数据,例如Twitter Streaming API、Netty等;另外一部分仅支持数据输出(Sink),不支持数据输入(Source),例如Apache Cassandra、Elasticsearch、Hadoop FileSystem 等。还有一部分即支持数据输入,也支持数据输出,例如Apache Kafka、Amazon Kinesis、RabbitMQ等连接器。
(2)自定义数据源连接器
Flink中已经实现了大多数主流的数据源连接器,但,flink的整体架构非常开放,用户可以自己定义连接器,以满足不同的数据源的接入需求。可以通过实现SourceFunction定义单个线程接入的数据接入器,也可以通过实现ParallelSourceFunction接口或继承RichParallelSourceFuntion类定义并发数据源接入器。DataSources定义完成后,可以通过使用StreamExecutionEnvironment的addSources方法添加数据源,这样就可以将外部系统中的数据转换成DataStream[T]数据集合,其中T类型是SourceFuntion返回值类型,然后就可以完成各种流式数据的转换操作。
1、Single-DataStream操作
数据处理的核心就是对数据进行各种转化操作,数据的transformation是将一个或者多个DataStream转换成一个或者多个新的DataStream,程序可以将多个transformation操作组合成一个复杂的拓扑结构。
window[KeyedStream->WindowedStream]: 对已经分区的KeyedStream上定义窗口,Window会根据某些规则(比如在最后5s到达的数据)对虚拟“key”相同的记录进行分组。
windowAll[DataStream->AllWindowedStream]: 也可以在常规DataStream上使用窗口,Window根据某些条件(比如最后5s到达的数据)对所有流事件进行分组。
Window Apply[WindowedStream->DataStream或AllWindowedStream->DataStream]: 将整个窗口应用在指定函数上,可以对WindowedStream和AllWindowedStream使用。
Window Reduce/Fold/Aggregation[WindowedStream->DataStream]: 对于WindowedStream数据流我们同样也可以应用Reduce、Fold、Aggregation函数。
2、Multi-DataStream操作
3、物理分区操作
Flink中将DataStream数据输出在外部系统的过程被定义为DataSink操作。在Flink内部定义的第三方外部系统连接器中,支持数据输出的有Apache Kafka、Apache Cassandra、ElasticSearch、Hadoop FileSystem、RabbitMQ、NIFI等。
1、基本数据输出
基本数据输出包含了文件输出、客户端输出、Socket网络端口等。如下代码所示,实现将DataStream数据集分别输出在本地文件系统和Socket网络端口。
val person = env.fromElements(("alex", 18), ("peter", 11))
person.writeAsCsv("file:///path/to/person.csv", WriteMode.OVERWRITE)
person.writeToSocket(outputHost, outputPort, new SimpleStringSchema())
2、第三方数据输出
Flink中提供了DataSink类操作算子来专门处理数据的输出,所有的数据输出都可以基于实现SinkFunction完成定义。例如在Flink中定义了FlinkKafkaProducer类来完成将数据输出到kafka的操作,需要根据不同的kafka版本选择不同的FlinkKafkaProducer。如下代码所示,通过FlinkKafkaProducer11将DataStream中的数据写入kafka的topic中。
val wordStream = env.fromElements("alex", "peter", "linda")
val kafkaProducer = new FlinkKafkaProducer011[string]("localhost:9092", "kafka-topic", new SimpleStringSchema)
wordStream.addSink(kafkaProducer)