1、在每一次运行Spark的任务的时候,算子内部的代码最终会被封装到Task中并发送到Executor中
2、但是如果算子内部中需要使用到算子外部的变量,此时就会将外部的变量也封装到Task中,然后再发送到Executor中
a、此时使用的Task实际上是外部变量的副本
b、Task的数量决定外部变量的副本数
3、因为Task的数量一定会比Exceutor的数量要多,此时Driver端就会将每一个外部变量广播给每一个Task。这样外部变量的副本的个数会很多,运行效率较低。
4、此时就可以将外部变量广播给每一个Exceutor,因为每一个Task都是在Exceutor上执行的,所以每一个Task会去对应的Exceutor上获取外部变量
5、广播变量是Exceutor之间共享的变量
6、广播变量在Driver中是一次性创建的,Exceutor对这些变量只能进行读取操作
7、一般会对本地的变量进行广播,例如是List集合、Map集合等。
8、广播变量广播到Executor中是有BlockManager所管理的
9、Executor上运行的Task都可以访问广播变量
广播变量获取流程如下:
1、首先是算子内部使用了广播变量
2、然后Task会向Executor申请获取广播变量,若Executor暂无数据,则
Executor首先会向同机架的其他Executor获取
若获取不到再向跨机架的Executor获取 如果还是获取不到,则
向Driver端获取数据
3、广播变量获取后会优先放入内存中,由BlockManager管理维护 后续Task可直接从MemoryStore中获取使用
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
object Demo24MapJoin {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
conf.setMaster("local")
conf.setAppName("Demo24MapJoin")
val sc: SparkContext = new SparkContext(conf)
val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")
val scoRDD: RDD[String] = sc.textFile("spark/data/stu/score.txt")
//将两张表进行关联,使用Spark中的join会产生shuffle的阶段会比较消耗时间
val stuRDD01: RDD[(String, String)] = stuRDD.map(kv => (kv.split(",")(0), kv))
val scoRDD01: RDD[(String, String)] = scoRDD.map(kv => (kv.split(",")(0), kv))
stuRDD01.join(scoRDD01).foreach(println)
/**
* 使用MapJoin:(实际上是对join的一种优化)大表join小表,中间没有shuffle的产生,可以将小表进行广播
*/
//首先将stuRDD01转化成本地的Map集合,在进行关联,前提也必须是一种kv形式的
val stuRDDMap: collection.Map[String, String] = stuRDD01.collectAsMap()
//将表进行关联
scoRDD01.map(kv=>{
stuRDDMap.getOrElse(kv._1,"")
}).foreach(println)
//第二种:使用广播变量,实现MapJoin,如果算子内部使用到外部的集合,就可以使用广播变量,可以通过调用变量.value在算子内部获取
val stuRDDMapBro: Broadcast[collection.Map[String, String]] = sc.broadcast(stuRDDMap)
scoRDD01.map(kv => {
stuRDDMapBro.value.getOrElse(kv._1, "")
}).foreach(println)
}
}