我使用Spark 2.1的结构化流来读取内容为二进制avro编码的Kafka主题。
因此,在设置DataFrame
:
val messages = spark
.readStream
.format("kafka")
.options(kafkaConf)
.option("subscribe", config.getString("kafka.topic"))
.load()
如果我打印此DataFrame
(messages.printSchema()
)的架构,我会得到以下内容:
root
|-- key: binary (nullable = true)
|-- value: binary (nullable = true)
|-- topic: string (nullable = true)
|-- partition: integer (nullable = true)
|-- offset: long (nullable = true)
|-- timestamp: long (nullable = true)
|-- timestampType: integer (nullable = true)
这个问题应与avro-decoding问题正交,但我们假设我想以某种方式将value
内容从邮件DataFrame
转换为Dataset[BusinessObject]
,通过函数Array[Byte] => BusinessObject
。例如完整性,函数可能只是(使用avro4s):
case class BusinessObject(userId: String, eventId: String)
def fromAvro(bytes: Array[Byte]): BusinessObject =
AvroInputStream.binary[BusinessObject](
new ByteArrayInputStream(bytes)
).iterator.next
当然,as miguno says in this related question我不能仅使用DataFrame.map()
应用转换,因为我需要为此类BusinessObject
提供隐式编码器。
可以定义为:
implicit val myEncoder : Encoder[BusinessObject] = org.apache.spark.sql.Encoders.kryo[BusinessObject]
现在,执行地图:
val transformedMessages : Dataset[BusinessObjecŧ] = messages.map(row => fromAvro(row.getAs[Array[Byte]]("value")))
但如果我查询新架构,我会得到以下内容:
root
|-- value: binary (nullable = true)
我认为这没有任何意义,因为数据集应该使用BusinessObject
case-class的Product属性并获得正确的值。
我在阅读器中使用.schema(StructType)
在Spark SQL上看到了一些示例,但我不能这样做,不仅因为我使用readStream
,而且因为我实际上有在能够在这些领域中操作之前转换列。
我希望告诉Spark SQL引擎transformedMessages
数据集架构是StructField
,案例类别为'字段。
答案 0 :(得分:1)
我会说你得到了你所要求的。当我already explained today Encoders.kryo
生成带有序列化对象的blob
时。它的内部结构对于SQL引擎是不透明的,并且在不反序列化对象的情况下无法访问。如此有效的代码所做的就是采用一种序列化格式并将其替换为另一种格式。
您遇到的另一个问题是您尝试将动态类型DataFrame
(Dataset[Row]
)与静态类型对象混合使用。排除UDT API Spark SQL并不像这样工作。您可以使用静态Dataset
或DataFrame
使用struct
层次结构编码的对象结构。
好消息是简单的产品类型如BusinessObject
应该可以正常工作而不需要笨拙的Encoders.kryo
。只需跳过Kryo编码器定义并确保导入隐式编码器:
import spark.implicits._