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
未完,失误上传