web-dev-qa-db-ja.com

スキーマを使用して、SparkでAVROメッセージをDataFrameに変換します。

スキーマを使用して avro メッセージを kafka から spark を付けて dataframe に変換する方法はありますか?ユーザーレコードのスキーマファイル:

{
  "fields": [
    { "name": "firstName", "type": "string" },
    { "name": "lastName", "type": "string" }
  ],
  "name": "user",
  "type": "record"
}

SqlNetworkWordCountの例 および Kafka、SparkおよびAvro-パート3、Avroメッセージの生成と消費 からのコードスニペットは、メッセージを読み取るために使用します。

object Injection {
  val parser = new Schema.Parser()
  val schema = parser.parse(getClass.getResourceAsStream("/user_schema.json"))
  val injection: Injection[GenericRecord, Array[Byte]] = GenericAvroCodecs.toBinary(schema)
}

...

messages.foreachRDD((rdd: RDD[(String, Array[Byte])]) => {
  val sqlContext = SQLContextSingleton.getInstance(rdd.sparkContext)
  import sqlContext.implicits._

  val df = rdd.map(message => Injection.injection.invert(message._2).get)
    .map(record => User(record.get("firstName").toString, records.get("lastName").toString)).toDF()

  df.show()
})

case class User(firstName: String, lastName: String)

どういうわけか、AVROメッセージをDataFrameに変換するためにケースクラスを使用する以外に方法はありません。代わりにスキーマを使用する可能性はありますか? Spark 1.6.2Kafka 0.10を使用しています。

興味がある場合に備えて、完全なコード。

import com.Twitter.bijection.Injection
import com.Twitter.bijection.avro.GenericAvroCodecs
import kafka.serializer.{DefaultDecoder, StringDecoder}
import org.Apache.avro.Schema
import org.Apache.avro.generic.GenericRecord
import org.Apache.spark.rdd.RDD
import org.Apache.spark.sql.SQLContext
import org.Apache.spark.streaming.kafka._
import org.Apache.spark.streaming.{Seconds, StreamingContext, Time}
import org.Apache.spark.{SparkConf, SparkContext}

object ReadMessagesFromKafka {
  object Injection {
    val parser = new Schema.Parser()
    val schema = parser.parse(getClass.getResourceAsStream("/user_schema.json"))
    val injection: Injection[GenericRecord, Array[Byte]] = GenericAvroCodecs.toBinary(schema)
  }

  def main(args: Array[String]) {
    val brokers = "127.0.0.1:9092"
    val topics = "test"

    // Create context with 2 second batch interval
    val sparkConf = new SparkConf().setAppName("ReadMessagesFromKafka").setMaster("local[*]")
    val ssc = new StreamingContext(sparkConf, Seconds(2))

    // Create direct kafka stream with brokers and topics
    val topicsSet = topics.split(",").toSet
    val kafkaParams = Map[String, String]("metadata.broker.list" -> brokers)
    val messages = KafkaUtils.createDirectStream[String, Array[Byte], StringDecoder, DefaultDecoder](
  ssc, kafkaParams, topicsSet)

    messages.foreachRDD((rdd: RDD[(String, Array[Byte])]) => {
      val sqlContext = SQLContextSingleton.getInstance(rdd.sparkContext)
      import sqlContext.implicits._

      val df = rdd.map(message => Injection.injection.invert(message._2).get)
    .map(record => User(record.get("firstName").toString, records.get("lastName").toString)).toDF()

      df.show()
    })

    // Start the computation
    ssc.start()
    ssc.awaitTermination()
  }
}

/** Case class for converting RDD to DataFrame */
case class User(firstName: String, lastName: String)

/** Lazily instantiated singleton instance of SQLContext */
object SQLContextSingleton {
  @transient  private var instance: SQLContext = _

  def getInstance(sparkContext: SparkContext): SQLContext = {
    if (instance == null) {
      instance = new SQLContext(sparkContext)
    }
    instance
  }
}
13
Sascha Vetter

OPはおそらく問題を解決しましたが、今後の参考のためにこの問題を一般的に解決したので、ここに投稿すると役立つと思いました。

したがって、一般的に言えば、Avroスキーマをspark StructTypeに変換し、RDDにあるオブジェクトをRow [Any]に変換してから、以下を使用する必要があります。

spark.createDataFrame(<RDD[obj] mapped to RDD[Row}>,<schema as StructType>

Avroスキーマを変換するために、私は spark-avro を次のように使用しました:

SchemaConverters.toSqlType(avroSchema).dataType.asInstanceOf[StructType]

RDDの変換はよりトリッキーでした。スキーマが単純な場合、おそらく次のような単純なマップを実行できます。

rdd.map(obj=>{
    val seq = (obj.getName(),obj.getAge()
    Row.fromSeq(seq))
    })

この例では、オブジェクトには2つのフィールドnameとageがあります。

重要なことは、Rowの要素が以前のStructTypeのフィールドの順序と型と一致することを確認することです。

私の特定のケースでは、将来のスキーマ変更をサポートするために一般的に処理したいはるかに複雑なオブジェクトがあったので、コードがはるかに複雑になりました。

oPによって提案されたメソッドは、一部のケースでも機能するはずですが、複雑なオブジェクト(プリミティブまたはケースクラスではない)を暗示するのは困難です

別のヒントは、クラス内にクラスがある場合、そのクラスを行に変換して、ラッピングクラスが次のようなものに変換されるようにすることです。

Row(Any,Any,Any,Row,...)

オブジェクトを行に変換する方法について前述したspark-avroプロジェクトも見ることができます。自分でいくつかのロジックを使用しました

これを読んでいる人がさらに助けが必要な場合はコメントで私に尋ねてください、私は助けようとします

こちら でも同様の問題が解決されます。

4
Tal Joffe

これを見てください https://github.com/databricks/spark-avro/blob/master/src/test/scala/com/databricks/spark/avro/AvroSuite.scala

だから代わりに

 val df = rdd.map(message => Injection.injection.invert(message._2).get)
.map(record => User(record.get("firstName").toString,records.get("lastName").toString)).toDF()

あなたはこれを試すことができます

 val df = spark.read.avro(message._2.get)
4
Sambit Tripathy

私は同様の問題に取り組みましたが、Javaでした。 Scalaについては不明ですが、ライブラリ com.databricks.spark.avro を見てください。

2
RadioLog

sparkアプリケーションを停止して再デプロイする必要なしにスキーマの変更を処理できる方法でこれを処理することに関心のある人は、(アプリロジックがこれを処理できると仮定して)これを参照してください question/answer

1
Ben