Spark1.6.0学习心得(三):shuffle、persist、cache、shared variable

本文深入解析Spark中的Shuffle机制,探讨其在不同操作中的触发原因,以及如何影响性能。从reduceByKey操作开始,阐述数据重新分布的过程,分析shuffle对内存和磁盘I/O的影响,提出优化建议,并介绍RDD持久化策略及共享变量的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Shuffle operations

Spark触发事件叫作shuffle,shuffle是spark的对于重新分布式数据机制,因此它在不同的分区分组不相同。其中包含复制数据在executors和机器上,执行shuffle是复杂和耗时的操作。

Background

为了进一步了解shuffle期间,发生的事情,思考这个reduceByKey操作的例子。

reduceByKey操作产生了一个新的RDD,rdd.reduceByKey(func:(v,v) => v),最大的挑战是,并不是所有的相同key的数据在相同的partitions中,甚至在同一台机器上,但是必须被组合去计算结果。

在spark中,为了在必要的地方指定的操作,数据并不是分布在分区之间。在计算期间,单个任务操作单个分区,为了管理所有数据,为单个reduceByKey减少任务去执行,Spark需要去执行一个 all-to-all的操作。它必须读取分区来查找所有的key的value,并且获取所有的value到一起在分区之间,去计算每个key的最终结果--这叫做shuffle。

尽管shuffle数据的每个分区element集都是确定性的,并且分区的排序也是如此,元素的排序不是确定的。如果希望在shuflle后获取预期的排序,可以通过以下方式:

  • mapPartitions to sort each partition using, for example, .sorted
  • repartitionAndSortWithinPartitions to efficiently sort partitions while simultaneously repartitioning
  • sortBy to make a globally ordered RDD

产生shuffle的原因:RDD分区之间的宽依赖

名称描述场景
宽依赖父RDD每个分区的数据可能被多个子RDD分区使用

一个父RDD的分区对应所有的子RDD的分区

一个父RDD分区对应非全部的的多个RDD分区

窄依赖父RDD每个分区的只被子RDD的一个分区使用 

如下图:

引起shuffle的操作有:

类型函数
maprepartition:repartition、coalesce
ByKey:groupByKey、reduceByKey
Join:cogroup、join

Performance Impact

shuffle是一个昂贵的操作,因为它包括disk I/O,data serialize, network I/O。为了shuffle管理数据,Spark 生成一系列任务 -- map 任务,去管理数据,还有一系列的reduce任务去聚合。

本质上,单个map任务的结果被保存在内存中,其他内存不能再保存。然后,结果基于目标分区被排序,并且写入到单个文件中。在reduce端,reduce task 读取相关排序的blocks。

shuffle操作能够消耗大量的heap内存,之前它雇用内存数据结构去管理记录或者之后转移记录。特别是,reduceByKey和aggregateByKey创建这些结果在map端,‘ByKey 操作产生数据结构在map端。当数据在内存超过一定的值,spark将划分这些表到disk,增加磁盘I/O和增加垃圾的回收。-- 优化点

Shuffle也能产生大量的中间文件在disk。如spark1.3,这些文件被保存,直到相应的RDD不再使用,且垃圾回收。这样做的目的是,shuffle文件不需要重新创建如果血脉是重新计算。垃圾回收可能仅仅发生在一个长期的时间,如果应用保留对这些RDD的引用,或者如果GC不经常出现。这意味长时间的运行Spark jobs可能会消耗大量的disk 存储。临时目录通过 spark.local.dir 进行配置。--这是一个优化点

RDD Persistence

Spark最重要的功能之一,是持久化(或者缓存)数据集在内存和操作之间。当你持久化RDD时,每个node存储它在内存中计算任何分区,并且在该数据的其他action中重用这个数据。这使得之后的操作更加快。Caching是一个关键工具对于简化计算和快速交互使用。

使用persist()或者cache()方法去缓存RDD。第一次计算在action,RDD将被保存在node的内存中。Spark的缓存是容错的 -- 如果RDD的任何分区丢失,这个分区将被重新计算,使用那些创建它的transformation。

另外,能够使用不能的存储级别每个持久化RDD,如

level描述 
MEMORY_ONLY

default level.

存储在JVM中的RDD没有序列化,如果内存不够,一些分区将不被缓存,并且在它们被需要时,进行动态的重新计算

 The cache() method is a shorthand for using the default storage level,

 
