我在Spark中,我有一个来自Avro文件的RDD。我现在想对该RDD进行一些转换并将其保存为Avro文件:
val job = new Job(new Configuration())
AvroJob.setOutputKeySchema(job, getOutputSchema(inputSchema))
rdd.map(elem => (new SparkAvroKey(doTransformation(elem._1)), elem._2))
.saveAsNewAPIHadoopFile(outputPath,
classOf[AvroKey[GenericRecord]],
classOf[org.apache.hadoop.io.NullWritable],
classOf[AvroKeyOutputFormat[GenericRecord]],
job.getConfiguration)
运行此Spark时,抱怨Schema $ recordSchema不可序列化。
如果我取消注释.map调用(并且只有rdd.saveAsNewAPIHadoopFile),则调用成功。
我在这里做错了什么?
有什么想法吗?
答案 0 :(得分:5)
此处的问题与作业中使用的avro.Schema类的不可序列化有关。当您尝试从map函数内的代码引用架构对象时抛出异常。
例如,如果您尝试执行以下操作,您将获得"任务不可序列化" 例外:
val schema = new Schema.Parser().parse(new File(jsonSchema))
...
rdd.map(t => {
// reference to the schema object declared outside
val record = new GenericData.Record(schema)
})
只需在功能块中创建一个新的架构实例,就可以使一切工作正常:
val schema = new Schema.Parser().parse(new File(jsonSchema))
// The schema above should not be used in closures, it's for other purposes
...
rdd.map(t => {
// create a new Schema object
val innserSchema = new Schema.Parser().parse(new File(jsonSchema))
val record = new GenericData.Record(innserSchema)
...
})
由于您不希望为您处理的每条记录解析avro架构,因此更好的解决方案是在分区级别解析架构。以下也有效:
val schema = new Schema.Parser().parse(new File(jsonSchema))
// The schema above should not be used in closures, it's for other purposes
...
rdd.mapPartitions(tuples => {
// create a new Schema object
val innserSchema = new Schema.Parser().parse(new File(jsonSchema))
tuples.map(t => {
val record = new GenericData.Record(innserSchema)
...
// this closure will be bundled together with the outer one
// (no serialization issues)
})
})
只要您提供对jsonSchema文件的可移植引用,上面的代码就可以工作,因为map函数将由多个远程执行程序执行。它可以是对HDFS中文件的引用,也可以与JAR中的应用程序一起打包(在后一种情况下,您将使用类加载器函数来获取其内容)。
对于那些试图将Avro与Spark一起使用的人,请注意仍然存在一些未解决的编译问题,您必须在Maven POM上使用以下导入:
<dependency>
<groupId>org.apache.avro</groupId>
<artifactId>avro-mapred</artifactId>
<version>1.7.7</version>
<classifier>hadoop2</classifier>
<dependency>
请注意"hadoop2"
分类器。您可以在https://issues.apache.org/jira/browse/SPARK-3039跟踪问题。
答案 1 :(得分:2)
Spark使用的默认序列化程序是Java序列化。因此,对于所有Java类型,它将尝试使用Java序列化进行序列化。 AvroKey不可序列化,因此您会收到错误。
您可以在自定义序列化(如Avro)中使用KryoSerializer或插件。您可以在此处阅读有关序列化的更多信息http://spark-project.org/docs/latest/tuning.html
您还可以通过可外部化的东西包装您的对象。查看一下在这里包装AvroFlumeEvent的SparkFlumeEvent:https://github.com/apache/spark/blob/master/external/flume/src/main/scala/org/apache/spark/streaming/flume/FlumeInputDStream.scala
答案 2 :(得分:0)
使用数据帧,使用数据库库创建avro非常简单。
dataframe.write.format( “com.databricks.spark.avro”)。阿夫罗($ hdfs_path)
在您的情况下,输入是avro,因此它将具有与之关联的架构,因此您可以直接将avro读入数据帧,并且在转换之后,您可以使用上面的代码写入avro。
将avro读入数据帧:
Spark 1.6
val dataframe = sqlContext.read.avro($ hdfs_path)OR val dataframe = sqlContext.read.format( “com.databricks.spark.avro”)。负载($ hdfs_path)
Spark 2.1
val dataframe = sparkSession.read.avro($ hdfs_path)OR val dataframe = sparkSession.read.format( “com.databricks.spark.avro”)。负载($ hdfs_path)