Apache Flink DataStream API之程序结构分析&DataSource

ApacheFlink? - 是针对于数据流的状态计算,Flink具有特殊类DataSet和DataStream来表示程序中的数据。您可以将它们视为可以包含重复项的不可变数据集合。在DataSet的情况下,数据是有限的,而对于DataStream,元素的数量可以是无限的。

这些集合在某些关键方面与常规Java集合不同。首先,它们是不可变的,这意味着一旦创建它们就无法添加或删除元素。你也不能简单地检查里面的元素。 最初通过在Flink程序中添加Source来创建集合,并通过使用诸如map,filter等API方法对它们进行转换来从这些集合中派生新集合。

结构分析

Flink程序看起来像是转换数据集合的常规程序。每个程序包含相同的基本部分:

  • 获得执行环境,
  • 加载/创建初始数据,
  • 指定此数据的转换,
  • 指定放置计算结果的位置,
  • 触发程序执行

获取执行环境

Flink提供了三种运行Flink计算的方式:

  • 远程jar包部署方式
var streamEnv = StreamExecutionEnvironment.getExecutionEnvironment()
  • 1
  • 本地执行
var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
  • 1
  • 跨平台提交
var streamEnv = StreamExecutionEnvironment.createRemoteEnvironment("CentOS",8081,"jarFiles")
  • 1

用户可以更具需求自行抉择选择哪种方式测试或者运行代码。

加载/创建初始数据

创建需要加载的数据源,一般该数据源来源于消息队列或者其他第三方系统,为了测试方便这里我们先使用socketTextStream实现数据源的加载。

var source = streamEnv.socketTextStream("CentOS", 9999)
  • 1

指定此数据的转换

通过指定数据转换规则对DataStream上应用转换以创建新的派生DataStream。

source.flatMap(_.split("\\W+"))
      .map((_,1))
      .keyBy(0)
      .sum(1)
  • 1
  • 2
  • 3
  • 4

将计算结果存储到 文件中

writeAsCsv("D:/flinks/results")
  • 1

触发程序执行

streamEnv.execute("wordcounts")
  • 1

将以上程序放置在一起

import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.api.scala._

//1.创建流处理执行环境 - 本地环境
var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
//2.创建数据源
var source = streamEnv.socketTextStream(“CentOS”, 9999)
//3.数据转换
source.flatMap(.split("\W+"))
.map((
,1))
.keyBy(0)
.sum(1)
//4.指定输出路径
.writeAsCsv(“D:/flinks/results”, FileSystem.WriteMode.OVERWRITE)
//5.触发任务执行
streamEnv.execute(“wordcounts”);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

Flink数据源

数据源是您的程序从中读取输入的位置。您可以使用StreamExecutionEnvironment.addSource(sourceFunction)将数据源附加到程序。 Flink附带了许多预先实现的源函数,但您可以通过为非并行源实现SourceFunction,或者通过实现ParallelSourceFunction接口或为并行源扩展RichParallelSourceFunction来编写自己的自定义源。

预定义的流源:

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

在底层,Flink将文件读取过程分为两个子任务,即目录监控和数据读取。这些子任务中的每一个都由单独的实体实现。监视由单个非并行(并行度= 1)任务实现,而读取由并行运行的多个任务执行。后者的并行性等于job的并行性。单个监视任务的作用是扫描目录(定期或仅一次,具体取决于watchType),找到要处理的文件,将它们分成分割,并将这些分割分配给下游Reader。Reader是那些将读取实际数据的任务。每个分割仅由一个读取器读取,而读取器可以逐个读取多个Split数据。

重要: 如果watchType设置为FileProcessingMode.PROCESS_CONTINUOUSLY,则在修改文件时,将完全重新处理其内容。这可以打破“完全一次”的语义,因为在文件末尾附加数据将导致其所有内容被重新处理。 如果watchType设置为FileProcessingMode.PROCESS_ONCE,则源扫描路径一次并退出,而不等待读者完成读取文件内容。当然Reader将继续读取文件数据,直到读取所有文件内容。当读取完成后,不再有检查点保存。这可能会导致节点故障后恢复速度变慢,因为作业将从上一个检查点恢复读取。

<!--HDFS依赖-->
<dependency>
  <groupId>jdk.tools</groupId>
  <artifactId>jdk.tools</artifactId>
  <version>1.8</version>
  <scope>system</scope>
  <systemPath>D:/Program Files/Java/jdk1.8.0_121/lib/tools.jar</systemPath>
