Flink API
Flink 的核心概念
核心概念概述
Flink 程序是實現分佈式集合轉換操作(如:過濾、映射、更改狀態、join、分組、定義窗口、聚合等)的有規律的程序。集合最初是由 sources(數據源) (例如: 從文件中讀取、kafka topic、或者來自本地、內存中的集合等)創建的, 結果通過 sink 輸出,可能是將數據寫入文件中,或者以標準輸出的形式輸出(例如:輸出到控制檯)。
Flink 程序可以以不同的形式運行——以獨立的形式運行或者嵌入到其他程序中執行。執行的動作可以發生在本地,也可以發生在多臺機器構成的集羣中。
根據數據源類型的不同,可以是有界的或者無界的,你可以寫批處理程序或者流處理程序,其中 DataSet API 是提供給批處理程序用的,而 DataStream API 是提供給流處理程序用的。
根據數據源類型的不同,可以是有界的或者無界的,你可以寫批處理程序或者流處理程序,其中 DataSet API 是提供給批處理程序用的,而 DataStream API 是提供給流處理程序用的。
Flink的架構
- Deploy: 本地,集羣,谷歌和亞馬遜的雲服務器上。
- Core:自身的runtime 環境和基礎環境通信。
- API:DataSet API(批處理) DataStream API(流處理)
- Libraries:CEP 複雜事件處理,Table/Sql:結構化數據分析,FlinkM1 機器學習,Gelly圖數據庫。
DataSet 和 DataStream
Flink 使用 DataSet 和 DataStream 這兩個特殊的類來表示程序中的數據,你可以將它們想象成一個包含重複數據的不可變數據集合,其中 DataSet 的數據是有限的而 DataStream 中的數據個數則是無限的。DataSet 是批處理的數據抽象,DataStream 是流處理面的數據抽象。
幾種形式的對比: Flink DataFlow 基本形式
Flink 的編程步驟
- 獲取數據
- Transfiramtion 操作
- Sink 的位置(print)
- 觸發執行
//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 的有:(端口獲取)
- socketTextStream(hostName,port)
- socketTextStream(hostName,port,delimiter)
- socketTestStream(hostName,port,delimiter,maxRetry)
val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment // 連接此socket獲取輸入數據 val text = env.socketTextStream("vm2", 9999, '\n') //打印接受到的數據 text.print() env.execute("DataSourceFromSocket --- ")
二. 基於文件或者文件夾
-
readTextFile(String path) 讀取 txt 格式的文件。
-
readFile(FileInputFormat inputFormat,String path) 如果數據源不是 txt 文件,可以指定文件的格式。
-
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類型
-
writeAsText() 以 txt 形式寫出去。
-
writeAsCsv() 以 csv 形式寫出去
-
write()
-
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 )
- ApacheKafka(source/sink)
- Apache Cassandra (sink)
- Elasticsearch (sink)
- Hadoop FileSystem (sink)
- RabbitMQ (source/sink)
- Apache ActiveMQ (source/sink)
- ARedis(sink) \color{red}{ARedis (sink)}ARedis(sink)
三. 自定義 sink
實現自定義的 sink,步驟如下:
- 實現 SinkFunction 接口
- 繼承 RichSinkFunction
計數器
多分區的情況下無法計數
- 定義計數器
- 註冊計數器
- 獲取計數器
/**
* 計數器
*/
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 支持的數據類型
-
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 通過字段名稱獲得。
-
Java POJO
Java 和 Scala 的類在滿足下列條件時,將會被 Flink 視作特殊的 POJO 數據類型專門進行處理:
1. 是公共類 2. 無參構造是公共的 3. 所有的屬性都是可獲得的(聲明爲公共的,或提供 get ,set 方法)。 4. 字段的類型必須是 Flink 支持的。Flink 會用 Avro 來序列化任意的對象。 -
基本類型
Flink 支持 Java 和 Scala 所有的基本數據類型, 比如 Integer ,String,和 Double1.2 無參構造。
-
通用類
Flink 支持大多數的 Java,Scala 類(API 和自定義)包含不能序列化字段的類在增加一些限制後也可支持。遵循 Java Bean 規範的類一般都可以使用。
所有不能視爲 POJO 的類 Flink 都會當做一般類處理。這些數據類型被視作黑箱,其內容是不可見的。通用類使用 Kryo 進行序列/反序列化。
-
值類型 Values
通過實現 org.apache.flinktypes.Value 接口的 read 和 write 方法提供自定義代碼來進行序列化/反序列化,而不是使用通用的序列化框架。
Flink 預定義的值類型與原生數據類型是一一對應的(例如: ByteValue, ShortValue, IntValue, LongValue, FloatValue, DoubleValue, StringValue, CharValue, BooleanValue)。這些值類型作爲原生數據類型的可變變體,他們的值是可以改變的,允許程序重用對象從而緩解 GC 的壓力
-
Hadoop Writables
它實現 org.apache.hadoop.Writable 接口的類型,該類型的序列化邏輯在 write() 和 readFields() 方法中實現
-
特殊類型
- Scala 的 Either、Option 和 Try。
- 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 的流式處理中,會涉及到時間的不同概念,如下。
- Event Time:是事件創建的時間。它通常由事件中的時間戳描述,例如採集的日誌數據中,每一條記錄都會記錄自己的生成時間,Flink 通過時間戳分配器訪問事件時間戳。
- Ingestion Time:是數據進入 Flink 的時間。
- 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)。
-
滾動窗口
將數據依據固定的窗口長度對數據進行切片。特點:時間對齊、窗口長度固定、沒有重疊
滾動窗口分配器將每個元素分配到一個指定窗口大小的窗口中,滾動窗口有一個固定的大小,並且不會出現重疊。例如:你設置了一個 5 分鐘大小的滾動窗口,如下圖
適用場景:適合做 BI (商業智能)統計等(做每個時間段的聚合計算)
-
滑動窗口
滑動窗口是固定窗口的更廣義的一種形式,滑動窗口由固定的窗口長度和滑動間隔組成。特點:時間對齊,窗口長度固定,有重疊
滑動窗口分配器將元素分配到固定長度的窗口中,與滾動窗口類似,窗口的大小由窗口大小參數來配置,另一個窗口滑動參數控制滑動窗口開始的頻率。因此,滑動窗口如果滑動參數小於窗口大小的話,窗口是會重疊的,在這種情況下元素會被分配到多個窗口中。
例如:你有 10 分鐘的窗口和 5 分鐘的滑動,那麼每個窗口中 5 分鐘的窗口裏包含着上個 10 分鐘產生的數據,如下圖:
適用場景:對最近一個時間段內的統計(求某接口最近 5 分鐘的失敗率來覺得是否報警)。
-
會話窗口
由一系列事件組合一個指定事件長度的 timeout 間隙組成,類似於 web 應用的 session,也就是一段時間沒有接收到新數據就會生成新的窗口。特點:時間無對齊
session 窗口分配器通過 session 活動來對元素進行分組,session 窗口跟滾動窗口和滑動窗口相比,不會有重疊和固定的開始時間和結束時間的情況,相反,當它在一個固定的時間週期內不再收到元素,即非活動間隔產生,那麼這個窗口就會關閉。。一個 session 窗口通過一個 session 間隔來配置,這個 session 間隔定義了非活躍週期的長度,當這個非活躍週期產生,那麼當前的 session 將關閉並且後續的元素將被分配到新的 session 窗口中去。
Window API
TimeWindow
TimeWindow 是將指定時間範圍內的所有數據組成一個 window ,一次對一個 window 裏面的所有數據進行計算。
-
滾動窗口
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") }
-
滑動窗口
滑動窗口和滾動窗口的函數名是完全一致的,只是在傳參數時需要傳入兩個參數,一個是 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
未完,失誤上傳