spark广播变量,累加器和SparkShuffle

在默认情况下,当 Spark 在集群的多个不同节点的多个任务上并行运行一个函数时,它会把函数中涉及到的每个变量,在每个任务上都生成一个副本。但是, 有时候需要在多个任务之间共享变量,或者在任务(Task)和任务控制节点 (Driver Program)之间共享变量。

为了满足这种需求,Spark 提供了两种类型的变量:

  1. 累加器 accumulators:累加器支持在所有不同节点之间进行累加计算(比 如计数或者求和)。
  2. 广播变量 broadcast variables:广播变量用来把变量在所有节点的内存 之间进行共享,在每个机器上缓存一个只读的变量,而不是为机器上的每 个任务都生成一个副本

广播变量

广播变量允许程序员将一个只读的变量缓存在每台机器上,而不用在任务之间传递变量。广播变量可被用于有效地给每个节点一个大量输入数据集的副本。Spark还尝试使用高效地广播算法来分发变量,进而减少通信的开销。

Spark的动作通过一系列的步骤执行,这些步骤由分布式的洗牌操作分开。Spark自动地广播每个步骤每个任务需要的通用数据。这些广播数据被序列化地缓存,在运行任务之前被反序列化出来。这意味着当我们需要在多个阶段的任务之间使用相同的数据,或者以反序列化形式缓存数据是十分重要的时候,显式地创建广播变量才有用。

import org.apache.spark.broadcast.Broadcast 
import org.apache.spark.rdd.RDD 
import org.apache.spark.{
    
    SparkConf, SparkContext} 
object BroadcastVariablesTest {
    
     
	 def main(args: Array[String]): Unit = {
    
     
	 	val conf: SparkConf = new SparkConf()
	 	.setAppName("wc")
	 	.setMaster("local[*]") 
	 	val sc: SparkContext = new SparkContext(conf) 
	 	sc.setLogLevel("WARN") 

	//不使用广播变量 
		val kvFruit: RDD[(Int, String)] = sc.parallelize(List((1,"apple"),(2,"orange"), (3,"banana"),(4,"grape"))) 
		val fruitMap: collection.Map[Int, String] =kvFruit.collectAsMap 
	//scala.collection.Map[Int,String] = Map(2 -> orange, 4 -> grape, 1 -> apple, 3 -> banana) 
		val fruitIds: RDD[Int] = sc.parallelize(List(2,4,1,3)) 
	//根据水果编号取水果名称 
		val fruitNames: RDD[String] = fruitIds.map(x=>fruitMap(x)) 	
		fruitNames.foreach(println) 
	//注意:以上代码看似一点问题没有,但是考虑到数据量如果较大,且 Task 数较多, 
	//那么会导致,被各个 Task 共用到的 fruitMap 会被多次传输 
	//应该要减少 fruitMap 的传输,一台机器上一个,被该台机器中的 Task 共用即可 
	//如何做到?---使用广播变量 
	//注意:广播变量的值不能被修改,如需修改可以将数据存到外部数据源,如 MySQL、Redis 
	
	println("=====================") 
	
		val BroadcastFruitMap: Broadcast[collection.Map[Int, String]] = sc.broadcast(fruitMap) 
		val fruitNames2: RDD[String] = fruitIds.map(x=>BroadcastFruitMap.value(x)) 
		fruitNames2.foreach(println) 
	}
}

累加器

累加器是仅仅被相关操作累加的变量,因此可以在并行中被有效地支持。它可以被用来实现计数器和总和。Spark原生地只支持数字类型的累加器,编程者可以添加新类型的支持。如果创建累加器时指定了名字,可以在Spark的UI界面看到。这有利于理解每个执行阶段的进程。(对于python还不支持)

累加器通过对一个初始化了的变量v调用SparkContext.accumulator(v)来创建。在集群上运行的任务可以通过add或者"+="方法在累加器上进行累加操作。但是,它们不能读取它的值。只有驱动程序能够读取它的值,通过累加器的value方法。
关键词:sc.broadcast()

import org.apache.spark.rdd.RDD 
import org.apache.spark.{
    
    Accumulator, SparkConf, SparkContext} 

