Spark / Scala迭代器无法分配在foreach循环外定义的变量

时间:2017-07-31 19:39:59

标签: scala apache-spark iterator

请注意:虽然这个问题提到了Spark(2.1)我认为这实际上是一个Scala(2.11)问题,任何精通Scala的开发者都能够回答它!

我有以下代码创建Spark Dataset(基本上是一个2D表)并逐行迭代它。如果特定行的username列的值为“fizzbuzz”,那么我想设置一个在迭代器之外定义的变量,并在行迭代完成后使用该变量:

val myDataset = sqlContext
     .read
     .format("org.apache.spark.sql.cassandra")
     .options(Map("table" -> "mytable", "keyspace" -> "mykeyspace"))
     .load()

var foobar : String
myDataset.collect().foreach(rec =>
  if(rec.getAs("username") == "fizzbuzz") {
    foobar = rec.getAs("foobarval")
  }
)

if(foobar == null) {
  throw new Exception("The fizzbuzz user was not found.")
}

当我运行时,我得到以下异常:

error: class $iw needs to be abstract, since:
it has 2 unimplemented members.
/** As seen from class $iw, the missing signatures are as follows.
 *  For convenience, these are usable as stub implementations.
 */
  def foobar=(x$1: String): Unit = ???

class $iw extends Serializable {
      ^

我得到这个的任何特殊原因?

3 个答案:

答案 0 :(得分:3)

在方法或非抽象类中,您必须为每个变量定义一个值;在这里,您保留null未定义。如果您将其定义为具有var foobar: String = null 的初始值:

,那么事情将按预期工作
foobar

但是:请注意您的代码都是非惯用代码(不遵循Scala和Spark的最佳做法)并且可能存在风险/缓慢:

  • 您应该避免使用collect这样的可变值 - 不可变代码更易于推理,并且真正让您利用Scala的强大功能
  • 您应该避免在DataFrame上调用collect,除非您确定它非常小,因为OutOfMemoryError会收集来自工作节点的所有数据(其中有许多(可能)进入单个驱动程序节点,这将很慢并可能导致null
  • 不鼓励使用NullPointerException(因为它通常会导致意外的DataFrame.filter

此代码的更惯用的版本将使用Option来过滤相关记录,并可能import spark.implicits._ val foobar: Option[String] = myDataset .filter($"username" === "fizzbuzz") // filter only relevant records .take(1) // get first 1 record (if it exists) as an Array[Row] .headOption // get the first item in the array, or None .map(r => r.getAs[String]("foobarval")) // get the value of the column "foobarval", or None if (foobar.isEmpty) { throw new Exception("The fizzbuzz user was not found.") } 来正确表示潜在的空值,例如:

$('label[for=Number_x0020__x0028_Or_x0020_Ran_e6c9be8f-bccc-474a-8b0a-e9a136acbca7_$RadioButtonChoiceFieldFillInRadio]')
  .html('new label');

答案 1 :(得分:2)

应该初始化

foobar变量:

var foobar: String = null

此外,这看起来并不正确:

foobar = rec.getAs("foobarval")

应该是:

foobar = rec.getAs[String]("foobarval")

总的来说,这不是一条路。它完全不受Spark执行模型的影响。我会过滤并改为:

myDataset.filter($"username" === "fizzbuzz").select("foobarval").take(1)

答案 2 :(得分:0)

您可能应该使用过滤器并选择数据框:

import spark.sqlContext.implicits._

val data = spark.sparkContext.parallelize(List(
  """{ "username": "none", "foobarval":"none" }""",
  """{ "username": "fizzbuzz", "foobarval":"expectedval" }"""))

val df = spark.read.json(data)
val foobar = df.filter($"username" === "fizzbuzz").select($"foobarval").collect.head.getString(0)