Spark之 cache 的坑

来源:互联网 发布:c语言default语句 编辑:程序博客网 时间:2024/06/06 01:39

转子:http://bit1129.iteye.com/blog/2182146




 

 调用reduceByKey对应的ShuffledRDD对应的cache

 

cache不起作用

 

Java代码  收藏代码
  1. package spark.examples  
  2.   
  3. import org.apache.spark.SparkConf  
  4. import org.apache.spark.SparkContext  
  5.   
  6. import org.apache.spark.SparkContext._  
  7.   
  8. object SparkWordCountCache {  
  9.   def main(args: Array[String]) {  
  10.     System.setProperty("hadoop.home.dir""E:\\devsoftware\\hadoop-2.5.2\\hadoop-2.5.2");  
  11.     val conf = new SparkConf()  
  12.     conf.setAppName("SparkWordCount")  
  13.     conf.setMaster("local[3]")  
  14.     conf.set("spark.shuffle.manager""hash"); ///hash是否有影响?  
  15.     val sc = new SparkContext(conf)  
  16.     val rdd1 = sc.textFile("file:///D:/word.in.3");  
  17.     val rdd2 = rdd1.flatMap(_.split(" "))  
  18.     val rdd3 = rdd2.map((_, 1))  
  19.     val rdd4 = rdd3.reduceByKey(_ + _, 3);  
  20.     rdd4.cache();  
  21.     rdd4.saveAsTextFile("file:///D:/wordout" + System.currentTimeMillis());  
  22.     val result = rdd4.collect; ///没有触发ShuffleMapTask执行,但是依然需要从ShuffleMapTask产生的结果拉取数据  
  23.     result.foreach(println(_));  
  24.     sc.stop  
  25.   }  
  26. }  

 

以上代码调用rdd3.cache(),而rdd3是一个ShuffleMapRDD,也就是说,保存的是Stage2里面的RDD结果。此时调用cache.collect时,产生的Task都是ResultTask,也就是说,由于cache作用,最后一个Job并没有从前面从头计算?

感觉不对,即使不用cache,也应该不会从头计算吧

 

经验证,感觉是对的,将上面的代码做如下修改,结果一样,最后也不会调用ShuffleMapTask,但是在执行ResultTask时,还是会从MapTask的输出中拉取数据,所以并没有对Shuffle读过程进行简化。

 

Java代码  收藏代码
  1. rdd3.saveAsTextFile("file:///D:/wordout" + System.currentTimeMillis());  
  2. val result = rdd3.collect;  
  3. result.foreach(println(_));  

 

上来就踩了个cache的坑!Spark是不支持ShuffleMapRDD的cache的,虽然上面不需要ShuffleMapTask,但是ResultTask运行时,依然需要从MapTask的结果中拉取数据

 

 

调用groupByKey对应的ShuffledRDD对应的cache

 

结果rdd.cache起作用了

 

Java代码  收藏代码
  1. package spark.examples  
  2.   
  3. import org.apache.spark.SparkContext  
  4. import org.apache.spark.SparkContext._  
  5. import org.apache.spark.SparkConf  
  6.   
  7. object SparkGroupByExample {  
  8.   
  9.   def main(args: Array[String]) {  
  10.     val conf = new SparkConf().setAppName("GroupByKey").setMaster("local")  
  11.     val sc = new SparkContext(conf)  
  12.     sc.setCheckpointDir("/tmp/checkpoint/" + System.currentTimeMillis())  
  13.   
  14.     val data = Array[(Int, Char)]((1'a'), (2'b'),  
  15.       (3'c'), (4'd'),  
  16.       (5'e'), (3'f'),  
  17.       (2'g'), (1'h')  
  18.     )  
  19.     val pairs = sc.parallelize(data)  
  20.     val rdd = pairs.groupByKey(2)  
  21.     rdd.cache  
  22.     rdd.count;  
  23.     rdd.collect.foreach(println(_));  
  24.   }  
  25. }  

 

 

 

调用textFile对应的MappedRDD对应的cache操作

 

基本流程:假如在一个程序中有两个Job。第一个Job运行时,,对于调用了cache的RDD首先计算它的数据,然后写入cache。第二个job在运行时,会直接从cache中读取。

这对于迭代计算的Job,会非常适合,将上个任务的结果缓存,供第二个任务使用,然后依次类推

 

 

