Flink支持广播变量,就是将数据广播到具体的taskmanager上,数据存储在内存中,这样可以减缓大量的shuffle操作;
比如在数据join阶段,不可避免的就是大量的shuffle操作,我们可以把其中一个dataSet广播出去,一直加载到taskManager的内存中,可以直接在内存中拿数据,避免了大量的shuffle,导致集群性能下降;
广播变量创建后,它可以运行在集群中的任何function上,而不需要多次传递给集群节点。另外需要记住,不应该修改广播变量,这样才能确保每个节点获取到的值都是一致的。
一句话解释,可以理解为是一个公共的共享变量,我们可以把一个dataset 数据集广播出去,然后不同的task在节点上都能够获取到,这个数据在每个节点上只会存在一份。如果不使用broadcast,则在每个节点中的每个task中都需要拷贝一份dataset数据集,比较浪费内存(也就是一个节点中可能会存在多份dataset数据)。
注意:因为广播变量是要把dataset广播到内存中,所以广播的数据量不能太大,否则会出现OOM这样的问题
需求:从内存中拿到data2的广播数据,再与data1数据根据第二列元素组合成(Int, Long, String, String)
val data1 = new mutable.MutableList[(Int, Long, String)]
data1 .+=((1, 1L, “xiaoming”))
data1 .+=((2, 2L, “xiaoli”))
data1 .+=((3, 2L, “xiaoqiang”))
val ds1 = env.fromCollection(data1)
val data2 = new mutable.MutableList[(Int, Long, Int, String, Long)]
data2 .+=((1, 1L, 0, “Hallo”, 1L))
data2 .+=((2, 2L, 1, “Hallo Welt”, 2L))
data2 .+=((2, 3L, 2, “Hallo Welt wie”, 1L))
val ds2 = env.fromCollection(data2)
开发步骤
获取 ExecutionEnvironment 运行环境
加载创建数据源
数据转换(使用广播)
打印测试
import java.util
import org.apache.flink.api.common.functions.RichMapFunction
import org.apache.flink.configuration.Configuration
//进行隐式参数的转换
import org.apache.flink.api.scala._
import scala.collection.mutable
object 广播变量 {
def main(args: Array[String]): Unit = {
//样例数据
val env = ExecutionEnvironment.getExecutionEnvironment
val data1 = new mutable.MutableList[(Int, Long, String)]
data1.+=((1, 1L, "xiaoming"))
data1.+=((2, 2L, "xiaoli"))
data1.+=((3, 2L, "xiaoqiang"))
val ds1 = env.fromCollection(data1)
val data2 = new mutable.MutableList[(Int, Long, Int, String, Long)]
data2.+=((1, 1L, 0, "Hallo", 1L))
data2.+=((2, 2L, 1, "Hallo Welt", 2L))
data2.+=((2, 3L, 2, "Hallo Welt wie", 1L))
val ds2 = env.fromCollection(data2)
//注意这两个参数一个是输入类型,一个是输出类型
ds1.map(new RichMapFunction[(Int, Long, String), (Int, Long, String, String, Long)] {
var ds: util.List[(Int, Long, Int, String, Long)] = null
//在一个方法用来获取变量
override def open(parameters: Configuration): Unit = {
//获取广播变量
ds = getRuntimeContext.getBroadcastVariable[(Int, Long, Int, String, Long)]("ds2")
}
import collection.JavaConverters._
//这个方法用来处理数据的
override def map(value: (Int, Long, String)): (Int, Long, String, String, Long) = {
//拿到广播变量以后因为它是list,所以要先遍历
var tuple: (Int, Long, String, String, Long) = null
for (line <- ds.asScala) {
//首先判断一下第二个索引的是不是相同,如果相同就进行数据合并
if (value._2 == line._2) {
tuple = (value._1, value._2, value._3, line._4, line._5)
}
}
tuple
}
}).withBroadcastSet(ds2, "ds2")
.print() //触发执行打印
}
}
Flink提供了一个分布式缓存,类似于hadoop,可以使用户在并行函数中很方便的读取本地文件
缓存的工作机制
程序注册一个文件或者目录(本地或者远程文件系统,例如hdfs或者s3),通过ExecutionEnvironment注册缓存文件并为它起一个名称。当程序执行,Flink自动将文件或者目录复制到所有taskmanager节点的本地文件系统,用户可以通过这个指定的名称查找文件或者目录,然后从taskmanager节点的本地文件系统访问它
也就是说当我们在运行并行函数的时候如果会用到本地或者hdfs上的文件的话,可以使用分布式缓存,将这个文件缓存到每个task节点,当函数在使用功能的时候就可以从本地系统访问他了
注意:flink分布式缓存是基于磁盘的,并不是内存的,他可以看做是spark的广播,只不过这里广播的是文件
需求:从hdfs上拿到subject.txt数据,再与Clazz数据组合成新的数据集,组成:(学号 , 班级 , 学科 , 分数)
样例数据
hdfs上的
subject.txt : (学号, 学科, 分数)
1,wuli,76
2,yuwen,80
3,yingwu,75
4,shuju,65
5,huaxue,87
6,shuju,95.5
7,yuwen,96.5
8,huaxue,95.5
Clazz : (学号, 班级 )
val clazz:DataSet[Clazz] = env.fromElements(
Clazz(1,“class_1”),
Clazz(2,“class_1”),
Clazz(3,“class_2”),
Clazz(4,“class_2”),
Clazz(5,“class_3”),
Clazz(6,“class_3”),
Clazz(7,“class_4”),
Clazz(8,“class_1”)
)
步骤
获取 ExecutionEnvironment 运行环境
开启分布式缓存
加载本地数据
数据转换(使用分布式缓存)
打印测试
参考代码
import org.apache.flink.api.common.functions.RichMapFunction
import org.apache.flink.api.scala.{DataSet, ExecutionEnvironment}
import org.apache.flink.api.scala._
import org.apache.flink.configuration.Configuration
import scala.collection.mutable.ListBuffer
import scala.io.Source
object DistributeCache {
def main(args: Array[String]): Unit = {
//flink分布式缓存
/**首先定义两个类,一个Clazz和一个我们要输出的合并后的数据的类
* 1.获取 ExecutionEnvironment 运行环境
* 2.开启分布式缓存
* 2. 加载本地数据
* 3. 数据转换(使用分布式缓存)
* 4. 打印测试
*/
//获取执行环境
val env = ExecutionEnvironment.getExecutionEnvironment
//设置分布式缓存地址
val path = "hdfs://node01:9000/score.txt"
//然后注册分布式缓存
env.registerCachedFile(path, "DistributeCache")
//然后加载本地数据
val clazz: DataSet[Clazz] = env.fromElements(
Clazz(1, "class_1"),
Clazz(2, "class_1"),
Clazz(3, "class_2"),
Clazz(4, "class_2"),
Clazz(5, "class_3"),
Clazz(6, "class_3"),
Clazz(7, "class_4"),
Clazz(8, "class_1")
)
clazz.map(new RichMapFunction[Clazz, info] {
var myline = new ListBuffer[String]
//在老一结果open实现获取分布式缓存的方法
override def open(parameters: Configuration): Unit = {
//获取分布式缓存
val file = getRuntimeContext.getDistributedCache.getFile("DistributeCache")
//解析文件,获取每一行,io流
val str: Iterator[String] = Source.fromFile(file.getAbsoluteFile).getLines()
str.foreach(line => {
myline.append(line)
})
}
//这是处理数据的方法
override def map(value: Clazz): info = {
var info: info = null
//在这里进行数据转换
for (line <- myline) {
val splits_words = line.split(",")
//然后再判断学号是否相同,学号相同的话就进行数据的合并
if (
splits_words(0).toInt == value.id) {
//判断之后进行数据的合并
info(value.id, value.clazz, splits_words(1), splits_words(2))
}
}
//在这里进行返回
info
}
}).print() //最后触发执行打印一下
}
//在有一个输出的类
case class info(in: Int, clazz: String, subject: String, score: Double)
case class Clazz(id: Int, clazz: String)
}
1.广播变量是基于内存的,是将变量分发到各个worker节点的内存上
2.分布式缓存是基于磁盘的,将文件copy到各个节点上,当函数运行时可以在本地文件系统检索该文件
声明:文章仅记录自己平时学习的过程的记录,如果有不对的或者侵权的地方请联系我改正,避免误导更多的人