RDD持久化、广播、累加器

                                                                         RDD持久化、广播、累加器

本期内容:

1 action实战

2 RDD持久化剖析及实战

3 广播和累加器实战

启动HDFS
启动Spark-all.sh
启动Spark-shell 进行测试
启动后,开始对action的操作(reduce、count、collect、saveAsTextFile、take、foreach、countByKey)

一、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

(3)count

scala> numbers.count
res3: Long = 50
(4)take
(5)countByKey(对应的可以对应多少不同的value)
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)
(6)saveAsTextFile
scala> sc.textFile("/library/wordcount/input/Data").flatMap(_.split(" ").map(word =>(word,1)).reduceByKey(_+_,1).saveAsTextFile("/library/wordcount/input"))
二、RDD持久化:
    第一种情况基于action的,
    第二种是persist。
Spark在默认情况下,数据存于内存中,适合内存的迭代,中间不会产生临时数据,但分布式系统风险非常高,即出错率较高。RDD有血统关系的,如果后面的RDD的数据分片出错或者RDD出错,可以根据前面RDD的血统关系计算出来,但如果在链条较大的任务,没有对父RDD进行persist或者Cache(用内存空间替换计算时间,时间与空间的一种权衡),就需要重新计算。所以在以下几种情况下要不要保存数据?
Persist的几种情况:
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之前(框架默认帮助我们把数据持久化到本地磁盘)
注:前三种都是手动,第五种框架自动进行。因为如果shuffle出错,所以依赖的父RDD都要全部重新计算,所以必须在shuffle前进行persist。
从源码角度分析perisist:
  /** 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(内存分布式文件系统)
  MEMORY_AND_DISK,极大的防止oom,比较安全,但是运行速度不是很快,这一点像对disk做raid5的过程。
MEMORY_ONLY_SER序列化:减少数据体积,介绍空间,若出现oom,可以将内存中的数据序列化
   MEMORY_AND_DISK_SER_2 将计算的结果放两份副本,好处是在假设两份内存有一份挂掉后,可以立即切换到另一台机器上的内存中。
在节点上运行实例:
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  
注: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
}
(2)广播(线程中共享)不必每个task都拷贝一份副本,因为它是全局唯一的,极大的减少oom,减少通信,冗余、共享变量等。广播是将数据广播到Executor的内存中,其内部所以的任务都会只享有全局唯一的变量,减少网络传输。
          text在读取数据时候,拷贝一份的数据副本(变量),因为函数式编程(变量不变),不拷贝状态容易被改变,数据量小(1、引用较小2、数据本身小),变量大容易产生oom(task拷贝数据 在内存中运行),网络传输慢,要提前,冗余、共享,减少通信。
广播变量:

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

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

(本段摘自:http://blog.csdn.net/happyanger6/article/details/46576831


join:一般都会有shuffle
    广播是由Driver 广播会广播到Executor所在的内存中,所以task才能访问。
    广播是由Driver发给当前Application分配的所有Executor内存级别的全局只读变量,Executor中的线程池中的线程共享该全局变量,极大的减少了网络传输(否则的话每个Task都要传输一次该变量)并极大的节省了内存,当然也隐形的提高的CPU的有效工作。
Executor共享数据两种方式:HDFS 和tachyon
创建rdd,text使用广播变量(Executor中运行)
注:只能由SparkContext进行广播
实战创建广播:
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>
(3)累加器(获取全局唯一的状态对象,SparkContext创建,被Driver控制,在Text实际运行的时候,每次都可以保证修改之后获取全局唯一的对象,Driver中可读,Executor可读)

        累加器是仅仅被相关操作累加的变量,因此可以在并行中被有效地支持。它可以被用来实现计数器和总和。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
   累加器  在记录集群全局唯一的状态的时候极其重要,保持唯一的全局状态的变量,所以其重要性不言而喻。
Driver中取值,Executor中计算,
1、累计器全局(全集群)唯一,只增不减(Executor中的task去修改,即累加)
2、累加器是Executor共享;
我的理解应该是对的,集群全局变量,谁操作,从driver上拿去操作,然后下个Executor在用的时候,拿上个Executor执行的结果,也就是从Driver那里拿。

王家林老师是大数据技术集大成者,中国Spark第一人:

DT大数据梦工厂

新浪微博:www.weibo.com/ilovepains/

微信公众号:DT_Spark

博客:http://.blog.sina.com.cn/ilovepains

TEL:18610086859

Email:18610086859@vip.126.com












评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值