import org.apache.log4j.{Level, Logger}
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author Jacky
* Spark中的共享变量
* 在Spark中,只要不涉及任务的提交,那么所有的代码都在Spark程序的Driver端运行,
* 也就是说,只要不触发Action算子,那么所有的代码都在Spark程序的Driver端运行,包括对RDD的定义和Transformation算子操作。
* 总之,凡是不涉及到任务提交的代码片段,都是在Driver端运行。
*
* 在Spark中,当一个RDD的算子引用到了一个外部变量时,那么在执行Task任务的时候,每个task都会去Driver端
* 拷贝一份外部变量的副本到Executor上运行。每个Task拷贝的副本只能是对应的Task去操作,其他的Task是不能操作
* 另外一个Task拷贝的副本数据,即这个副本是不能共享的。
* 由于外部变量不是共享的,那么当一个Job任务有很多个Task的时候,就意味着要拷贝的副本数就很多,比如,一个外部变量
* 的数据大小是1G,此时这个Job一共有1000个Task任务需要执行,那么这1000个Task就要从Driver端拷贝1000G的数据(1TB)到
* Executor上执行。可以想像这会极大的消耗网络流量传输,同时也会消耗Executor大量的内存。
* 这样就会大大降低Spark作业的运行效率。
*
* 在Spark的Executor上的数据都是由BlockManager来管理的。
* 要解决上面的问题,此时我们可以将外部变量做成广播变量,那么每个Executor只会有一份外部变量的副本,
* 在Executor上执行的Task都共享这份外部变量的数据。
*
* 第一步:Task先去自己所持有的BlockManager中查找是否存在这个外部变量。
* 第二步:如果在自己的BlockManager中查找不到这个外部变量,此时它就会去近邻的Executor的BlockManager中查找这个外部变量。
* 第三步:如果在所有的Executor的BlockManager中找不到这个外部变量,那么这个Task就会去Driver端拷贝一份外部变量的副本存储
* 到Executor的BlockManager中。
*
* 由于每个Executor上只有一份外部变量的副本数据,那么此时需要拷贝的数据量大大减少,这样会直接降低网络流量传输,
* 同时也会减少Executor内存消耗。
*/
object Scala_ShareVariableDemo {
def main(args: Array[String]): Unit = {
//在Driver端运行
Logger.getLogger("org").setLevel(Level.WARN)
//在Driver端运行
val conf = new SparkConf().setAppName("Scala_ShareVariableDemo").setMaster("local")
//在Driver端运行
val sc = new SparkContext(conf)
//在Driver端运行,在Driver端定义了一个num=2的这样的一个变量
var num = 2
//通过集合创建初始的RDD
//这个也是在Driver端运行,这里只是定义了RDD1,并没有真正的执行,也即并没有真正的形成一个RDD,并指定分区数为2。
val rdd1 = sc.parallelize(Array("hello", "word", "spark", "flink"), 2)
//由于map算子也是Transformation类型的算子,它不触发任务的提交,那么这种操作也是在Driver端运行。
//凡时对RDD进行Transformation算子转换操作,都是在Driver端运行的。
val rdd2 = rdd1.map((_, 1))
//注意这里就有点不同了,由于foreach它是一个Action算子,在Spark中一个RDD只要触发了一个Action算子,
//那么就意味着触发了一个Job,此时Spark就会将这个Job划分成一个一个的Stage,然后形成一个一个的Task,
//最后会将这些Task提交到Executor上运行。
rdd2.foreach(x => {
//定义在RDD算子里面的变量,称为内部变量
val num2 = 7
//定义在RDD算子外面的变量,称为外部变量
num = num + x._2
//在算子内部打印num
//这里是在Executor上的Task中打印的,打印的是对num副本的操作后的结果。
println(num)
})
//这个代码也是在Driver端运行
println(num)
sc.stop()
}
}