使用Spark cache来保障正确的一个例子

我们通常以为Spark cache就是一个用来优化spark程序性能的。本文举的例子会告诉你,cache的作用有时候可能比提高性能更重要。(原文标题:Using Spark’s cache for correctness, not just performance)

在学习Apache Spark的时候,我们被告知RDD是不可变的。但是,我这里要将到一个和这点冲突的一个小程序。这个Scala程序创建了一个小的RDD,然后在它上面执行了一些简单的转换,然后在相同的RDD上调用两次RDD.count()。然后通过一个断言(assert)来比较这两次条用的值。我们的第一感觉是:这个断言总是通过的。在这两次调用之间没有执行其他任何调用,况且,RDD是不可变的啊,我们理所当然应该得到相同的值,对不? 下面是程序:

/*
 * This file is licensed to You under the Eclipse Public License (EPL);
 *  http://www.opensource.org/licenses/eclipse-1.0.php
 * (C) Copyright IBM Corporation 2015
 */

import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
import scala.util.Random

object MutableRDD {
 def main(args: Array[String]) {
   val conf = new SparkConf().setAppName("Immutable RDD test")
   val sc = new SparkContext(conf)

   // start with a sequence of 10,000 zeros
   val zeros = Seq.fill(10000)(0)

   // create a RDD from the sequence, and replace all zeros with random values
   val randomRDD = sc.parallelize(zeros).map(x=>Random.nextInt())

   // filter out all non-positive values, roughly half the set
   val filteredRDD = randomRDD.filter(x=>x>0)

   // count the number of elements that remain, twice
   val count1 = filteredRDD.count()
   val count2 = filteredRDD.count()

   // Since filteredRDD is immutable, this should always pass, right? 
   assert(count1 == count2, "\nMismatch!  count1="+count1+" count2=+count2)

   System.out.println("Program completed successfully")
 }
}

我们使用了一个随机数生成器,按理说数字排列合适的话,这个程序的确是应该完全正确的。但实际是,尽管执行了很多次测试,得到的输出却总是这样的:

Exception in thread "main" java.lang.AssertionError: assertion failed: 
Mismatch!  count1=4984 count2=4973
    at scala.Predef$.assert(Predef.scala:179)
    at MutableRDD$.main(MutableRDD.scala:30)
    at MutableRDD.main(MutableRDD.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:95)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:55)
    at java.lang.reflect.Method.invoke(Method.java:507)
    at org.apache.spark.deploy.SparkSubmit$.org$apache$spark$deploy$SparkSubmit$$runMain(SparkSubmit.scala:664)
    at org.apache.spark.deploy.SparkSubmit$.doRunMain$1(SparkSubmit.scala:169)
    at org.apache.spark.deploy.SparkSubmit$.submit(SparkSubmit.scala:192)
    at org.apache.spark.deploy.SparkSubmit$.main(SparkSubmit.scala:111)
    at org.apache.spark.deploy.SparkSubmit.main(SparkSubmit.scala)

为什么会这样?出了什么问题问题呢? 其实,关键就在于一个Spark 转换(transformation)和一个Spark action的区别。我们都知道,transformation是惰性的,在执行一个action之前,Spark不会执行任何实际的处理。让我们再看下上面的程序,下面是步骤及他们的类型:

map() -> transformation
filter() -> transformation
count() -> action
count() -> action

在执行count()之前的所有步骤都是transformactions,因此第一次调用count()时会使得RDD开始计算,从初始的10000个零的集合。但是,这仍然不能完全解释为什么第二次的执行会产生一个不同的值。

我们知道,如果明确对RDD调用了cache()或者persist(),Spark就会将RDD缓存到内存中供重用。上面的程序并没有缓存RDD,因此,但我们第一次执行完filteredRDD.count()的时候,filteredRDD的内容就丢弃了!第二次调用的时候会从最初的10000个零的集合开始重新创建的一个新的RDD。尽管所以步骤一样,但使用了一个随机数字生成器,生成的随机数就和第一次不一样,所以最终count()输出的也会是不一样的值。这个和RDD是不可变的,或者filteredRDD是一个变量,或者在两次count()是否有其他的调用没有关系。

所以,想要让我们的程序总是通过断言的修改办法也就很简单了。只要将randomRDD.filter(x=>x>0)替换为 randomRDD.filter(x=>x>0).cache()就可以了.如果要缓存的RDD太大的话,即使调用cache(),Spark也可能会丢掉和重新计算RDD的部分。所以在大的程序中,最后是使用RDD.filter(x=>x>0).persist(StorageLevel.MEMORY_AND_DISK)。

Using Spark’s cache is not just a performance tool, which can be left out of simple programs. As seen above, it can also be important for reproducibility in any program which contains some level of sampling, random values, or other forms of variability. 
所以,请记住:Spark的cache不仅仅是一个性能工具。就像我们看到的,但程序中包括抽样,随机值,或者其他形式的可变性时,cache也是非常重要的!

原文:http://www.spark.tc/using-sparks-cache-for-correctness-not-just-performance/

猜你喜欢

转载自blog.csdn.net/u012501054/article/details/89100293