Scala 通过对字符串的反射定义spark udf

Scala 通过对字符串的反射定义spark udf,scala,apache-spark,spark-dataframe,udf,scala-reflect,Scala,Apache Spark,Spark Dataframe,Udf,Scala Reflect,我试图在spark(2.0)中从包含scala函数定义的字符串定义udf。以下是代码段: val universe: scala.reflect.runtime.universe.type = scala.reflect.runtime.universe import universe._ import scala.reflect.runtime.currentMirror import scala.tools.reflect.ToolBox val toolbox = currentMirro

我试图在spark(2.0)中从包含scala函数定义的字符串定义udf。以下是代码段:

val universe: scala.reflect.runtime.universe.type = scala.reflect.runtime.universe
import universe._
import scala.reflect.runtime.currentMirror
import scala.tools.reflect.ToolBox
val toolbox = currentMirror.mkToolBox()
val f = udf(toolbox.eval(toolbox.parse("(s:String) => 5")).asInstanceOf[String => Int])
sc.parallelize(Seq("1","5")).toDF.select(f(col("value"))).show
这给了我一个错误:

  Caused by: java.lang.ClassCastException: cannot assign instance of scala.collection.immutable.List$SerializationProxy to field org.apache.spark.rdd.RDD.org$apache$spark$rdd$RDD$$dependencies_ of type scala.collection.Seq in instance of org.apache.spark.rdd.MapPartitionsRDD
   at java.io.ObjectStreamClass$FieldReflector.setObjFieldValues(ObjectStreamClass.java:2133)
   at java.io.ObjectStreamClass.setObjFieldValues(ObjectStreamClass.java:1305)
   at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2024)
   at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1942)
   at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1808)
   at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
   at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2018)
   at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1942)
   at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1808)
   at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
   at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
   at org.apache.spark.serializer.JavaDeserializationStream.readObject(JavaSerializer.scala:75)
   at org.apache.spark.serializer.JavaSerializerInstance.deserialize(JavaSerializer.scala:114)
   at org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:66)
   at org.apache.spark.scheduler.Task.run(Task.scala:85)
   at org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:274)
   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
   at java.lang.Thread.run(Thread.java:745)
但是,当我将udf定义为:

val f = udf((s:String) => 5)

它很好用。这里的问题是什么?最终目标是获取一个具有scala函数defn的字符串,并将其用作udf。

我也有同样的错误,它没有显示ClassNotFoundException,因为JavaDeserializationStream类正在捕获异常,根据您的环境,它会失败,因为它无法从RDD/DataSet中找到您试图执行的类,但不会显示ClassNotFoundError。为了解决这个问题,我必须生成一个包含项目中所有类(包括函数和依赖项)的jar,并将jar包含在spark环境中

这适用于独立群集

conf.setJars ( Array ("/fullpath/yourgeneratedjar.jar", "/fullpath/otherdependencies.jar") )
这是一个纱线簇

conf.set("spark.yarn.jars", "/fullpath/yourgeneratedjar.jar,/fullpath/otherdependencies.jar")

正如Giovanny所观察到的,问题在于类装入器不同(您可以通过对任何对象调用
.getClass.getClassLoader
来进一步研究这一点)。然后,当工作人员试图反序列化您的反射函数时,所有的麻烦都会烟消云散

这是一个不涉及任何类装入器黑客的解决方案。想法是将反射步骤移到工人身上。我们最终将不得不重做反射步骤,但每个工作人员只能重做一次。我认为这是非常理想的——即使您只在主节点上进行了一次反射,您也必须为每个工作人员做相当多的工作,以使他们能够识别函数

val f = udf {
  new Function1[String,Int] with Serializable {
    import scala.reflect.runtime.universe._
    import scala.reflect.runtime.currentMirror
    import scala.tools.reflect.ToolBox

    lazy val toolbox = currentMirror.mkToolBox()
    lazy val func = {
      println("reflected function") // triggered at every worker
      toolbox.eval(toolbox.parse("(s:String) => 5")).asInstanceOf[String => Int]
    }

    def apply(s: String): Int = func(s)
  }
}
然后,调用
sc.parallelize(Seq(“1”,“5”)).toDF.select(f(col(“value”))).show
就可以了

请随意注释
println
——这只是计算反射发生次数的一种简单方法。在
sparkshell--master'local'
中只有一次,但在
sparkshell--master'local[2]
中是两次

工作原理
UDF会立即求值,但它在到达工作节点之前不会被使用,因此惰性值
toolbox
func
只能在工作节点上求值。此外,由于他们懒惰,每个工人只能对他们进行一次评估。

似乎你遇到了这个问题-@vsminkov不是那样的。伙计,你不可能把火花和scala反射这两个更复杂更丑陋的怪物组合在一起。:)职业危害!:)尝试将org.scala lang:scala compiler:2.11.8和org.scala lang:scala reflect:2.11.8专门添加到--packages列表中;但错误仍然是一样的。无论如何,在运行作业之前,我会将所有应用程序依赖项作为maven坐标列表包含在内。@sourabh我想我发现了问题,当您使用反射生成函数时,该函数仅对本地类加载器可用,一旦尝试反序列化该函数,它将抛出ClassNotFoundException,因为该函数不可用于worker的类装入器,请在使用
val f=udf((s:String)=>5)
时检查生成的类,您将看到该函数的MyObject$$anonfunc$…类。我建议使用scala解释器生成.class文件,并生成一个包含该文件的jarclass@GiovannyGutierrez你能把最后一条评论移到答案上并加以扩展吗?我认为@alec使用延迟初始化来解决这个问题是一个更好的方法,并且没有必要进行进一步的更改,因为可以检测到类型,而不是指定字符串和Int类型,如果可能的话,它将处理任何类型的UDF