Java代码  收藏代码
  1. package spark.examples  
  2.   
  3. import org.apache.spark.SparkConf  
  4. import org.apache.spark.SparkContext  
  5.   
  6. import org.apache.spark.SparkContext._  
  7.   
  8. object SparkWordCountCache {  
  9.   def main(args: Array[String]) {  
  10.     System.setProperty("hadoop.home.dir""E:\\devsoftware\\hadoop-2.5.2\\hadoop-2.5.2");  
  11.     val conf = new SparkConf()  
  12.     conf.setAppName("SparkWordCount")  
  13.     conf.setMaster("local")  
  14.     //Hash based Shuffle;  
  15.     conf.set("spark.shuffle.manager""hash");  
  16.     val sc = new SparkContext(conf)  
  17.     val rdd1 = sc.textFile("file:///D:/word.in.3");  
  18.     rdd1.cache() ///数据读取后即做cache,第一个job运行后,就会缓存  
  19.     val rdd2 = rdd1.flatMap(_.split(" "))  
  20.     val rdd3 = rdd2.map((_, 1))  
  21.     val result = rdd3.collect; ///打印rdd3的内容  
  22.     result.foreach(println(_));  
  23.     val rdd4 = rdd3.reduceByKey(_ + _); ///对rdd3做reduceByKey操作  
  24.     rdd4.saveAsTextFile("file:///D:/wordout" + System.currentTimeMillis());  
  25.     sc.stop  
  26.   }  
  27. }  

 

 

源代码基本流程:

 

  • 调用RDD的iterator方法,计算RDD的数据集合(得到的是一个可迭代的集合)
  • 在RDD的iterator方法中,检查RDD的storage level,如果设置了storage level,那么调用SparkEnv.get.cacheManager.getOrCompute(this, split, context, storageLevel)
  • 在CacheManager的getOrCompute方法中,

           a.首先判断是否存在于cache中,如果存在则直接返回,

           b.如果不存在,则调用  val computedValues = rdd.computeOrReadCheckpoint(partition, context)进行计算。

           c.计算结束后,调用CacheManager自身的putInBlockManager将计算得到的数据缓存

           d. 数据放入BlockManager后,还需要更新这个RDD和BlockManager之间的对应关系,以便下次再计算这个RDD时,检查RDD数据是否已经缓存

 

主要源代码

 

 1. getOrCompute方法

 

Java代码  收藏代码
  1. /** Gets or computes an RDD partition. Used by RDD.iterator() when an RDD is cached. */  
  2.   def getOrCompute[T](  
  3.       rdd: RDD[T],  
  4.       partition: Partition,  
  5.       context: TaskContext,  
  6.       storageLevel: StorageLevel): Iterator[T] = {  
  7.   
  8.     val key = RDDBlockId(rdd.id, partition.index) //RDD的id和partition的index构造RDDBlockId,一个RDD可以有多个partition  
  9.     logDebug(s"Looking for partition $key")  
  10.     blockManager.get(key) match { ///从blockManger中根据key查找,key最后会存入BlockManager么吗?BlockManager管理Spark的块信息  
  11.       case Some(blockResult) =>  
  12.         // Partition is already materialized, so just return its values  
  13.         context.taskMetrics.inputMetrics = Some(blockResult.inputMetrics)  
  14.         new InterruptibleIterator(context, blockResult.data.asInstanceOf[Iterator[T]])  
  15.   
  16.       case None =>  
  17.         // Acquire a lock for loading this partition  
  18.         // If another thread already holds the lock, wait for it to finish return its results  
  19.         val storedValues = acquireLockForPartition[T](key) ///根据Key获取缓存的数据,acquireLockForPartition名字起得不好  
  20.         if (storedValues.isDefined) { ///找到数据  
  21.           return new InterruptibleIterator[T](context, storedValues.get)  
  22.         }  
  23.   
  24.         // Otherwise, we have to load the partition ourselves  
  25.         ///为找到缓存的数据,表明是job第一次运行  
  26.         try {   
  27.           logInfo(s"Partition $key not found, computing it")  
  28.           val computedValues = rdd.computeOrReadCheckpoint(partition, context) ///计算RDD数据  
  29.   
  30.           // If the task is running locally, do not persist the result  
  31.           if (context.isRunningLocally) { ///如果数据在本地,就不需要缓存了?  
  32.             return computedValues  
  33.           }  
  34.   
  35.           // Otherwise, cache the values and keep track of any updates in block statuses  
  36.           ///缓存数据  
  37.           val updatedBlocks = new ArrayBuffer[(BlockId, BlockStatus)]  
  38.             
  39.           ///将数据存入BlockManager,注意四个参数  
  40.           val cachedValues = putInBlockManager(key, computedValues, storageLevel, updatedBlocks)  
  41.             
  42.           ///这是什么意思?任务的metrics,任务的  
  43.           val metrics = context.taskMetrics  
  44.           val lastUpdatedBlocks = metrics.updatedBlocks.getOrElse(Seq[(BlockId, BlockStatus)]())  
  45.           metrics.updatedBlocks = Some(lastUpdatedBlocks ++ updatedBlocks.toSeq)  
  46.           new InterruptibleIterator(context, cachedValues)  
  47.   
  48.         } finally {  
  49.           loading.synchronized {  
  50.             loading.remove(key)  
  51.             loading.notifyAll()  
  52.           }  
  53.         }  
  54.     }  
  55.   }  

 

