Flink的使用

Flink 的核心概念

核心概念概述

      Flink 程序是實現分佈式集合轉換操作(如:過濾、映射、更改狀態、join、分組、定義窗口、聚合等)的有規律的程序。集合最初是由 sources(數據源) (例如: 從文件中讀取、kafka topic、或者來自本地、內存中的集合等)創建的, 結果通過 sink 輸出,可能是將數據寫入文件中,或者以標準輸出的形式輸出(例如:輸出到控制檯)。


      Flink 程序可以以不同的形式運行——以獨立的形式運行或者嵌入到其他程序中執行。執行的動作可以發生在本地,也可以發生在多臺機器構成的集羣中。


      根據數據源類型的不同,可以是有界的或者無界的,你可以寫批處理程序或者流處理程序,其中 DataSet API 是提供給批處理程序用的,而 DataStream API 是提供給流處理程序用的。


      根據數據源類型的不同,可以是有界的或者無界的,你可以寫批處理程序或者流處理程序,其中 DataSet API 是提供給批處理程序用的,而 DataStream API 是提供給流處理程序用的。


在這裏插入圖片描述
                                                      Flink的架構
  1. Deploy: 本地,集羣,谷歌和亞馬遜的雲服務器上。
  2. Core:自身的runtime 環境和基礎環境通信。
  3. API:DataSet API(批處理) DataStream API(流處理)
  4. Libraries:CEP 複雜事件處理,Table/Sql:結構化數據分析,FlinkM1 機器學習,Gelly圖數據庫。

    DataSet 和 DataStream

      Flink 使用 DataSet 和 DataStream 這兩個特殊的類來表示程序中的數據,你可以將它們想象成一個包含重複數據的不可變數據集合,其中 DataSet 的數據是有限的而 DataStream 中的數據個數則是無限的。DataSet 是批處理的數據抽象,DataStream 是流處理面的數據抽象。

幾種形式的對比:

在這裏插入圖片描述                                             Flink DataFlow 基本形式
在這裏插入圖片描述

Flink 的編程步驟

  1. 獲取數據
  2. Transfiramtion 操作
  3. Sink 的位置(print)
  4. 觸發執行
//A.獲取執行環境
val env = ExecutionEnvironment.createLocalEnvironment(1)
//B.獲取數據
val text = env.readTextFile("D:/hello.txt")
//引入隱式轉換
import org.apache.flink.api.scala._
//C.Transforamtion 操作
val word = text.flatMap(_.split(" ")).map((_, 1)).groupBy(0).sum(1)
//D.Sink的位置(print)
word.print()
word.writeAsText("D:/result.txt")
//E.觸發執行
env.execute("flink wordcount demo")

延遲執行(懶加載)

      所有的 Flink 的執行都是延遲執行的,這個 Spark 中的 action 的時候纔會執行,Flink 和 Spark 是一樣的道理,當遇到顯示的觸發的時候纔會運行,例如調用 execte()的時候,這樣程序的其他操作才被執行。

  好處:

      這樣的延遲操作,可以構建比較複雜的 DAG 圖,這樣可以提高它的資源利用,沒有必須要每一步都要落地,是一條線的形式執行的,這樣可以大大的提高執行效率。

指定 key

      Flink 的數據模型不在基於鍵值對。因此,您無需將數據集類型物理打包到鍵和值中。

      鍵是"虛擬的":他們被定義爲實際數據上的函數,以指導分組算子,可以在具體算子通過參數指定。

      主要有以下幾種形式

  使用 tuple 的形式:

val rdd: DataSet[(Int, Int)] = env.fromCollection(List((1, 2), (1, 3)))
val rdd01: GroupedDataSet[(Int, Int)] = rdd.groupBy(0)

  還可以是字段表達式:

object filedsExepl {
  def main(args: Array[String]): Unit = {
    val env: ExecutionEnvironment = ExecutionEnvironment.createLocalEnvironment(1)
    import org.apache.flink.api.scala._
    val value: DataSet[String] = env.readTextFile("D:/hello.txt")
    value.map(w => WordWithCount(w, 1)).groupBy("word").first(2).print()
  }
}
case class WordWithCount(word: String, count: Long)

  還可以是字段表達式:

