Databrick Azure广播变量不可序列化

时间:2018-04-26 10:21:58

标签: scala apache-spark databricks

因此,我尝试使用Azure Databricks创建一个非常简单的spark笔记本,并希望使用简单的RDD地图调用。

这只是为了搞乱,所以这个例子有点做作,但我无法在RDD地图调用中获得值,除非它是一个静态常量值

我尝试过使用广播变量

这是一个使用int的简单示例,我在广播然后尝试在RDD地图中使用

val sparkContext = spark.sparkContext
val sqlContext = spark.sqlContext

import sqlContext.implicits._
val multiplier = 3
val multiplierBroadcast = sparkContext.broadcast(multiplier)
val data = Array(1, 2, 3, 4, 5)
val dataRdd = sparkContext.parallelize(data)
val mappedRdd = dataRdd.map(x => multiplierBroadcast.value)
val df = mappedRdd.toDF
df.show()

这是另一个例子,我使用简单的可序列化单例对象与int字段,我广播然后尝试在RDD映射中使用

val sparkContext = spark.sparkContext
val sqlContext = spark.sqlContext

import sqlContext.implicits._
val multiplier = 3
object Foo extends Serializable { val theMultiplier: Int = multiplier}
val fooBroadcast = sparkContext.broadcast(Foo)
val data = Array(1, 2, 3, 4, 5)
val dataRdd = sparkContext.parallelize(data)
val mappedRdd = dataRdd.map(x => fooBroadcast.value.theMultiplier)
val df = mappedRdd.toDF
df.show()

最后是一个List[int],其中包含我播放的单个元素,然后尝试在RDD地图中使用

val sparkContext = spark.sparkContext
val sqlContext = spark.sqlContext

import sqlContext.implicits._
val multiplier = 3
val listBroadcast = sparkContext.broadcast(List(multiplier))
val data = Array(1, 2, 3, 4, 5)
val dataRdd = sparkContext.parallelize(data)
val mappedRdd = dataRdd.map(x => listBroadcast.value.head)
val df = mappedRdd.toDF
df.show()

但是上述所有示例都会因此错误而失败。您可以看到哪一个指向RDD映射值不可序列化的问题。我看不出这个问题,并且int值应该可以使用我认为的所有上述示例进行序列化

org.apache.spark.SparkException: Task not serializable
    at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:345)
    at org.apache.spark.util.ClosureCleaner$.org$apache$spark$util$ClosureCleaner$$clean(ClosureCleaner.scala:335)
    at org.apache.spark.util.ClosureCleaner$.clean(ClosureCleaner.scala:159)
    at org.apache.spark.SparkContext.clean(SparkContext.scala:2375)
    at org.apache.spark.rdd.RDD$$anonfun$map$1.apply(RDD.scala:379)
    at org.apache.spark.rdd.RDD$$anonfun$map$1.apply(RDD.scala:378)
    at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:151)
    at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:112)
    at org.apache.spark.rdd.RDD.withScope(RDD.scala:371)
    at org.apache.spark.rdd.RDD.map(RDD.scala:378)

但是,如果我使RDD映射中的值成为像这样的常规int值

val sparkContext = spark.sparkContext
val sqlContext = spark.sqlContext

import sqlContext.implicits._
val data = Array(1, 2, 3, 4, 5)
val dataRdd = sparkContext.parallelize(data)
val mappedRdd = dataRdd.map(x => 6)
val df = mappedRdd.toDF
df.show()

一切正常,我看到我的简单DataFrame按预期显示

enter image description here

任何想法?

2 个答案:

答案 0 :(得分:0)

从您的代码中,我会假设您使用的是Spark 2+。也许,没有必要下拉到RDD级别,而是使用DataFrames。

下面的代码显示了如何连接两个DataFrame并明确广播第一个。

import sparkSession.implicits._
import org.apache.spark.sql.functions._

val data = Seq(1, 2, 3, 4, 5)
val dataDF = data.toDF("id")

val largeDataDF = Seq((0, "Apple"), (1, "Pear"), (2, "Banana")).toDF("id", "value")
val df = largeDataDF.join(broadcast(dataDF), Seq("id"))

df.show()

通常,小型DataFrame是广播的理想候选者,作为优化,将它们发送给所有执行者。 spark.sql.autoBroadcastJoinThreshold 是一种限制适合广播的DataFrame大小的配置。其他详细信息可在Spark official documentation

上找到

另请注意,使用DataFrames,您可以访问方便的 explain 方法。有了它,您可以看到物理计划,它可以用于调试。

在我们的示例中运行explain()将确认Spark正在进行 BroadcastHashJoin 优化。

df.explain()

== Physical Plan ==
*Project [id#11, value#12]
+- *BroadcastHashJoin [id#11], [id#3], Inner, BuildRight
:- LocalTableScan [id#11, value#12]
+- BroadcastExchange HashedRelationBroadcastMode(List(cast(input[0, int, false] as bigint)))
+- LocalTableScan [id#3]  

如果您需要有关DataFrame的其他帮助,请在http://allaboutscala.com/big-data/spark/

提供大量示例列表

答案 1 :(得分:0)

所以答案是你不应该在val中捕获Spark内容然后将其用于广播。所以这是工作代码

import sqlContext.implicits._
val multiplier = 3
val multiplierBroadcast = spark.sparkContext.broadcast(multiplier)
val data = Array(1, 2, 3, 4, 5)
val dataRdd = sparkContext.parallelize(data)
val mappedRdd = dataRdd.map(x => multiplierBroadcast.value)
val df = mappedRdd.toDF
df.show()

感谢@nadim Bahadoor的回答