2. putInBlockManager方法

Java代码  收藏代码
  1. /** 
  2.    * Cache the values of a partition, keeping track of any updates in the storage statuses of 
  3.    * other blocks along the way. 
  4.    * 
  5.    * The effective storage level refers to the level that actually specifies BlockManager put 
  6.    * behavior, not the level originally specified by the user. This is mainly for forcing a 
  7.    * MEMORY_AND_DISK partition to disk if there is not enough room to unroll the partition, 
  8.    * while preserving the the original semantics of the RDD as specified by the application. 
  9.    */  
  10.   private def putInBlockManager[T](  
  11.       key: BlockId,  
  12.       values: Iterator[T],  
  13.       level: StorageLevel,  
  14.       updatedBlocks: ArrayBuffer[(BlockId, BlockStatus)],  
  15.       effectiveStorageLevel: Option[StorageLevel] = None): Iterator[T] = {  
  16.   
  17.     val putLevel = effectiveStorageLevel.getOrElse(level)  
  18.     if (!putLevel.useMemory) {  
  19.       /* 
  20.        * This RDD is not to be cached in memory, so we can just pass the computed values as an 
  21.        * iterator directly to the BlockManager rather than first fully unrolling it in memory. 
  22.        */  
  23.       updatedBlocks ++=  
  24.         blockManager.putIterator(key, values, level, tellMaster = true, effectiveStorageLevel)  
  25.       blockManager.get(key) match {  
  26.         case Some(v) => v.data.asInstanceOf[Iterator[T]]  
  27.         case None =>  
  28.           logInfo(s"Failure to store $key")  
  29.           throw new BlockException(key, s"Block manager failed to return cached value for $key!")  
  30.       }  
  31.     } else {  
  32.       /* 
  33.        * This RDD is to be cached in memory. In this case we cannot pass the computed values 
  34.        * to the BlockManager as an iterator and expect to read it back later. This is because 
  35.        * we may end up dropping a partition from memory store before getting it back. 
  36.        * 
  37.        * In addition, we must be careful to not unroll the entire partition in memory at once. 
  38.        * Otherwise, we may cause an OOM exception if the JVM does not have enough space for this 
  39.        * single partition. Instead, we unroll the values cautiously, potentially aborting and 
  40.        * dropping the partition to disk if applicable. 
  41.        */  
  42.       blockManager.memoryStore.unrollSafely(key, values, updatedBlocks) match {  
  43.         case Left(arr) =>  
  44.           // We have successfully unrolled the entire partition, so cache it in memory  
  45.           updatedBlocks ++=  
  46.             blockManager.putArray(key, arr, level, tellMaster = true, effectiveStorageLevel)  
  47.           arr.iterator.asInstanceOf[Iterator[T]]  
  48.         case Right(it) =>  
  49.           // There is not enough space to cache this partition in memory  
  50.           val returnValues = it.asInstanceOf[Iterator[T]]  
  51.           if (putLevel.useDisk) {  
  52.             logWarning(s"Persisting partition $key to disk instead.")  
  53.             val diskOnlyLevel = StorageLevel(useDisk = true, useMemory = false,  
  54.               useOffHeap = false, deserialized = false, putLevel.replication)  
  55.             putInBlockManager[T](key, returnValues, level, updatedBlocks, Some(diskOnlyLevel))  
  56.           } else {  
  57.             returnValues  
  58.           }  
  59.       }  
  60.     }  

原创粉丝点击