def main(args: Array[String]): Unit = {
  val env: ExecutionEnvironment = ExecutionEnvironment.createLocalEnvironment(1)
  import org.apache.flink.api.scala._
  val value: DataSet[String] = env.readTextFile("D:/hello.txt")
  value.map(w => WordWithCount(w, 1)).groupBy(x=>x.word).first(2).print()
}

指定轉換函數(Transformation)

  增強函數形式:

**
  * 通過指定繼承實現類或接口 指定轉換函數
  */
object TransFun {
  def main(args: Array[String]): Unit = {
    val env: ExecutionEnvironment = ExecutionEnvironment.createLocalEnvironment(1)
    import org.apache.flink.api.scala._
    val data: DataSet[String] = env.fromCollection(List("1", "2", "3"))
    val rdd: DataSet[Int] = data.map(new MyMapFunction())
    rdd.print()
  }
}
class MyMapFunction extends RichMapFunction[String, Int] {
  def map(in: String): Int = {
    in.toInt
  }
}


或者

/**
  * 通過匿名內部類的這種形式,指定轉換函數
  */
object TransFun01 {
  def main(args: Array[String]): Unit = {
    val env: ExecutionEnvironment = ExecutionEnvironment.createLocalEnvironment(1)
    import org.apache.flink.api.scala._
    val data: DataSet[String] = env.fromCollection(List("1", "2", "3"))
    val rdd: DataSet[Int] = data.map(new RichMapFunction[String, Int] {
      def map(in: String):Int = { in.toInt }
    })
    rdd.print()
  }
}

  Lambda Functions:

**
  * 通過使用Lambda  函數形式,指定轉換函數
  */
object TransFun02 {
  def main(args: Array[String]): Unit = {
    val env: ExecutionEnvironment = ExecutionEnvironment.createLocalEnvironment(1)
    import org.apache.flink.api.scala._
    val data: DataSet[String] = env.fromCollection(List("1", "2", "3"))
    val rdd: DataSet[Int] = data.map(line=>line.toInt)
    rdd.print()
  }
}

Flink API 編程

      Flink 中程序是實現數據集轉換的常規程序(例如:過濾,映射,映射,連接,分組)。數據集最初是從某些來源創建的(例如,通過讀取文件或者從本地集合創建)。結果通過接收器返回,接收器可以例如數據寫入(分佈式)文件或者標準輸出(例如命令行終端)。Flink 程序可以在各種環境中運行,獨立運行或者嵌入其他程序中。執行可以在本地 JVM 中執行,也可以在許多計算機的集羣上執行。

支持的數據源 DataSet

一.  基於 socket 的有:(端口獲取)

  1. socketTextStream(hostName,port)
  2. socketTextStream(hostName,port,delimiter)
  3. socketTestStream(hostName,port,delimiter,maxRetry)
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    // 連接此socket獲取輸入數據
    val text = env.socketTextStream("vm2", 9999, '\n')
    //打印接受到的數據
    text.print()
    env.execute("DataSourceFromSocket --- ")
    

二.  基於文件或者文件夾

  1. readTextFile(String path) 讀取 txt 格式的文件。

  2. readFile(FileInputFormat inputFormat,String path) 如果數據源不是 txt 文件,可以指定文件的格式。

  3. readFileStream(String filePath, long intervalMils,FileMonitoringFunction.watchType watchType) 其中 interval 是輪詢的間隔,這個需要指定,而且 watch type 保留三種選擇。
    FileMonitoringFunction.WatchType.ONLY_NEW_FILES 只處理新文件
    FileMonitoringFunction.WatchType.PROCESS_ONLY_APPENDED 只處理追加的內容
    FileMonitoringFunction.WatchType.REPROCESS_WITH_APPENDED 既要處理追加的 內容,也會處理file中之前的內容

    object DataSourceFromtxtFile {
      def main(args: Array[String]): Unit = {
        //A.獲取執行環境
        val env = ExecutionEnvironment.createLocalEnvironment(1)
        //B.獲取數據
        val text = env.readTextFile("D:/hello.txt")
        //C.數據
        text.print()
      }
    }
    

    讀取csv

    def csvFile(env: ExecutionEnvironment): Unit = {
      import org.apache.flink.api.scala._
      env.readCsvFile[(String, Int, String)]("D:/hello.csv", ignoreFirstLine = true).print()
    }
    

    遞歸讀取文件

    def readRecursiveFiles(env: ExecutionEnvironment): Unit = {
      env.readTextFile("d:/").print()
      println("-----------------------------")
      val conf: Configuration = new Configuration()
      conf.setBoolean("recursive.file.enumeration", true)
      env.readTextFile("d:/").withParameters(conf).print()
    }
    

    讀取壓縮格式的文件

    /**
      * 從壓縮文件中讀取數據生成dataset
      */
    def readCompressionFiles(env: ExecutionEnvironment): Unit = {
      env.readTextFile("d:/").print()
    }
    

