繁体   English   中英

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

[英]How to convert RDD[GenericRecord] to dataframe in scala?

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

我花了一些时间试图使它工作(特别是如何正确地反序列化数据,但看起来您已经涵盖了这件事)...更新

  //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>

您将需要根据自己的需要更改spark版本。

更新:上面的代码仅适用于平面 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)

即使这样可能对您有帮助,

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"

希望这会有所帮助。 干杯

https://stackoverflow.com/a/48828303/5957143https://stackoverflow.com/a/47267060/5957143的组合适合我。

我使用以下内容创建MySchemaConversions

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]
}

然后我用

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

// unionedResultRdd为unionRDD [GenericRecord]

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

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

object MyObject{
    def myConverter(record: GenericRecord,
        myAvroRecordConverter: (GenericRecord) => Row): Row =
            myAvroRecordConverter.apply(record)
}

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

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

请注意,无需显式设置任何架构列。 我们重用了旧的DF的架构,该架构属于StructType类,可以轻松扩展。 但是,这种方法有时是不可能的,并且在某些情况下可能不如第一种方法有效。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM