参考官网:http://spark.apache.org/docs/latest/rdd-programming-guide.html#shared-variables
默认情况下,如果在一个算子函数中使用到了某个外部的变量,那么这个变量的值会被拷贝到每个task中。此时每个task只能操作自己的那份变量副本。意思就是说当Spark在集群的多个不同节点的多个任务上并行运行一个函数的时候,它会把函数中涉及到的每个变量在每个节点每个任务上都生成一个副本。Spark 操作实际上操作的是这个函数所用变量的一个独立副本。这些变量被复制到每台机器上,并且这些变量在远程机器上的所有更新都不会传递回驱动程序。通常跨任务的读写变量是低效的(就是多线程的去操作这些变量)。
如果需要在多个任务之间共享变量,就需要共享变量了。
Spark为此提供了两种有限类型的共享变量,一种是Broadcast Variable(广播变量),另一种是Accumulator(累加变量)。Broadcast Variable会将使用到的变量,仅仅为每个节点拷贝一份,更大的用处是优化性能,减少网络传输以及内存消耗。Accumulator则可以让多个task共同操作一份变量,主要可以进行累加操作。
Broadcast Variable
广播变量允许开发人员在每个节点(Worker or Executor)缓存只读变量,而不是在Task之间传递这些变量。使用广播变量能够高效地在集群每个节点创建大数据集的副本。同时Spark还使用高效的广播算法分发这些变量,从而减少通信的开销。
广播变量是将变量复制到每一台机器上而不是像普通变量那样每个task复制,它是高效的,广播变量只能读取,并不能修改。 经典应用是大表与小表的join中通过广播变量小表来实现以brodcast join取代reduce join。
举个例子,假如说有在函数的外部定义了一个普通的变量,这个变量的大小为10M,那么现在你去用一个函数操作一个RDD,比如这样:RDD.foreach(x =>{对这个变量进行一波操作}), 里面有1000个task,那么每个task都会有一个变量的拷贝,现在就需要10G的资源,这样肯定是不行的。如果用广播变量的话,这个数据集比较大,只是每个机器拷贝一个副本,很多的task共同使用这个副本。
Spark的任务操作一般会跨越多个阶段,对于每个阶段内的所有任务所需要的公共数据,Spark都会自动进行广播。
看官网例子:
//广播变量
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 join举例
在spark core里面,如何实现广播变量?
经典应用比如:大表与小表的join中通过广播变量小表来实现以brodcast join 取代reduce join
用map join可以实现它,不过map join在广播变量里是叫broadcast join(mapjoin在hive优化里讲到过)。
例如:
目前有两个表
表1:
学号 | 学号 |
---|---|
601 | zhangsan |
602 | lisi |
603 | wangwu |
表2:
学号 | 年龄 | 性别 |
---|---|---|
601 | 25 | man |
602 | 22 | woman |
603 | 18 | woman |
604 | 28 | man |
通过学号进行关联,想要得到如下结果:
学号 | 名字 | 年龄 | 性别 |
---|---|---|---|
601 | zhangsan | 25 | man |
602 | lisi | 22 | woman |
Accumulator
可以叫计数器或者累加器
累加器是通过 一个关联和交换的操作 只能进行累加的变量,因此可以有效地支持并行,多个task对一个变量并行的操作。它们可以用来实现计数器(如MapReduce)或求和的操作。Spark 本身支持数字类型的累加器,程序员可以添加对新类型的支持,自定义的。
作为用户,您可以创建命名或匿名的累加器。
可以通过SparkContext.longAccumulator()或者SparkContext.doubleAccumulator()来创建累加器。运行在集群中的任务,就可以使用add()方法来把数值累加到累加器上。但是任务节点执行做累加操作,不能读取累加器的值,只有任务控制节点(Driver Program)可以使用value方法来读取。
参数有两个(Int,String),第一个参数为初始累加值,默认为0,第二个参数为累加器的名字。
在IDEA中可以看到定义的时候有一个带有名字的,有一个不带有名字的,看自己需要来选择。
看下官网例子:
scala> val accum = sc.longAccumulator("My Accumulator")
accum: org.apache.spark.util.LongAccumulator = LongAccumulator(id: 0, name: Some(My Accumulator), value: 0)
scala> sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum.add(x))
scala> accum.value
res2: Long = 10
在Web UI的stage界面里面,可以看到上面的,从界面可以看到,总共有8个task,8个并行度。我自己跑的话就只有两个task。
累加器只能由Spark内部进行更新,并保证每个任务在累加器的更新操作仅执行一次,也就是说重启任务也不应该更新。在转换操作中,用户必须意识到任务和作业的调度过程重新执行会造成累加器的多次更新。
参考博客:
https://blog.csdn.net/anbang713/article/details/81588829
https://www.cnblogs.com/zzhangyuhang/p/9005347.html
https://blog.csdn.net/qq_32641659/article/details/90183882#23_REST_API_64