RDD持久化、广播、累加器
本期内容:
1 action实战
2 RDD持久化剖析及实战
3 广播和累加器实战
一、action操作
(1)reduce(reduce是action的一种,会触发作业)(2)collect
def collect(): Array[T] = withScope {
val results = sc.runJob(this, (iter: Iterator[T]) => iter.toArray)
Array.concat(results: _*)
}
collect将Executor中的运行结果收集到Driver上,
如果想在命令终端中看到执行结果,就必须collect,凡是Action级别的操作都会触发sc.runJob
。
scala> numbers.count
res3: Long = 50
scala> val scores = Array(Tuple2(1,100),Tuple2(1,100),Tuple2(2,100),Tuple2(2,100),Tuple2(3,100))
scores: Array[(Int, Int)] = Array((1,100), (1,100), (2,100), (2,100), (3,100))
scala> val data = sc.parallelize(scores) //将其转化为RDD
scala> val datas = data.countByKey
datas: scala.collection.Map[Int,Long] = Map(1 -> 2, 3 -> 1, 2 -> 2)
scala> sc.textFile("/library/wordcount/input/Data").flatMap(_.split(" ").map(word =>(word,1)).reduceByKey(_+_,1).saveAsTextFile("/library/wordcount/input"))
1,某步骤计算特别耗时(重新算代价特别大);2,计算链条特别长的情况 (重新算代价特别大) ;3,checkpoint所在的RDD也一定要持久化数据( checkpoint是lazy的,框架本身会对checkpoint的RDD触发新的job,不进行persist的话,进行checkpoint的时候数据就会重新计算一遍,所以checkpoint之前一定要进行 persist,因为在checkpoint前有了persist的前提下,计算过一遍之后,再进行计算的时候计算速度非常快,手动rdd.cache/ rdd.persist ->rdd.checkpoint );4,shuffle之后(因为shuffle要进行网络传输,网络传输风险大,数据极易丢失,所以shuffle之前进行persist避免数据丢失);5,shuffle之前(框架默认帮助我们把数据持久化到本地磁盘)
/** Persist this RDD with the default storage level (`MEMORY_ONLY`). */
def persist(): this.type = persist(StorageLevel.MEMORY_ONLY)
/** Persist this RDD with the default storage level (`MEMORY_ONLY`). */
def cache(): this.type = persist()
存储级别是StorageLevel:
可以看出cache是persist的一种特殊情况,cache将数据放在内存中,存储级别是StorageLevel
/**
* Various [[org.apache.spark.storage.StorageLevel]] defined and utility functions for creating
* new storage levels.
*/
object StorageLevel {
val NONE = new StorageLevel(false, false, false, false) //不保存
val DISK_ONLY = new StorageLevel(true, false, false, false) //保存在磁盘上
val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2) //在两个节点上保存有
val MEMORY_ONLY = new StorageLevel(false, true, false, true) //默认情况下,数据保存在内存,耗费内存方便迭代
val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2) //在两台机器上缓存
val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false) //序列化,减少数据体积,弊端:使用数据的时候要使用反序列化,耗费CPU
val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
val MEMORY_AND_DISK = new StorageLevel(true, true, false, true) //Spark优先考虑内存,内存不够的情况下将数据放在内存中
val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false) //序列化
val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
val OFF_HEAP = new StorageLevel(false, false, true, false) //tachyon(内存分布式文件系统)
sc.textFile("/library/wordcount/input/Data").flatMap(_.split(" ")).map(word => (word, 1)).reduceByKey(_+_,1).cache.count //运行结果较慢
val cached = sc.textFile("library/wordcount/input/Data").flatMap(_.split(" ")).map(word => (word, 1)).reducByKey(_+_,1).cache
1、进行unpersist,persist是lazy级别的,unpersist是eager级别的(立即执行,清空缓存)2、丢失缓存数据,在没有unpersist的情况下,优先使用计算数据,缓存数据会被丢弃掉。
def unpersist(blocking: Boolean = true): this.type = {
logInfo("Removing RDD " + id + " from persistence list")
sc.unpersistRDD(id, blocking)
storageLevel = StorageLevel.NONE
this
}
广播变量允许程序员将一个只读的变量缓存在每台机器上,而不用在任务之间传递变量。广播变量可被用于有效地给每个节点一个大输入数据集的副本。Spark还尝试使用高效地广播算法来分发变量,进而减少通信的开销。
Spark的动作通过一系列的步骤执行,这些步骤由分布式的洗牌操作分开。Spark自动地广播每个步骤每个任务需要的通用数据。这些广播数据被序列化地缓存,在运行任务之前被反序列化出来。这意味着当我们需要在多个阶段的任务之间使用相同的数据,或者以反序列化形式缓存数据是十分重要的时候,显式地创建广播变量才有用。
(本段摘自:http://blog.csdn.net/happyanger6/article/details/46576831)
scala> val numbers = 10
numbers: Int = 10
//
scala> val myBraodCast = sc.broadcast(numbers)
myBraodCast: org.apache.spark.broadcast.Broadcast[Int] = Broadcast(4)
//根据数据集创建RDD
scala> val data = sc.parallelize(1 to 100)
data: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[5] at parallelize at <console>:27
//通过在一个变量v上调用SparkContext.broadcast(v)可以创建广播变量。广播变量是围绕着v的封装,广播变量是围绕着v的封装,可以通过value方法访问这个变量。
scala> val bn = data.map(_* broadcastNumber.value)
<span style="font-size:12px;">//直接collect的结果
scala> bn.collect
res1: Array[Int] = Array(10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 210, 220, 230, 240, 250, 260, 270, 280, 290, 300, 310, 320, 330, 340, 350, 360, 370, 380, 390, 400, 410, 420, 430, 440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590, 600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740, 750, 760, 770, 780, 790, 800, 810, 820, 830, 840, 850, 860, 870, 880, 890, 900, 910, 920, 930, 940, 950, 960, 970, 980, 990, 1000)</span>
累加器是仅仅被相关操作累加的变量,因此可以在并行中被有效地支持。它可以被用来实现计数器和总和。Spark原生地只支持数字类型的累加器,编程者可以添加新类型的支持。如果创建累加器时指定了名字,可以在Spark的UI界面看到。这有利于理解每个执行阶段的进程。(对于python还不支持)
累加器通过对一个初始化了的变量v调用SparkContext.accumulator(v)来创建。在集群上运行的任务可以通过add或者"+="方法在累加器上进行累加操作。但是,它们不能读取它的值。只有驱动程序能够读取它的值,通过累加器的value方法。
/**
* Create an [[org.apache.spark.Accumulator]] variable of a given type, with a name for display
* in the Spark UI. Tasks can "add" values to the accumulator using the `+=` method. Only the
* driver can access the accumulator's `value`.
*/
def accumulator[T](initialValue: T, name: String)(implicit param: AccumulatorParam[T])
: Accumulator[T] = {
val acc = new Accumulator(initialValue, param, Some(name))
cleaner.foreach(_.registerAccumulatorForCleanup(acc))
acc
}
累加器的特征:
全局的,Accumulator:对于Executor只能修改但不可读,只对Driver可读(因为通过Driver控制整个集群的状态),不同的executor 修改,不会彼此覆盖(枷锁机制)
scala> val sum = sc.accumulator(0)
sum: org.apache.spark.Accumulator[Int] = 0
scala> val data = sc.parallelize(Array(1,2,3,4,5,6,6,7,8,8,9))
data: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[2] at parallelize at <console>:27
scala> val result = data.foreach(item =>sum +=item)
result: Unit = ()
scala> println(sum)
59
1、累计器全局(全集群)唯一,只增不减(Executor中的task去修改,即累加);2、累加器是Executor共享;
DT大数据梦工厂
新浪微博:www.weibo.com/ilovepains/
微信公众号:DT_Spark
博客:http://.blog.sina.com.cn/ilovepains
TEL:18610086859
Email:18610086859@vip.126.com