Flink入门
基础编程框架
不管是批处理还是流式处理,Flink的编程框架均遵循如下框架:
- 批处理:
设置批处理的execution environment
代码:ExecutionEnvironment.getExecutionEnvironment - 流处理:
设置流处理的execution environment
代码:StreamExecutionEnvironment.getExecutionEnvironment - 从environment中获取数据
- 使用对应的Flink API处理数据
- 执行程序
wordcount批处理版本
object FlinkBatchJob {
def main(args: Array[String]): Unit = {
// 获取批处理上下文,等同于SparkContext
val env = ExecutionEnvironment.getExecutionEnvironment
// 读取数据
val text: DataSet[String] =env.readTextFile("data/wc.data")
// transformation操作
// flink中没有reducebykey
val result = text.flatMap(_.toLowerCase.split(","))
.filter(_.nonEmpty) // 过滤掉有空格的情况
.map((_,1))
.groupBy(0) // 根据单词进行分组(根据index取字段)
.sum(1) // 根据value进行求和(根据index取字段)
result.print()
}
}
wordcount流处理版本
object FlinkStreamingJob {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 接收数据
val text: DataStream[String] = env.socketTextStream("localhost", 9999)
// 通过tuple方式来指定key
// transformation操作
// flink流处理中没有groupby只有keyby
// val result = text.flatMap(_.toLowerCase.split(","))
// .filter(_.nonEmpty) // 过滤掉有空格的情况
// .map((_,1))
// .keyBy(0) // 根据单词进行分组(根据index取字段)
// .sum(1) // 根据value进行求和(根据index取字段)
val result = text.flatMap(_.toLowerCase.split(","))
.filter(_.nonEmpty)
.map(x => WC(x,1))
// .keyBy("word") // 通过字段表达式来指定key,这种方式代码的可读性更高
.keyBy(_.word) // 使用key的selector function来指定key
.sum("count")
result.print("huhu_bigdata").setParallelism(2) // 设置最大的并行度为2
// 触发程序
env.execute(this.getClass.getSimpleName)
}
}
case class WC(word: String, count: Int)
Flink基本API
ExecutionEnvironment
在使用DataSet/DataStream进行相关的编程时,创建ExecutionEnvironment(上下文)有3种方式:
- getExecutionEnvironment()
- createLocalEnvironment()
- createRemoteEnvironment(host: String, port: Int, jarFiles: String*)
其实我们在使用时,通过getExecutionEnvironment进行获取即可;内部实现里,会取决于程序如何进行执行,如果是在IDE中执行的就会去createLocalEnvironment,如果以jar包的方式在服务器上运行,则会createRemoteEnvironment
注意点:
- 对于流处理来讲,最后需要调用一下env.excute()方法,否则的话是不会执行的
- 对于批出来来讲,最后只要有个sink就行了
Lazy Evaluation
所有的flink应用程序都是延迟执行的,当应用程序的main方法被加载的时候,数据的加载、transformation并不会立刻的去被执行,但是每一步的操作都会被添加到执行计划中去,当触发execute()方法后,这些操作才会被真正的执行(这点与spark是一样的)
延迟执行,可以使我们在应用程序中去构建一个非常复杂的执行计划
Specifying Keys
一些transformation,比如:join、coGroup、keyBy、grouBy等,都是需要有1个key定义在集合上之后才能使用这些算子的,那么对应的数据源必须为(key,value)这种结构的
指定key的3种方式:
- 通过tuple的方式来指定key
使用tuple中key的下标0、1、2…来进行指定,比如xxx.keyBy(0)
可读性较差,换一个人来不知道这个0代表的是啥字段 - 使用字段表达式来指定key
引入case class,比如xxx.keyBy(“word”)
这种方式使用的多,相比上面那种可读性更高,但是可能会写错字段名,虽然编译通过了,但是程序执行直接挂了
使用case class会带来最方便的一点是:case class默认就是序列化的 - 使用key的selector function来指定key
比如xxx.keyBy(_.word),生产最推荐使用这种方式
Specifying Transformation Functions
在Flink当中使用transformation有3种方式:
- 使用lambda function
- 自定义function
- 使用rich function
使用lambda function
这种方式比较简单快速,生产上也较为推荐
自定义Function
我们观察可以发现:像filter算子里有FilterFunction,map算子里也有MapFunction:
其实在Flink中每个算子都是有对应的function的,同时我们也可以通过自定义来进行实现,需要自定义RuozedataFilter,继承FilterFunction:
实际使用:
该实现方式指定死了参数,无法动态传入,因此可以改写完动态传入参数的形式:
实际上还有第三种写法,就是使用匿名内部类的方式,如下:
Rich Function
Rich代表增强的意思,在Flink中RichXXXFunction是一种比较牛逼的东西,后面会深入剖析
以RichFilterFunction为例继承了AbstractRichFunction这个接口,该接口有如下方法:
- open初始化方法
- close资源释放
- getRuntimeContext拿到整个作业运行时的上下文,这个很有用
对于RichXXXFunction而言都是基于AbstractRichFunction实现的,该Function为一个生命周期函数;以MapReduce的编程模型为例,当我们在MapReduce中实现Mapper方法的时候,需要实现setup、cleanup、run方法,同时会在run中调用了setup与cleanup;比如要连接数据库,总不可能每条记录去连一条的,肯定是需要在task运行起来的是初始化,结束后再进行释放资源;同理,在Flink中也可以在RichFunction中这样做
自定义RuozedataMap继承RichMapFunction,在里面重写map方法并注入数据处理逻辑:
运行时我们可以发现open的调用次数与我们设置的并行度相关,并行度设置为多少则open调用多少次
代码
object SpecifyingTransformationsApp {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(2) // 并行度设置
val stream = env.readTextFile("data/access.log")
// val accessStream = stream.map(x => {
// val splits = x.split(",")
// Access(splits(0).toLong, splits(1), splits(2).toLong)
// })
// 需求: 过滤traffic大于4000的
// 使用Lambda Functions
// accessStream.filter(_.traffic > 4000).print()
// 使用自定义Function,写死
// accessStream.filter(new RuozedataFilter).print()
// 使用自定义Function,传参
// accessStream.filter(new RuozedataFilterValue(5000)).print()
// 使用自定义Function,匿名内部类方式
// accessStream.filter(new FilterFunction[Access] {
// override def filter(value: Access): Boolean = value.traffic > 4000
// }).print()
// 使用Rich Function
stream.map(new RuozedataMap).filter(_.traffic > 4000).print()
env.execute(this.getClass.getSimpleName)
}
}
/**
* 自定义Function,写死
*/
class RuozedataFilter extends FilterFunction[Access] {
override def filter(value: Access): Boolean = value.traffic > 4000
}
/**
* 自定义Function,传参
*/
class RuozedataFilterValue(traffic: Long) extends FilterFunction[Access] {
override def filter(value: Access): Boolean = value.traffic > traffic
}
/**
* 自定义RichFunction
* String: 读进来的数据
* Access: 写出去的类型
*/
class RuozedataMap extends RichMapFunction[String, Access] {
override def map(value: String): Access = {
val splits = value.split(",")
Access(splits(0).toLong, splits(1), splits(2).toLong)
}
// 调用次数与并行度设置的大小相同
override def open(parameters: Configuration): Unit = {
super.open(parameters)
println("====open====")
}
override def getRuntimeContext: RuntimeContext = {
super.getRuntimeContext
}
override def close(): Unit = {
super.close()
}
}
Function体系结构
map ==> MapFunction
filter ==> FilterFunction
xxx ==> XxxFunction
RichXxxFunction,比较重要,继承AbstractRichFunction,并实现XxxFunction
RichMapFunction,是对MapFunction的增强;继承AbstractRichFunction,并实现MapFunction