MEMORY_ONLY_SER存储在JVM中的RDD有序列化,更高效的存储,但是需要更多的CPU消耗 
MEMORY_AND_DISK存储在JVM中的RDD没有序列化,如果内存不够,一些分区将保存在本地disk,当使用时,再进行读取 
MEMORY_AND_DISK_SER存储在JVM中的RDD序列化,如果内存不够,一些分区将保存在本地disk,当使用时,再进行读取 

MEMORY_ONLY_2,

MEMORY_AND_DISK_2,

Same as the levels above, but replicate each partition on two cluster nodes. 
DISK_ONLY

Store the RDD partitions only on disk.

 

OFF_HEAP (experimental)

Store RDD in serialized format in Tachyon. 相比MEMORY_ONLY_SER,

OFF_HEAP减小了GC回收的消耗并且允许executors更加智能和共享内存池, 在环境有有大量的内存和更多并行化的应用中,共享OFF_HEAP更加吸引人的。 与此同时, as the RDDs reside in Tachyon, 执行程序崩溃不会导致内存缓存丢失。 In this mode, the memory in Tachyon is discardable. Thus, Tachyon does not attempt to reconstruct a block that it evicts from memory. If you plan to use Tachyon as the off heap store, Spark is compatible with Tachyon out-of-the-box.

思考:如何释放当程序崩溃以后的数据

https://www.ibm.com/developerworks/cn/opensource/os-cn-spark-tachyon/

Tachyon 是 AMPLab 开发的一款内存分布式文件系统。它介于计算层和存储层之间,可以简单的理解为存储层在内存内的一个 Cache 系统。

Which Storage Level to Choose

存储的级别意味提供一个在memory使用与CPU效率之间的平衡。挑选原则:

操作级别
allowing operations on the RDDs to run as fast as possible.MEMORY_ONLY
more space-efficient, but still reasonably fast to access.MEMORY_ONLY_SER 
fast fault recovery

MEMORY_ONLY_2,

MEMORY_AND_DISK_2,

n environments with high amounts of memory or multiple applicationsOFF_HEAP
  • Don’t spill to disk unless the functions that computed your datasets are expensive, or they filter a large amount of the data. Otherwise, recomputing a partition may be as fast as reading it from disk.

  • Use the replicated storage levels if you want fast fault recovery (e.g. if using Spark to serve requests from a web application). All the storage levels provide full fault tolerance by recomputing lost data, but the replicated ones let you continue running tasks on the RDD without waiting to recompute a lost partition.

  • In environments with high amounts of memory or multiple applications, the experimental OFF_HEAP mode has several advantages:

    • It allows multiple executors to share the same pool of memory in Tachyon.
    • It significantly reduces garbage collection costs.
    • Cached data is not lost if individual executors crash.
  • Removing Data

    自动:drops out old data partitions in a least-recently-used (LRU) fashion

       手动RDD.unpersist()

Shared Variables

名称分类描述
Shared Variablesbroadcast

允编程人员保存只读变量缓存在每台机器,而不是随任务一起传送副本,以高效的方式向每个node提供大量输入数据集的副本。

Spark actions由一系列的stages,stages的划分依据由分化的shuffle。Spark自动广播需每个stage任务被需要的公共数据。通过广播方式的数据以序列化的形式缓存,在每个任务运行前反序列化数据。

actions --1:n--> stage --1:n--> task

创建broadcast variables SparkContext.broadcast(v)

注意:广播的类型在广播之后不能进行更改

accumulator

Spark natively supports accumulators of numeric types, and programmers can add support for new types. 

SparkContext.accumulator(v)

在action中执行,只执行一次,不重复执行

  

SparkContext.accumulableCollection

对集合进行元素进行操作

 

创建broadcast variable

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)

自定义Accumulator

object VectorAccumulatorParam extends AccumulatorParam[Vector] {
  def zero(initialValue: Vector): Vector = {
    Vector.zeros(initialValue.size)
  }
  def addInPlace(v1: Vector, v2: Vector): Vector = {
    v1 += v2
  }
}

// Then, create an Accumulator of this type:
val vecAccum = sc.accumulator(new Vector(...))(VectorAccumulatorParam)

 对集合元素进行操作

object MyAccumulator {
	def main(args: Array[String]): Unit = {
		val conf = new SparkConf().setAppName("my accumulator spark").setMaster("local[*]")
		val context = new SparkContext(conf)

		val multiAccumulators = context.accumulableCollection(mutable.MutableList(1,2,3))

		context.parallelize(Array(4,5,6,7)).map(x => {
			multiAccumulators += x
			x
		}).collect()
		multiAccumulators.value.foreach(println)

		context.stop()
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值