object AccumulatorTest {
    
     
	def main(args: Array[String]): Unit = {
    
     
		val conf: SparkConf = new SparkConf()
		.setAppName("wc")
		.setMaster("local[*]")  	
		val sc: SparkContext = new SparkContext(conf) 
		sc.setLogLevel("WARN") 
	//使用 scala 集合完成累加 
		var counter1: Int = 0; var data = Seq(1,2,3) 
		data.foreach(x => counter1 += x ) 
		println(counter1)//6 
	
	println("+++++++++++++++++++++++++") 
	
	//使用 RDD 进行累加 
		var counter2: Int = 0; 
		val dataRDD: RDD[Int] = sc.parallelize(data) //分布式集合的[1,2,3] 			
		dataRDD.foreach(x => counter2 += x) 
		println(counter2)//0 
	//注意:上面的 RDD 操作运行结果是 0 
	//因为 foreach 中的函数是传递给 Worker 中的 Executor 执行,用到了 counter2 变量 
	//而 counter2 变量在 Driver 端定义的,在传递给 Executor 的时候,各个 Executor 都有了一份 counter2 
	//最后各个 Executor 将各自个 x 加到自己的 counter2 上面了,和 Driver 端的 counter2 没有关系 

//那这个问题得解决啊!不能因为使用了 Spark 连累加都做不了了啊! 
//如和解决?---使用累加器 
		val counter3: Accumulator[Int] = sc.accumulator(0) 
		dataRDD.foreach(x => counter3 += x) 
		println(counter3)//6 
 	 } 
}

Sparkshuffle

spark shuffle 演进的历史
  • Spark 0.8及以前 Hash Based Shuffle
  • Spark 0.8.1 为Hash Based Shuffle引入File Consolidation机制
  • Spark 0.9 引入ExternalAppendOnlyMap
  • Spark 1.1 引入Sort Based Shuffle,但默认仍为Hash Based Shuffle
  • Spark 1.2 默认的Shuffle方式改为Sort Based Shuffle
  • Spark 1.4 引入Tungsten-Sort Based Shuffle
  • Spark 1.6 Tungsten-sort并入Sort Based Shuffle
  • Spark 2.0 Hash Based Shuffle退出历史舞台
1、未经优化的HashShuffleManager

在这里插入图片描述

2、优化后的HashShuffleManager

​ 开启consolidate机制之后,在shuffle write过程中,task就不是为下游stage的每个task创建一个磁盘文件了,此时会出现shuffleFileGroup的概念,每个shuffleFileGroup会对应一批磁盘文件,磁盘文件的数量与下游stage的task数量是相同的。
在这里插入图片描述

3、SortShuffle

​ 后面就引入了 Sort Based Shuffle, map端的任务会按照Partition id以及key对记录进行排序。同时将全部结果写到一个数据文件中,同时生成一个索引文件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mxz2JWvP-1636637911096)(D:/桌面/资料/复习/岗位指导/大数据开发/assert/1582793774290.png)]

4、sortshuffle的bypass运行机制

bypass运行机制的触发条件如下:

  • shuffle map task数量小于spark.shuffle.sort.bypassMergeThreshold=200参数的值。

  • 不是聚合类的shuffle算子。

    ​ 每个task会为每个下游task都创建一个临时磁盘文件,并将数据按key进行hash然后根据key的hash值,将key写入对应的磁盘文件之中。当然,写入磁盘文件时也是先写入内存缓冲,缓冲写满之后再溢写到磁盘文件的。最后,同样会将所有临时磁盘文件都合并成一个磁盘文件,并创建一个单独的索引文件。

    ​ 该过程的磁盘写机制其实跟未经优化的HashShuffleManager是一模一样的,因为都要创建数量惊人的磁盘文件,只是在最后会做一个磁盘文件的合并而已。因此少量的最终磁盘文件,也让该机制相对未经优化的HashShuffleManager来说,shuffle read的性能会更好

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T0p0C8px-1636637911097)(D:/桌面/资料/复习/岗位指导/大数据开发/assert/1582793941507.png)]

5、Tungsten-Sort Based Shuffle

​ 后面就引入了 Tungsten-Sort Based Shuffle, 这个是直接使用堆外内存和新的内存管理模型,节省了内存空间和大量的gc, 是为了提升性能。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43278189/article/details/121276344