如何将RDD [GenericRecord]转换为scala中的数据帧?

时间:2017-11-13 12:46:35

标签: scala apache-spark spark-dataframe avro

我从Avaf(序列化器和反序列化器)获取kafka主题的推文。 然后我创建了一个Spark消费者,它在RDD [GenericRecord]的Dstream中提取推文。 现在我想将每个rdd转换为数据帧,以通过SQL分析这些推文。 任何将RDD [GenericRecord]转换为数据帧的解决方案吗?

4 个答案:

答案 0 :(得分:7)

我花了一些时间尝试完成这项工作(特别是如何正确地反序列化数据,但看起来你已经覆盖了这个)...更新

  //Define function to convert from GenericRecord to Row
  def genericRecordToRow(record: GenericRecord, sqlType : SchemaConverters.SchemaType): Row = {
    val objectArray = new Array[Any](record.asInstanceOf[GenericRecord].getSchema.getFields.size)
    import scala.collection.JavaConversions._
    for (field <- record.getSchema.getFields) {
      objectArray(field.pos) = record.get(field.pos)
    }

    new GenericRowWithSchema(objectArray, sqlType.dataType.asInstanceOf[StructType])
  }

//Inside your stream foreachRDD
val yourGenericRecordRDD = ... 
val schema = new Schema.Parser().parse(...) // your schema
val sqlType = SchemaConverters.toSqlType(new Schema.Parser().parse(strSchema))

var rowRDD = yourGeneircRecordRDD.map(record => genericRecordToRow(record, sqlType))
val df = sqlContext.createDataFrame(rowRDD , sqlType.dataType.asInstanceOf[StructType])

如您所见,我正在使用SchemaConverter从您用于反序列化的模式中获取数据帧结构(这可能会对模式注册表更加痛苦)。为此,您需要以下依赖

    <dependency>
        <groupId>com.databricks</groupId>
        <artifactId>spark-avro_2.11</artifactId>
        <version>3.2.0</version>
    </dependency>

您需要根据自己的需要更改火花版本。

更新:上面的代码仅适用于平面 avro架构。

对于嵌套结构,我使用了不同的东西。您可以复制类SchemaConverters,它必须在com.databricks.spark.avro内(它使用databricks包中的一些受保护的类),或者您可以尝试使用spark-bigquery依赖项。默认情况下,该类不可访问,因此您需要在包com.databricks.spark.avro中创建一个类来访问工厂方法。

package com.databricks.spark.avro

import com.databricks.spark.avro.SchemaConverters.createConverterToSQL
import org.apache.avro.Schema
import org.apache.spark.sql.types.StructType

class SchemaConverterUtils {

  def converterSql(schema : Schema, sqlType : StructType) = {
    createConverterToSQL(schema, sqlType)
  }

}

之后你应该能够转换像

这样的数据
val schema = .. // your schema
val sqlType = SchemaConverters.toSqlType(schema).dataType.asInstanceOf[StructType]
....
//inside foreach RDD
var genericRecordRDD = deserializeAvroData(rdd)
/// 
var converter = SchemaConverterUtils.converterSql(schema, sqlType)
... 
val rowRdd = genericRecordRDD.flatMap(record => {
        Try(converter(record).asInstanceOf[Row]).toOption
      })
//To DataFrame
 val df = sqlContext.createDataFrame(rowRdd, sqlType)

答案 1 :(得分:0)

即使这样的事情对你有帮助,

val stream = ...

val dfStream = stream.transform(rdd:RDD[GenericRecord]=>{
     val df = rdd.map(_.toSeq)
              .map(seq=> Row.fromSeq(seq))
              .toDF(col1,col2, ....)

     df
})

我想建议你另一种方法。使用Spark 2.x,您可以跳过创建DStreams的整个过程。相反,你可以用结构化流媒体做这样的事情,

val df = ss.readStream
  .format("com.databricks.spark.avro")
  .load("/path/to/files")

这将为您提供一个可以直接查询的数据框。这里,ss是spark会话的实例。 /path/to/files是从kafka转储所有avro文件的地方。

PS:您可能需要导入spark-avro

libraryDependencies += "com.databricks" %% "spark-avro" % "4.0.0"

希望这有帮助。干杯

答案 2 :(得分:0)

https://stackoverflow.com/a/48828303/5957143https://stackoverflow.com/a/47267060/5957143的组合对我有用。

我使用以下内容创建MySchemaConversions

c:\Users\bharat.c.rupare.> call c:\Users\bharat.c.ruparel\AppData\Local\Continuum\anaconda3\Scripts\activate.bat

然后我用

package com.databricks.spark.avro

import org.apache.avro.Schema
import org.apache.avro.generic.GenericRecord
import org.apache.spark.sql.Row
import org.apache.spark.sql.types.DataType

object MySchemaConversions {
  def createConverterToSQL(avroSchema: Schema, sparkSchema: DataType): (GenericRecord) => Row =
    SchemaConverters.createConverterToSQL(avroSchema, sparkSchema).asInstanceOf[(GenericRecord) => Row]
}

// unionedResultRdd是unionRDD [GenericRecord]

val myAvroType = SchemaConverters.toSqlType(schema).dataType
val myAvroRecordConverter = MySchemaConversions.createConverterToSQL(schema, myAvroType)

在对象MyObject中使用myConverter的优点是您不会遇到序列化问题(java.io.NotSerializableException)。

var rowRDD = unionedResultRdd.map(record => MyObject.myConverter(record, myAvroRecordConverter))
 val df = sparkSession.createDataFrame(rowRDD , myAvroType.asInstanceOf[StructType])

答案 3 :(得分:-3)

您可以使用SQLContext对象中提供的createDataFrame(rowRDD:RDD [Row],schema:StructType)。转换旧DataFrame的RDD的示例:

import sqlContext.implicits.
val rdd = oldDF.rdd
val newDF = oldDF.sqlContext.createDataFrame(rdd, oldDF.schema)

请注意,无需显式设置任何架构列。我们重用旧的DF架构,它是StructType类,可以很容易地扩展。但是,这种方法有时是不可能的,在某些情况下效率可能低于第一种方法。