</dependency>
<dependency>
  <groupId>org.apache.hadoop</groupId>
  <artifactId>hadoop-common</artifactId>
  <version>${hadoop.version}</version>
</dependency>
<dependency>
  <groupId>org.apache.hadoop</groupId>
  <artifactId>hadoop-hdfs</artifactId>
  <version>${hadoop.version}</version>
</dependency>

<!–Scala依赖–>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-scala_KaTeX parse error: Expected 'EOF', got '&' at position 102: …n punctuation">&̲lt;/</span>arti…{flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-scala_KaTeX parse error: Expected 'EOF', got '&' at position 102: …n punctuation">&̲lt;/</span>arti…{flink.version}</version>
</dependency>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • readTextFile
var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
var source = streamEnv.readTextFile("hdfs:///demo/words")
source.flatMap(_.split("\\W+"))
    .map((_,1))
    .keyBy(0)
    .sum(1)
    .writeAsCsv("D:/flinks/results", FileSystem.WriteMode.OVERWRITE)

streamEnv.execute(“wordcounts”);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • readFile

系统会每间隔10ms检测目录,如果有新文件产生立即采集,如果文本内容发生变化,立即重新读取。

var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
var inputFormat=new TextInputFormat(null)
var source = streamEnv.readFile(
        inputFormat,
        "hdfs:///demo/words",
        FileProcessingMode.PROCESS_CONTINUOUSLY,
        10,
        new FilePathFilter {
            override def filterPath(path: Path): Boolean = {
                return !path.getPath().contains("log")
            }
        }
    )
    source.flatMap(_.split("\\W+"))
    .map((_,1))
    .keyBy(0)
    .sum(1)
    .print()

streamEnv.execute(“wordcounts”)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

Socket-based

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

var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
var source = streamEnv.socketTextStream("CentOS", 9999)
source.flatMap(_.split("\\W+"))
      .map((_,1))
      .keyBy(0)
      .sum(1)
  	  .writeAsCsv("D:/flinks/results", FileSystem.WriteMode.OVERWRITE)

streamEnv.execute(“wordcounts”);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Collection-based

  • fromCollection(Collection) - 从Java Java.util.Collection创建数据流。集合中的所有元素必须属于同一类型。
var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
    var source =streamEnv.fromCollection(List("this is a demo hello world"))
    source.flatMap(_.split("\\W+"))
    .map((_,1))
    .keyBy(0)
    .sum(1)
    .print()

streamEnv.execute(“wordcounts”)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • fromElements(T …) - 根据给定的对象序列创建数据流。所有对象必须属于同一类型。
var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
    var source =streamEnv.fromElements("this is a demo","good good study")
    source.flatMap(_.split("\\W+"))
    .map((_,1))
    .keyBy(0)
    .sum(1)
    .print()

streamEnv.execute(“wordcounts”)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • fromCollection(Iterator,Class) - 从迭代器创建数据流。该类指定迭代器返回的元素的数据类型。
var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
var source =streamEnv.fromCollection(Iterator(User(1,"zhangsan",true),
                                              User(2,"lisi",false),
                                              User(3,"ww",true)))
source.map(user=>(user.sex,1))
	.keyBy(0)
	.sum(1)
	.print()

streamEnv.execute(“usersexcount”)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • generateSequence(from,to) - 并行生成给定时间间隔内的数字序列。
var streamEnv = StreamExecutionEnvironment.createLocalEnvironment()
var source =streamEnv.generateSequence(0,1000)
source.filter(_%2==0)
.print()

streamEnv.execute(“计算偶数”)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

addSource(流处理)

添加新的源功能。例如,要从Apache Kafka读取,您可以使用addSource(new FlinkKafkaConsumer <>(…))

<dependency>
  <groupId>org.apache.flink</groupId>
  <artifactId>flink-connector-kafka_${flink.scala.version}</artifactId>
  <version>${flink.version}</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
val env = StreamExecutionEnvironment.getExecutionEnvironment
val props = new Properties()
props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "CentOS:9092")
props.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "g1")

env.addSource(new FlinkKafkaConsumer[String](“topic01”,new SimpleStringSchema(),props))
.flatMap(line => for( i <- line.split(" ")) yield (i,1))
.keyBy(_._1)
.reduce((in1,in2)=>(in1._1,in1._2+in2._2))
.print()

env.execute(“kafka message count”)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

注意在测试机器上必须配置主机名和IP的映射关系,否则系统联系不上Kafka服务器。

更多精彩内容关注

微信公众账号

                                </div>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章