三.  基於集合

/**
  * 從集合中獲取數據
  *
  * @param e
  */
def fromCollection(env: ExecutionEnvironment): Unit = {
  import org.apache.flink.api.scala._
  val data = 0 to 10
val rdd: DataSet[String] = env.fromCollection(List("1","2"))
val rdd: DataSet[String] = env.fromElements("")
val rdd: DataSet[Long] = env.generateSequence(0, 1000)

  env.fromCollection(data).print()
}

sink 的目的地(代碼演示)

一.  Data Sink類型

  1. writeAsText() 以 txt 形式寫出去。

  2. writeAsCsv() 以 csv 形式寫出去

  3. write()

  4. print() / printToErr() 打印每個元素的 toString() 方法的值到標準輸出或者標準錯誤輸出流中。

    	object SinkApp {
      def main(args: Array[String]): Unit = {
        val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
        val data = 1 to 10
        import org.apache.flink.api.scala._
        val dds: DataSet[Int] = env.fromCollection(data)
        dds.writeAsText("d:/shuchu.txt",WriteMode.NO_OVERWRITE)
        env.execute()
      }
    }
    

二.  Flink內置 Connectors (這裏有很多數據下沉的 api )

  1. ApacheKafka(source/sink)
  2. Apache Cassandra (sink)
  3. Elasticsearch (sink)
  4. Hadoop FileSystem (sink)
  5. RabbitMQ (source/sink)
  6. Apache ActiveMQ (source/sink)
  7. ARedis(sink) \color{red}{ARedis (sink)}ARedis(sink)

三.  自定義 sink

實現自定義的 sink,步驟如下:

  1. 實現 SinkFunction 接口
  2. 繼承 RichSinkFunction

計數器

多分區的情況下無法計數

  1. 定義計數器
  2. 註冊計數器
  3. 獲取計數器
/**
  * 計數器
  */
object MyTrans01 {
  def main(args: Array[String]): Unit = {
    val env: ExecutionEnvironment = ExecutionEnvironment.createLocalEnvironment(1)
    import org.apache.flink.api.scala._
    val ds: DataSet[String] = env.fromElements("hadoop", "spark", "hbase", "redis", "zookeeper")

    /* ds.map(new RichMapFunction[String, Long] {
       var counter = 0l
       override def map(in: String): Long = {
         counter=counter+1
         println("counter:    "+counter)
         counter
       }
     }).setParallelism(3).print()*/
    //累加器
    val info = ds.map(new RichMapFunction[String, String] {
      //1.定義一個計數器(不要導錯包)
      val counter: LongCounter = new LongCounter()

      override def open(parameters: Configuration): Unit = {
        //2.註冊計數器
        getRuntimeContext.addAccumulator("acc--name", counter)
      }

      override def map(in: String): String = {
        counter.add(1)
        in
      }
    }).setParallelism(6)
    // info.print() //不支持這種做法
    info.writeAsText("d:/a.txt",WriteMode.OVERWRITE)
    val result: JobExecutionResult = env.execute("exe name")
    val l: Long = result.getAccumulatorResult[Long]("acc--name")
    println(l)
  }
}

