一、广播变量的创建与使用
spark 的广播变量允许在每个工作节点缓存一个只读的变量,这样做的好处是避免任务为每一个Task共享的数据单独创建拷贝,大大节省了运算空间占用,在Java中通过JavaSparkContext.broadcast(v)方法,Scala中通过SparkContext.broadcast(v) 方法对变量v进行包装和分发操作,使用时调用 broadcast.value()方法即可使用,代码如下:
JAVA版本:
public static void main(String[] args) throws Exception {
SparkConf conf = new SparkConf()
.setAppName("broadcastUpdate")
.set("spark.streaming.kafka.maxRatePerPartition", args[0]);//限制吞吐量
JavaStreamingContext jsic = new JavaStreamingContext(conf,
Durations.seconds(120));
// 设置checkpoint目录
jsic.checkpoint(CST.SPARK_CHECKPOINT_DIR_SINGLE);
// 创建task集合的广播变量
JavaSparkContext jsc = jsic.sparkContext();
//广播变量创建
List<String> strList = new ArrayList<String>();
Broadcast<String> broadcast = jsc.broadcast(strList);
//广播变量数据使用
String s = broadcast.value();
}
Scala版本:
scala> val broadcastVar = sc.broadcast(Array(1, 2, 3))
broadcastVar: org.apache.spark.broadcast.Broadcast[Array[Int]] = Broadcast(0)
scala> broadcastVar.value
res0: Array[Int] = Array(1, 2, 3)
二、广播变量的更新操作
实际应用场景中,广播变量中的值并不是一直不变的,一般涉及到规则匹配,黑名单过滤筛选等操作的话,需要对广播到各个节点的数据做定期,这时需要先调用broadcast.unpersist(Boolean)释放掉节点的广播变量数据,再将更新的数据用同样的名字重新广播一次,其中的参数默认为False,代表该广播变量如果有节点正在使用时,是否阻塞程序。该部分代码一般放在output操作的foreachRDD()中
JAVA版本:
resultDStream.foreachRDD(new Function<JavaRDD<String>, Void>() {
private static final long serialVersionUID = 1L;
@Override
public Void call(JavaRDD<String> RDD) throws Exception {
SparkContext context = RDD.context();
//获取到RDD的JavaSparkContext
JavaSparkContext jsc = JavaSparkContext.fromSparkContext(context);
broadcast.unpersist(true);//默认为false
String newStr = "new broadcast value";
//重新生成广播变量,采用一样的变量名称
Broadcast<String> broadcast = jsc.broadcast(newStr);
}
});
Scala版本:
resuleDstream.foreachRDD(new VoidFunction<JavaRDD<String>>() {
@Override
public void call(JavaRDD<String> stringJavaRDD) throws Exception {
//获取广播变量值
broadcast.value();
//释放广播变量值
broadcast.unpersist();
//重新广播,更新值列表
broadcast=stringJavaRDD.context().broadcast(newStr,ClassManifestFactory.classType(String.class));
}
});
三、JAVA版本出现的编译时错误及解决方法
在上面的JAVA更新广播变量操作中,重新生成广播变量并分发时,编译器可能会提示报错:
这是因为:Java8在 lambda 表达式中使用局部变量会提示:Local variable flag defined in an enclosing scope must be final or effectively final
首先这是一个java编译时的错误,翻译成中文是:不可变的局部变量不能被赋值,因为它已经被定义在一个封闭类型中。
解决的办法:将广播变量作一下封装,用集合或者数组,如果变量类型是基本数据类型一般用数组。
//广播变量申明
String broadcastStr = "broadcast value";
Broadcast<String> broadcast = jsc.broadcast(broadcastStrs);
//用数组包装该广播变量类,否则在匿名函数内的调用无法通过编译
final List<Broadcast<String>> broadcastList = new ArrayList<Broadcast<String>>();
broadcastList.add(broadcast);
//广播变量更新
@Override
public Void call(JavaRDD<String> v1) throws Exception {
//释放之前的节点缓存
broadcast.unpersist();
SparkContext context = v1.context();
JavaSparkContext jjsc = JavaSparkContext.fromSparkContext(context);
String newbroadcastStr = "another broadcast value";
Broadcast<String> newbroadcast = jjsc.broadcast(newbroadcastStr);
//将List中元素替换,方便下次广播变量的更新
broadcastList.set(0, newbroadcast);
return null;
}