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