分佈式緩存

      Flink 提供了一個分佈式緩存,類似於 Apacke Hadoop, 使本地可訪問用戶函數的並行實例。此功能可用於共享包含靜態外部數據(如字典或機器學習的迴歸模型)的文件。

      緩存的工作原理如下。程序在其執行環境中以特定名稱註冊本地或遠程文件系統(如 HDFS 或者 S3)的文件或目錄作爲緩存文件。當程序執行時,Flink 自動將文件或者目錄複製到所有工作人員的本地文件系統。用戶函數可以查找指定名稱下的文件或目錄,並從工作人員的本地文件系統中訪問它。使用如下:

/**
  * 分佈式緩存
  */
object Mytrans02 {
  def main(args: Array[String]): Unit = {
    val env = ExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._
    val ds: DataSet[String] = env.fromElements("hadoop", "spark", "storm")
    env.registerCachedFile("D:/hello.txt", "FileName")
    ds.map(new RichMapFunction[String, String]() {
      override def open(parameters: Configuration): Unit = {
        val dcFile: File = getRuntimeContext.getDistributedCache().getFile("FileName")
        val lines= FileUtils.readLines(dcFile)
        //此時會出現一個問題 java-->scala
        //
        import scala.collection.JavaConverters._
        for (line <- lines.asScala) {
          println(line)
        }
      }
      override def map(in: String): String = {
        in
      }
    }).print()
    // define your program and execute
    env.execute()
  }
}

Flink 支持的數據類型

  1. Java Tuple 和 Scala Case 類

          Tuple 是包含固定數量各種類型字段的複合類。Flink Java Api 提供了 Tuple1-Tuple25。Tuple 的字段可以是 Flink 的任意類型,甚至嵌套 Tuple 。

          Scala 的 Case 類(以及 Scala 的 Tuple ,實際是 Case class 的特殊類型)是包含了一定數量多種類型字段的組合類型。Tuple 字段通過他們 1-offset 名稱定爲,例如 _1 代表第一個字段。Case class 通過字段名稱獲得。

  2. Java POJO

          Java 和 Scala 的類在滿足下列條件時,將會被 Flink 視作特殊的 POJO 數據類型專門進行處理:

    1. 是公共類 2. 無參構造是公共的 3. 所有的屬性都是可獲得的(聲明爲公共的,或提供 get ,set 方法)。 4. 字段的類型必須是 Flink 支持的。Flink 會用 Avro 來序列化任意的對象。
  3. 基本類型

          Flink 支持 Java 和 Scala 所有的基本數據類型, 比如 Integer ,String,和 Double1.2 無參構造。

  4. 通用類

          Flink 支持大多數的 Java,Scala 類(API 和自定義)包含不能序列化字段的類在增加一些限制後也可支持。遵循 Java Bean 規範的類一般都可以使用。

          所有不能視爲 POJO 的類 Flink 都會當做一般類處理。這些數據類型被視作黑箱,其內容是不可見的。通用類使用 Kryo 進行序列/反序列化。

  5. 值類型 Values

          通過實現 org.apache.flinktypes.Value 接口的 read 和 write 方法提供自定義代碼來進行序列化/反序列化,而不是使用通用的序列化框架。

          Flink 預定義的值類型與原生數據類型是一一對應的(例如: ByteValue, ShortValue, IntValue, LongValue, FloatValue, DoubleValue, StringValue, CharValue, BooleanValue)。這些值類型作爲原生數據類型的可變變體,他們的值是可以改變的,允許程序重用對象從而緩解 GC 的壓力

  6. Hadoop Writables

          它實現 org.apache.hadoop.Writable 接口的類型,該類型的序列化邏輯在 write() 和 readFields() 方法中實現

  7. 特殊類型

    1. Scala 的 Either、Option 和 Try。
    2. Java API 有自己的 Either 實現。

Flink wordCount

  測試數據:
      Hello heloo word spark storm
      Hadoop hadoop hadoop

   Flink + scala 批處理
   引入pom:

<dependencies>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-scala_2.11</artifactId>
        <version>1.6.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-clients_2.11</artifactId>
        <version>1.6.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-streaming-scala_2.11</artifactId>
        <version>1.6.0</version>
    </dependency>
