广播变量&累加变量
1、 广播&累加器
我们传递给Spark的函数,如map(),或者filter()的判断条件函数,能够利用定义在函数之外的变量,但是集群中的每一个task都会得到变量的一个副本,并且task在对变量进行的更新不会被返回给driver。而Spark的两种共享变量:累加器(accumulator)和广播变量(broadcast variable),在广播和结果聚合这两种常见类型的通信模式上放宽了这种限制。 使用累加器可以很简便地对各个worker返回给driver的值进行聚合。
由于对于worker节点来说,累加器的值是不可访问的,所有对于worker上的task,累加器是write-only的。这使得累加器可以被更高效的实现,而不需要在每次更新时都进行通信。
Spark保证:在终止操作中对累加器的操作只执行一次,而转化操作中则可能多次执行。
累加器的操作可以多种,比如算术加法 MAX,只要这些操作符合交换律和结合律。
累加器会多次传递给Executor节点(而广播只一次)。
1. Accumulator
import java.util.ArrayList;
import java.util.List;
import org.apache.spark.Accumulator;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.broadcast.Broadcast;
import scala.Tuple2;
public class AccumulatorDemo {
public static void main(String[] xx){
SparkConf conf = new SparkConf();
conf.setMaster("local[4]");
conf.setAppName("WordCounter");
conf.set("spark.default.parallelism", "4");
conf.set("spark.testing.memory", "2147480000");
JavaSparkContext ctx = new JavaSparkContext(conf);
Person[] persons = new Person[10000];
Broadcast<Person []> persons_br = ctx.broadcast(persons);
Accumulator<Integer> count = ctx.accumulator(0);
List<String> data1 = new ArrayList<String>();
data1.add("Cake");
data1.add("Bread");
data1.add("");
data1.add("Cheese");
data1.add("Milk");
data1.add("Toast");
data1.add("Bread");
data1.add("");
data1.add("Egg");
data1.add("");
JavaRDD<String> rdd1 = ctx.parallelize(data1, 2);
System.out.println(rdd1.glom().collect());
rdd1.mapToPair(new PairFunction<String, String, Integer>() {
@Override
public Tuple2<String, Integer> call(String s) throws Exception {
long id = Thread.currentThread().getId();
System.out.println("s:" + s + " in thread:" + id);
if(s.equals("")){
count.add(1);
}
return new Tuple2<String, Integer>(s, 1);
}
}).collect();
System.out.println(count.value());
rdd1.mapToPair(new PairFunction<String, String, Integer>() {
@Override
public Tuple2<String, Integer> call(String s) throws Exception {
long id = Thread.currentThread().getId();
System.out.println("s:" + s + " in thread:" + id);
if(s.equals("")){
count.add(1);
// System.out.println("c:"+count.value());
}
System.out.println(persons_br.value().length);
return new Tuple2<String, Integer>(s, 1);
}
}).collect();
System.out.println(count.value());
ctx.stop();
}
}
class Person{}
2.BroadCast
import java.util.Arrays;
import java.util.List;
import org.apache.spark.Accumulator;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.Function2;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.broadcast.Broadcast;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.Time;
import org.apache.spark.streaming.api.java.JavaPairDStream;
import org.apache.spark.streaming.api.java.JavaReceiverInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import scala.Tuple2;
/**
* 实例:利用广播进行黑名单过滤! 检查新的数据 根据是否在广播变量-黑名单内,从而实现过滤数据。
*/
public class BroadCastDemo {
/**
* 创建一个List的广播变量
*
*/
private static volatile Broadcast<List<String>> broadcastList = null;
/**
* 计数器!
*/
private static volatile Accumulator<Integer> accumulator = null;
public static void main(String[] args) {
SparkConf conf = new SparkConf().setMaster("local[2]").setAppName("WordCountOnlineBroadcast");
conf.set("spark.testing.memory", "2147480000");
JavaStreamingContext jsc = new JavaStreamingContext(conf, Durations.seconds(5));
/**
* 注意:分发广播需要一个action操作触发。 注意:广播的是Arrays的asList
* 而非对象的引用。广播Array数组的对象引用会出错。 使用broadcast广播黑名单到每个Executor中!
*/
broadcastList = jsc.sparkContext().broadcast(Arrays.asList("Hadoop", "Mahout", "Hive"));
/**
* 累加器作为全局计数器!用于统计在线过滤了多少个黑名单! 在这里实例化。
*/
accumulator = jsc.sparkContext().accumulator(0, "OnlineBlackListCounter");
JavaReceiverInputDStream<String> lines = jsc.socketTextStream("Master", 9999);
/**
* 这里省去flatmap因为名单是一个个的!
*/
JavaPairDStream<String, Integer> pairs = lines.mapToPair(new PairFunction<String, String, Integer>() {
@Override
public Tuple2<String, Integer> call(String word) {
return new Tuple2<String, Integer>(word, 1);
}
});
JavaPairDStream<String, Integer> wordsCount = pairs.reduceByKey(new Function2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer v1, Integer v2) {
return v1 + v2;
}
});
/**
* Funtion里面 前几个参数是 入参。 后面的出参。 体现在call方法里面!
*
*/
// wordsCount.foreach(new Function2<JavaPairRDD<String, Integer>,Time,boolean>() {
// @Override
// public boolean call(JavaPairRDD<String, Integer> rdd, Time time) throws Exception {
// rdd.filter(new Function<Tuple2<String, Integer>, Boolean>() {
// @Override
// public Boolean call(Tuple2<String, Integer> wordPair) throws Exception {
// if (broadcastList.value().contains(wordPair._1)) {
// /**
// * accumulator不仅仅用来计数。 可以同时写进数据库或者缓存中。
// */
// accumulator.add(wordPair._2);
// return false;
// } else {
// return true;
// }
// };
// /**
// * 广播和计数器的执行,需要进行一个action操作!
// */
// }).collect();
// System.out.println("广播器里面的值" + broadcastList.value());
// System.out.println("计时器里面的值" + accumulator.value());
// return null;
// }
// });
jsc.start();
try {
jsc.awaitTermination();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
jsc.close();
}
}