1、计数器
需求:统计DataSet中元素的数量。
常规实现思路:
object CounterApp {
def main(args: Array[String]): Unit = {
val env: ExecutionEnvironment = ExecutionEnvironment
.getExecutionEnvironment
val data: DataSet[String] = env
.fromElements("hadoop", "spark", "flink")
data.map(new RichMapFunction[String, Long] {
// 1、定义计数器
var counter = 0L
override def map(value: String): Long = {
counter = counter + 1
println("counter ", counter)
counter
}
}).setParallelism(2).print() // 设置并行度2
// 每个并行度中的counter都是从1开始的
}
}
存在的问题:counter在每个并行度数据集中都是从1开始的,从上述程序的运行结果来看,这并不能够满足我们计数的需求。
下面我们将通过使用Flink自带的计数器实现计数功能。
/** 如何使用计数器实现计数呢 */
def counterInFlink(env:ExecutionEnvironment,
data: DataSet[String]) = {
data.map(new RichMapFunction[String, String] {
// 1、定义计数器
private val counter = new LongCounter()
override def open(parameters: Configuration): Unit = {
// 2、注册计数器
getRuntimeContext.addAccumulator("ele-counter", counter)
}
override def map(value: String): String = {
counter.add(1)
value
}
}).setParallelism(2).writeAsText("data/output/sink/counter",
writeMode = WriteMode.OVERWRITE)
val result: JobExecutionResult = env.execute("CounterApp")
// 3、获取计数器
println(result.getAccumulatorResult("ele-counter"))
}
总结:基于Flink编程的计数器开发步骤如下,
- 定义计数器
- 注册计数器
- 获取计数器
2、分布式缓存
Flink提供了分布式缓存,和hadoop比较类似,能够使得其他并行用户函数的实例像访问本地文件一样使用。这个功能可用于共享包含静态外部数据的文件,如字典或机器学习回归模型。
缓存工作机制如下,程序以特定的名称在ExecutionEnvironment中注册文件或者目录(可以时本地文件,也可以是HDFS或者S3上的文件)。当程序执行时,Flink会自动将文件或目录复制到所有Worker节点的本地文件系统中。用户函数可以查找指定名称下的文件或目录,并从Worker节点的本地文件系统中访问它。
可以通过如下方式,使用分布式缓存:
val env = ExecutionEnvironment.getExecutionEnvironment
// register a file from HDFS
env.registerCachedFile("hdfs:///path/to/your/file", "hdfsFile")
// register a local executable file (script, executable, ...)
env.registerCachedFile("file:///path/to/exec/file", "localExecFile", true)
// define your program and execute
...
val input: DataSet[String] = ...
val result: DataSet[Integer] = input.map(new MyMapper())
...
env.execute()
可以通过一个用户函数(例子中的是MapFunction)访问缓存文件。这个函数必须继承RichFunction类,这是因为它需要访问RuntimeContext。
// extend a RichFunction to have access to the RuntimeContext
class MyMapper extends RichMapFunction[String, Int] {
override def open(config: Configuration): Unit = {
// access cached file via RuntimeContext and DistributedCache
val myFile: File = getRuntimeContext.getDistributedCache.getFile("hdfsFile")
// read the file (or navigate the directory)
...
}
override def map(value: String): Int = {
// use content of cached file
...
}
}
基于上述分布式缓存介绍,下面通过注册本地文件作为分布式缓存,来详细说明分布式缓存的使用。
object DistributedCacheApp {
def main(args: Array[String]): Unit = {
val env: ExecutionEnvironment = ExecutionEnvironment
.getExecutionEnvironment
val filePath = "data/input/words.txt"
// 1、注册一个本地文件/HDFS文件
env.registerCachedFile(filePath, "wc")
val data: DataSet[String] = env
.fromElements("hadoop", "spark", "flink, zookeeper")
data.map(new RichMapFunction[String, String] {
override def open(parameters: Configuration): Unit = {
// 2、获取注册文件
val cacheFile: File = getRuntimeContext.getDistributedCache.getFile("wc")
// 3、读取文件内容
val lines: util.List[String] = FileUtils.readLines(cacheFile)
// 注意:此时会出现一个异常:java集合和scala集合不兼容的问题
import scala.collection.JavaConverters._
for (ele <- lines.asScala)
println(ele)
}
override def map(value: String): String = value
}).print()
}
}