【问题标题】:Spark closure argument bindingSpark 闭包参数绑定
【发布时间】:2015-10-17 18:43:31
【问题描述】:

我正在使用 Scala 中的 Apache Spark。

我在尝试使用来自第二个 RDD 的数据操作一个 RDD 时遇到问题。我试图将第二个 RDD 作为参数传递给正在“映射”到第一个 RDD 的函数,但似乎在该函数上创建的闭包绑定了该值的未初始​​化版本。

以下是一段更简单的代码,显示了我所看到的问题类型。 (我第一次遇到麻烦的真实例子更大,更难理解)。

我不太了解 Spark 闭包的参数绑定规则。

我真正在寻找的是一种基本方法或模式,用于如何使用另一个 RDD 的内容(之前在其他地方构建)来操作一个 RDD。

在以下代码中,调用 Test1.process(sc) 将失败并在 findSquare 中访问空指针(因为闭包中绑定的第二个参数未初始化)

object Test1 {

  def process(sc: SparkContext) {
    val squaresMap = (1 to 10).map(n => (n, n * n))
    val squaresRDD = sc.parallelize(squaresMap)

    val primes = sc.parallelize(List(2, 3, 5, 7))

    for (p <- primes) {
      println("%d: %d".format(p, findSquare(p, squaresRDD)))
    }
  }

  def findSquare(n: Int, squaresRDD: RDD[(Int, Int)]): Int = {
    squaresRDD.filter(kv => kv._1 == n).first._1
  }
}

【问题讨论】:

    标签: scala apache-spark


    【解决方案1】:

    您遇到的问题与闭包或 RDD 无关,与流行的看法相反,are serializable

    这只是违反了一项基本的 Spark 规则,该规则规定您不能从另一个操作或转换触发一个操作或转换*,并且该问题的不同变体已被多次询问。

    要了解为什么会这样,您必须考虑架构:

    • SparkContext 由驱动管理
    • 在转换中发生的所有事情都在工人身上执行。每个工作人员只能访问自己的部分数据,不能与其他工作人员通信**。

    如果您想使用多个 RDD 的内容,您必须使用组合 RDD 的转换之一,例如 joincartesianzipunion

    在这里你很可能(我不确定你为什么传递元组并且只使用这个元组的第一个元素)想要使用广播变量:

    val squaresMapBD = sc.broadcast(squaresMap)
    
    def findSquare(n: Int): Seq[(Int, Int)] = {
      squaresMapBD.value
        .filter{case (k, v) => k == n}
        .map{case (k, v) => (n, k)}
        .take(1)
    }
    
    primes.flatMap(findSquare)
    

    或笛卡尔:

    primes
      .cartesian(squaresRDD)
      .filter{case (n, (k, _)) => n == k}.map{case (n, (k, _)) => (n, k)}
    

    primes 转换为虚拟对(Int, null)join 会更有效:

    primes.map((_, null)).join(squaresRDD).map(...)
    

    但根据您的 cmets,我假设您对存在自然连接条件的场景感兴趣。

    根据上下文,您还可以考虑使用数据库或文件来存储常用数据。

    附带说明 RDD 不可迭代,因此您不能简单地使用 for 循环。为了能够做这样的事情,你必须先collect 或转换toLocalIterator。你也可以使用foreach方法。


    * 准确地说,您无法访问SparkContext

    ** Torrent 广播和树聚合涉及执行器之间的通信,因此在技术上是可行的。

    【讨论】:

    • 好的,谢谢。我对 Spark 还很陌生,还没有遇到你提到的规则(或者如果我在某处看到它,它还没有打到家)。在处理另一个数据集时,我很自然会想到“咨询”一个数据集,但我显然需要调整我的想法。
    • 感谢您提供的信息。我只有大约 2 周的时间来学习 Spark,所以我仍在努力将基本概念内化。在我想将 arg 传递给映射函数的某些情况下,广播变量可能是合适的。
    • 根据数据的大小,您应该按以下顺序传递它:argument
    • 这个答案是 Spark 内部定义的一个很好的变体。
    • 非常感谢您花时间扩充您对这个问题的回答——我从中学到了很多东西。我想我现在对如何“正确思考”一系列 Spark 编码问题有了更清晰的认识。现在我只需要删除一些代码并重写它。 :-)
    【解决方案2】:

    RDD 不可序列化,因此您不能在 rdd 转换中使用 rdd。 然后我从未见过用 for 语句枚举 rdd,通常我使用作为 rdd api 一部分的 foreach 语句。

    为了合并来自两个 rdd 的数据,您可以利用 join、union 或广播(以防您的 rdd 很小)

    【讨论】:

    • 我之前看到过 cmets 大意是 RDD 是可序列化的。我刚刚发现以下帖子中的答案非常中肯。我仍然对如何处理将一个数据集与另一个数据集进行操作的一般情况感到困惑(它们是根本不同类型的数据,不会自然“加入”等)stackoverflow.com/questions/29567247/serializing-rdd
    • 我碰巧使用的是旧版本的 Spark (1.2),正如其他帖子的答案中提到的,当您尝试执行我所做的操作时,由于未设置 SparkContext 字段,它会生成空指针异常RDD反序列化后。
    • RDD 是可序列化的,参见 docssource
    • 是的,这是真的,我的错。关键是另一个转换内部的转换。感谢您的深入解释
    猜你喜欢
    • 2015-01-01
    • 2011-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-18
    • 2012-08-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多