</dependencies>

   創建一個object類

def main(args: Array[String]): Unit = {
  val env = ExecutionEnvironment.createLocalEnvironment(1)
  val text = env.readTextFile("D:/hello.txt")
  import org.apache.flink.api.scala._
  val word = text.flatMap(_.split(" ")).map((_, 1)).groupBy(0).sum(1)
  word.print()
  word.writeAsText("D:/result.txt")
  env.execute("flink wordcount demo")
}

  打印結果

在這裏插入圖片描述

實時處理程序

  flink 實時接收一個端口的數據

import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.windowing.time.Time
// 定義一個數據類型保存單詞出現的次數
case class WordWithCount(word: String, count: Long)
object StreamingwordCount {
  def main(args: Array[String]): Unit = {
    // 獲取運行環境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    // 連接此socket獲取輸入數據
    val text = env.socketTextStream("vm2", 9999, '\n')
    //需要加上這一行隱式轉換 否則在調用flatmap方法的時候會報錯
    import org.apache.flink.api.scala._
    // 解析數據, 分組, 窗口化, 並且聚合求SUM
    val windowCounts = text
      .flatMap { w => w.split("\\s") }
      .map { w => WordWithCount(w, 1) }
      .keyBy("word")
      .timeWindow(Time.seconds(5), Time.seconds(1))
      .sum("count")
    // 打印輸出並設置使用一個並行度
    windowCounts.print().setParallelism(1)
    env.execute("Socket Window WordCount")
  }
}

  運行程序之後,啓動 linux ,執行 nc -lk 9999,輸入單詞

在這裏插入圖片描述

  查看結果
在這裏插入圖片描述

Time 與 Window

Time

      在 Flink 的流式處理中,會涉及到時間的不同概念,如下。

在這裏插入圖片描述

  1. Event Time:是事件創建的時間。它通常由事件中的時間戳描述,例如採集的日誌數據中,每一條記錄都會記錄自己的生成時間,Flink 通過時間戳分配器訪問事件時間戳。
  2. Ingestion Time:是數據進入 Flink 的時間。
  3. Processiong Time:是每一個執行基於時間操作的算子的本地系統時間,與機器相關,默認的時間屬性就是 Processiong Time。

      例如,一條日誌進入 Flink 的時間爲 2017-11-12 10:00:00 .123(ingestion time),到達 window 的系統時間爲 2017-11-12 10:00:00 .234(processing time),日誌的內容如下:

     2017-11-02 18:37:15.624 INFO Fail over to rm2 (event time)

      對於業務來說,要統計 1 min 內的故障日誌個數,evenTime是最重要的,因爲要根據日誌的生成時間進行統計。

Window

Window 概述

      streaming 流式計算是一種被設計用於處理無限數據集的數據引擎,而無限數據集市指不斷增粘的本質上無限的數據集,而 window 是一種切割無限數據爲有限塊進行處理的手段。

      window 是無限數據流處理的核心,window 將一個無限的 stream 拆分成有限大小的 "buckets"桶,我們可以在這些桶上做計算操作。

window類型

   Window 可以分成兩類:

  • CountWindow:按照指定的數據條數生成一個 Window,與時間無關。
  • TimeWindow:按照時間生成Window。

      對於 TimeWindow,可以根據窗口實現原理的不同分成三類:滾動窗口(Tumbling Window)、滑動窗口(Sliding Window)和會話窗口(Session Window)。

  1. 滾動窗口
       將數據依據固定的窗口長度對數據進行切片。

       特點:時間對齊、窗口長度固定、沒有重疊

          滾動窗口分配器將每個元素分配到一個指定窗口大小的窗口中,滾動窗口有一個固定的大小,並且不會出現重疊。例如:你設置了一個 5 分鐘大小的滾動窗口,如下圖

