从spark-shell中的分布式操作调用`JValue.extract`时出错

时间:2015-06-09 23:02:17

标签: apache-spark json4s

我试图在Spark中使用json4s的case类提取功能, 即呼叫jvalue.extract[MyCaseClass]。如果我将JValue对象带入主服务器并在那里进行提取,它可以正常工作,但工作中的相同调用失败:

import org.json4s._
import org.json4s.jackson.JsonMethods._
import scala.util.{Try, Success, Failure}

val sqx = sqlContext

val data = sc.textFile(inpath).coalesce(2000)

case class PageView(
 client:  Option[String]
)

def extract(json: JValue) = {
  implicit def formats = org.json4s.DefaultFormats
  Try(json.extract[PageView]).toOption
}

val json = data.map(parse(_)).sample(false, 1e-6).cache()

// count initial inputs
val raw = json.count 


// count successful extractions locally -- same value as above
val loc = json.toLocalIterator.flatMap(extract).size

// distributed count -- always zero
val dist = json.flatMap(extract).count // always returns zero

// this throws  "org.json4s.package$MappingException: Parsed JSON values do not match with class constructor"
json.map(x => {implicit def formats = org.json4s.DefaultFormats; x.extract[PageView]}).count

隐式Formatsextract函数中本地定义,因为DefaultFormats不可序列化,并且在顶层定义它导致它被序列化以传输给工作者而不是在那里构造。我认为问题仍然与DefaultFormats的远程初始化有关,但我不确定它是什么。

当我直接调用extract方法时,在我的extract函数中输入,就像在上一个示例中一样,它不再抱怨序列化,而只是抛出JSON与预期不匹配的错误结构

如何在分发给工人时将提取工作化?

修改

@WesleyMiao已经重现了这个问题,发现它特定于spark-shell。他报告此代码可作为独立应用程序使用。

2 个答案:

答案 0 :(得分:4)

在spark-shell中运行代码时,我遇到了与您相同的异常。然而,当我将您的代码转换为真正的火花应用并将其提交给独立的火花群时,我毫无例外地得到了预期的结果。

下面是我在一个简单的火花应用中添加的代码。

val data = sc.parallelize(Seq("""{"client":"Michael"}""", """{"client":"Wesley"}"""))

val json = data.map(parse(_))

val dist = json.mapPartitions { jsons =>
  implicit val formats = org.json4s.DefaultFormats
  jsons.map(_.extract[PageView])
}

dist.collect() foreach println

当我使用spark-submit运行它时,我得到了以下结果。

PageView(Some(Michael))                                                                                                                                       
PageView(Some(Wesley))

我也确信它不是以“本地[*]”模式运行。

现在我怀疑我们在spark-shell中运行时遇到异常的原因与spark-shell中的case类PageView定义有关,以及spark-shell如何序列化/分发给执行者。

答案 1 :(得分:1)

根据建议here我会将对象创建移动到地图中。即我会将函数createPageViews作为内部函数提取,并将createPageViews传递给工作者。

更准确地说,我会使用mapPartitions而不是map - 所以它必须每个分区只调用一次createPageViews(它的内部函数定义部分) - 而不是每个记录都调用一次。