Spark cache和persist使用场景和区别、广播和累加器使用方式和原理

@DT大数据梦工厂

一、RDD持久化

         1)操作RDD的时候怎么保存结果?

         2)cache或persist,checkpoint

想了解更多RDD的操作类型请点击:Spark Transformation、Action、Controller三种操作类型的使用

1.1 什么时候需要persist ?

(1)某步骤计算特别耗时;

(2)计算链条特别长;

(4)checkpoint所在的RDD也一定要persist(在checkpoint之前,手动进行checkpoint)持久化数据,为什么?checkpoint的工作机制,是lazy级别的,在触发一个作业的时候,开始计算job,job算完之后,转过来spark的调度框架发现RDD有checkpoint标记,转过来框架本身又基于这个checkpoint再提交一个作业,checkpoint会触发一个新的作业,如果不进行持久化,进行checkpoint的时候会重算,如果第一次计算的时候就进行了persist,那么进行checkpoint的时候速度会非常的快。

(5)shuffle之后;

(6)shuffle之前(框架默认帮助我们数据持久化到本地磁盘)

1.2 Spark Cache

cache是persist的一种特殊情况

  /**Persist this RDD with the default storage level (`MEMORY_ONLY`). */
  defpersist(): this.type = persist(StorageLevel.MEMORY_ONLY)
  /**Persist this RDD with the default storage level (`MEMORY_ONLY`). */
  def cache(): this.type = persist()

MEMORY_AND_DISK:spark优先考虑内存,如果内存足够的话就放在内存中,如果内存不够用的话,就把数据放在磁盘。

MEMORY_AND_DISK和MEMORY有什么区别呢?两个都优先考虑内存,但是MEMORY必须是内存,MEMORY的角度如果内存不够用的话就出现OOM,或数据会丢失。但是MEMORY_AND_DISK会把数据存在磁盘,不会出现数据丢失,主要会防止OOM,它会极大的降低OOM的可能,但是不管用什么方式都有可能出现OOM。

MEMORY_ONLY_SER_2,因为在内存中会有两个副本,如果其中一份内存崩溃就可以立即切换到另一份副本进行快速的计算,这就极大提升了计算,用空间换时间。

通过实例观察cache:

(1)没有进行cache时耗时:

17/01/21 14:02:04 INFOscheduler.DAGScheduler: Job 7 finished: count at <console>:28, took4.480135 s
res10: Long = 33254
scala>sc.textFile("/data/5000-8.txt.1").flatMap(_.split("")).map(word => (word, 1)).reduceByKey(_+_, 1).count

(2)加了一个cache第一次执行

17/01/21 14:03:48 INFOscheduler.DAGScheduler: Job 9 finished: count at <console>:28, took4.617932 s
res12: Long = 33254
scala>sc.textFile("/data/5000-8.txt.1").flatMap(_.split("")).map(word => (word, 1)).reduceByKey(_+_, 1).cache.count

(3)加了一个cache第二次执行

17/01/21 14:04:49 INFOscheduler.DAGScheduler: Job 11 finished: count at <console>:28, took0.462498 s
res14: Long = 33254
scala>sc.textFile("/data/5000-8.txt.1").flatMap(_.split("")).map(word => (word, 1)).reduceByKey(_+_, 1).cache.count

(4)当赋值后再次进行:速度提升了二十倍不止:

val cached =sc.textFile("/data/5000-8.txt.1").flatMap(_.split("")).map(word => (word, 1)).reduceByKey(_+_, 1).cache
17/01/21 14:08:00 INFOscheduler.DAGScheduler: Job 14 finished: count at <console>:30, took0.079047 s
res17: Long = 33254
scala> cached.count

cache放在内存中只有一份副本,只放在内存中,放在内存的Heap中。不会保存在什么目录或者HDFS上,有可能在很多机器的内存,数据量比较小,也有可能在一台机器的内存中。

persist内存不够用时数据保存在磁盘的哪里???

Executor local directory,executor运行的时候,有一个local direcory(本地目录,这是可以配置的)

cache通过unpersit强制把数据从内存中清除掉,如果计算的时候,肯定会先考虑计算需要的内存,这个时候,cache的数据就与可能丢失。

分析原因注意:::cache之后一定不能立即有其它算子,不能直接去接算子。

因为在实际工作的时候,cache后有算子的话,它每次都会重新触发这个计算过程。

cache不是一个action,运行它的时候没有执行一个作业

cache缓存如何让它失效:unpersist,它是立即执行的。

persistlazy级别的(没有计算),unpersisteager级别的。

问题:cache本身不能指定机器做缓存,这个是框架帮你做的;

序列化一般使用kryo序列化器

2份副本不会同时读数据,实际上只读一份,另一份是备胎。

如果数据缓存到一台机器上,如果数据量比较小的话,就放在本地执行计算,如果数据量比较大的话,不幸数据被放在一台机器上了,它会先排队,宁愿等一会儿,如果不行了,就从其它机器上抓取缓存。一般情况下是不会跨越机器抓缓存的。