在這裏插入圖片描述
         適用場景:適合做 BI (商業智能)統計等(做每個時間段的聚合計算)

  1. 滑動窗口
       滑動窗口是固定窗口的更廣義的一種形式,滑動窗口由固定的窗口長度和滑動間隔組成。

       特點:時間對齊,窗口長度固定,有重疊

          滑動窗口分配器將元素分配到固定長度的窗口中,與滾動窗口類似,窗口的大小由窗口大小參數來配置,另一個窗口滑動參數控制滑動窗口開始的頻率。因此,滑動窗口如果滑動參數小於窗口大小的話,窗口是會重疊的,在這種情況下元素會被分配到多個窗口中。

          例如:你有 10 分鐘的窗口和 5 分鐘的滑動,那麼每個窗口中 5 分鐘的窗口裏包含着上個 10 分鐘產生的數據,如下圖:

在這裏插入圖片描述
         適用場景:對最近一個時間段內的統計(求某接口最近 5 分鐘的失敗率來覺得是否報警)。

  1. 會話窗口
       由一系列事件組合一個指定事件長度的 timeout 間隙組成,類似於 web 應用的 session,也就是一段時間沒有接收到新數據就會生成新的窗口。

       特點:時間無對齊

          session 窗口分配器通過 session 活動來對元素進行分組,session 窗口跟滾動窗口和滑動窗口相比,不會有重疊和固定的開始時間和結束時間的情況,相反,當它在一個固定的時間週期內不再收到元素,即非活動間隔產生,那麼這個窗口就會關閉。。一個 session 窗口通過一個 session 間隔來配置,這個 session 間隔定義了非活躍週期的長度,當這個非活躍週期產生,那麼當前的 session 將關閉並且後續的元素將被分配到新的 session 窗口中去。

在這裏插入圖片描述

Window API

TimeWindow

      TimeWindow 是將指定時間範圍內的所有數據組成一個 window ,一次對一個 window 裏面的所有數據進行計算。

  1. 滾動窗口

          Flink 默認的時間窗口根據 Processiong Time 進行窗口的劃分,將 Flink 獲取到的數據根據進入 Flink 的時間劃分到不同的窗口中。

    def main(args: Array[String]): Unit = {
      // 獲取運行環境
      val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
      // 連接此socket獲取輸入數據
      val text =  env.socketTextStream("vm2", 9999, '\n')
      //需要加上這一行隱式轉換 否則在調用flatmap方法的時候會報錯
      import org.apache.flink.api.scala._
      // 解析數據, 分組, 窗口化, 並且聚合求SUM
      val windowCounts = text
        .flatMap { w => w.split(" ") }
        .map { w => WordWithCount01(w, 1) }
        .keyBy("word")
        //滾動時間窗口
        .timeWindow(Time.seconds(5))
        .sum("count")
      // 打印輸出並設置使用一個並行度
      windowCounts.print().setParallelism(1)
      env.execute("Socket TimeTumbWindow")
    }
    
  2. 滑動窗口

          滑動窗口和滾動窗口的函數名是完全一致的,只是在傳參數時需要傳入兩個參數,一個是 window_sive,一個是 sliding_size。

          下面代碼中的 sliding_size 設置爲 2s , 也就是說窗口沒 2s 計算一次,每次計算的 window 範圍是 5s 內的所有元素

    /**
      * 滑動窗口
      */
    object TimeSlidWindow {
      def main(args: Array[String]): Unit = {
        // 獲取運行環境
        val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
        // 連接此socket獲取輸入數據
        val text = env.socketTextStream("vm2", 9999, '\n')
        //需要加上這一行隱式轉換 否則在調用flatmap方法的時候會報錯
        import org.apache.flink.api.scala._
    
        // 解析數據, 分組, 窗口化, 並且聚合求SUM
        val windowCounts = text
          .flatMap { w => w.split(" ") }
          .map { w => WordWithCount01(w, 1) }
          .keyBy("word")
          //滾動時間窗口
          .timeWindow(Time.seconds(5), Time.seconds(1))
          .sum("count")
        // 打印輸出並設置使用一個並行度
        windowCounts.print().setParallelism(1)
        env.execute("Socket Window WordCount")
      }
    }
    

          時間間隔可以通過 Time.miliseconds(x),Time.seconds(x),Time.minutes(x) 等其中的一個來指定。

CountWindow


未完,失誤上傳

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