二、Spark广播实战

意义:构建算法很重要

(1)降低网络传输的数据量

(2)降低内存的使用

(3)加快程序的运行速度

为什么要有广播???

大变量:减少网络,减少数据冗余,默认每个Task执行的时候,每次都会拷贝一个数据副本(拷贝这个大变量),为什么要拷贝数据副本??函数式编程变量不变原则,如果不拷贝别人改变了状态的话,就不一致了,(拷贝更好的保证了状态的一致性,不好的就是,消耗大量的内存OOM)一个有一百万个task就要拷贝100万个大变量。

join的时候,一般都会有shuffle。

广播一定是由Driver发给当前Application分配的所有的Executor内存级别的全局只读变量。Executor中的线程池中的线程共享该全局变量,极大的减少了网络传输(否则的话每个Task都要传输该变量)并极大的节省了内存,当然也隐形的提高了CPU的有效工作。

(全局变量放到executor的内存中。)

如下图,普通情况下,list会发三次,(因为有三个task)有三个list的数据副本,网络传输慢,内存占用大,如果变量比较大,则极易出现OOM

如果是广播的方式,如上图Broadcast到Executor的内存中,list只有一份副本,共享唯一的一份广播变量,极大的较少了网络传输和内存消耗:

广播会给executor广播一份数据,所有task共享,而不是给每个task

Executor共享数据有两种方式:

方式一:基于HDFS;

方式二:基于Tacyhon,一个Executor是一个进程,资源独立不能共享。

broadcast需要手动的自己去写,广播变量和具体几个core没有任何关系。

一个Executor即是只有一个线程,它会不会只运行一个task???只能说同时只能运行一个task,只是很多的task排队。

广播变量不需要手动销毁,只要应用程序存在它就会存在。应用程序销毁的时候它就会销毁。广播的数据本来就是只读的,不可修改。

注意:::广播只能由sparkcotext来广播。

scala> val number = 10
number: Int = 10
scala> val broadcastNumber =sc.broadcast(number)
broadcastNumber:org.apache.spark.broadcast.Broadcast[Int] = Broadcast(40)//创建了一个广播对象。里面的类型是整数。

谁使用广播变量??? task

scala> val data = sc.parallelize(1 to1000)
data: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[66]at parallelize at <console>:27
scala> val bn = data.map(_ * broadcastNumber.value) //广播变量的使用
bn: org.apache.spark.rdd.RDD[Int] =MapPartitionsRDD[67] at map at <console>:33

广播变量中可以有很多的变量,可以用javabean等数据结构封装

三、Spark累加器实战

    累加器在Spark集群中是一个全局的指针不减变量,在所有的Executor中只能修改累加器的内容,也就是只能增加累加器的内容。 在Executor中不能读累加器的内容,在driver中能读累加器的内容。

Accumulator累加器:有了广播(广播全局变量)为什么需要累加器??

因为累加器是全局级别的,且executor中的task只能够去修改,只能去增加它的内容,只有driver可读.

Accumulator:Executor只能修改但是不可读,只对Driver可读,在记录集群的状态,尤其是全局唯一的状态,至关重要。

作为一个分布式集群肯定需要一个全局唯一的一个东西(例如:redisspark提供了一个全局唯一的一个累加器,它就可以唯一的保持一个全局状态。只有Driver可读,(因为我们是通过Driver控制整个集群的状态的)

累加器的原理:它是被Driver控制的,是通过sparkcontext创建的,创建后被Drive控制,所以在实际task运行的时候,它可以保证每次修改之后,Drive可读,来获取全局唯一的状态对象。

不同的Executor修改一定不会彼此覆盖,这个东西就相当于加锁了。进程级别数据安全。累加器的核心在Driver上。

通过spark context去创建一个积累器,这个累加器交给Executor,它的修改是在具体的task中做的,在task中怎么去修改呢???task每次拿到的都是accumulator这个全局唯一的写的一个实例,在一次运行的时候,都有一个计算的标记,在Driver访问的时候,会进行一个最后状态的读取。

修改是在具体的task中去做到,从累加器的角度,他每次拿到的都是accumulator全局唯一的实例。

scala> val sum = sc.accumulator(0)
sum: org.apache.spark.Accumulator[Int] = 0
scala> val data = sc.parallelize(1 to 100)
data: org.apache.spark.rdd.RDD[Int] =ParallelCollectionRDD[68] at parallelize at <console>:27
scala> val result = data.foreach(item=> sum +=item)

1,累加器在全局是唯一的,每次操作只增不减

2,在Executor中只能修改它,只能增加它的值,在Driver中只能读它的值。

如果要保持全局唯一状态的值的时候,使用累加器是一个很好的选择。

accumulator是集群共享的(是Executor级别共享的),记录了全局唯一状态。broadcast是线程全局共享的。两个Application不能共享累加器,但是不同的作业可以共享累加器。

展开阅读全文

没有更多推荐了,返回首页