我们从应用程序中的avro文件中读取时间戳信息。我正在测试从Spark 2.3.1到Spark 2.4的升级,其中包括新内置的spark-avro集成。但是,我无法弄清楚如何告诉avro模式我希望时间戳具有“ timestamp-millis”的逻辑类型,而不是默认的“ timestamp-micros”。
仅使用Databricks spark-avro 4.0.0软件包在Spark 2.3.1下查看测试avro文件,我们就有以下字段/架构:
{"name":"id","type":["string","null"]},
{"name":"searchQuery","type":["string","null"]},
{"name":"searchTime","type":["long","null"]},
{"name":"score","type":"double"},
{"name":"searchType","type":["string","null"]}
自纪元存储很长时间以来,其中的searchTime以毫秒为单位。一切都很好。
当我将其升级到Spark 2.4和内置的spark-avro 2.4.0软件包时,我有了以下较新的字段/模式:
{"name":"id","type":["string","null"]},
{"name":"searchQuery","type":["string","null"]},
{"name":"searchTime","type":[{"type":"long","logicalType":"timestamp-micros"},"null"]},
{"name":"score","type":"double"},
{"name":"searchType","type":["string","null"]}
可以看到,底层类型仍然很长,但是现在使用“ timestamp-micros”的逻辑类型进行了扩充。这完全符合the release notes所说的那样,但是,我找不到一种方法来指定使用'timestamp-millis'选项的模式。
这成为一个问题,当我将一个Timestamp对象写入到avro文件时,该对象初始化为在新纪元后10,000秒时,它将被读回10,000,000秒。在2.3.1 / databricks-avro下,它很长,没有任何关联的信息,所以它随它进入就出来了。
我们当前通过以下方式思考感兴趣的对象来构建模式:
val searchSchema: StructType = ScalaReflection.schemaFor[searchEntry].dataType.asInstanceOf[StructType]
我尝试通过创建一个修改过的架构来尝试增强此功能,该架构试图如下替换与searchTime条目对应的StructField:
val modSearchSchema = StructType(searchSchema.fields.map {
case StructField(name, _, nullable, metadata) if name == "searchTime" =>
StructField(name, org.apache.spark.sql.types.DataTypes.TimestampType, nullable, metadata)
case f => f
})
但是,spark.sql.types中定义的StructField对象没有可以增加其中的dataType的逻辑类型的概念。
case class StructField(
name: String,
dataType: DataType,
nullable: Boolean = true,
metadata: Metadata = Metadata.empty)
我还试图通过两种方式从JSON表示形式创建模式:
val schemaJSONrepr = """{
| "name" : "id",
| "type" : "string",
| "nullable" : true,
| "metadata" : { }
| }, {
| "name" : "searchQuery",
| "type" : "string",
| "nullable" : true,
| "metadata" : { }
| }, {
| "name" : "searchTime",
| "type" : "long",
| "logicalType" : "timestamp-millis",
| "nullable" : false,
| "metadata" : { }
| }, {
| "name" : "score",
| "type" : "double",
| "nullable" : false,
| "metadata" : { }
| }, {
| "name" : "searchType",
| "type" : "string",
| "nullable" : true,
| "metadata" : { }
| }""".stripMargin
第一次尝试只是从中创建一个数据类型
// here spark is a SparkSession instance from a higher scope.
val schema = DataType.fromJSON(schemaJSONrepr).asInstanceOf[StructType]
spark.read
.schema(schema)
.format("avro")
.option("basePath", baseUri)
.load(uris: _*)
这失败了,因为无法在其中为searchTime节点创建StructType,因为它具有“ logicalType”。第二次尝试是通过传入原始JSON字符串来简单地创建模式。
spark.read
.schema(schemaJSONrepr)
.format("avro")
.option("basePath", baseUri)
.load(uris: _*)
这不能说:
mismatched input '{' expecting {'SELECT', 'FROM', ...
== SQL ==
{
^^^
我发现在spark-avro API中,有一种方法可以从模式中获取逻辑类型,但无法弄清楚如何设置它。
您可以在上面看到我的失败尝试,我尝试使用Schema.Parser创建一个Avro模式对象,但是spark.read.schema中唯一接受的类型是String和StructType。
如果任何人都可以提供有关如何更改/指定此logicalType的见解,我将非常感谢。谢谢
答案 0 :(得分:0)
好的,我想我回答了我自己的问题。当我修改以编程方式构建的架构以使用显式的时间戳类型
时val modSearchSchema = StructType(searchSchema.fields.map {
case StructField(name, _, nullable, metadata) if name == "searchTime" =>
StructField(name, org.apache.spark.sql.types.DataTypes.TimestampType, nullable, metadata)
case f => f
})
当我们有要读取的Row对象时,在进行读取时我没有改变逻辑。最初,我们将读取Long并将其转换为Timestamp,这是事情发生的地方,因为它以毫秒为单位回读Long,这使它比我们预期的大1000倍。通过将读取更改为读取Timestamp对象,可以直接让底层逻辑解决此问题,从而将其从我们(我的手)手中夺走。所以:
// searchTime = new Timestamp(row.getAs[Long]("searchTime")) BROKEN
searchTime = row.getAs[Timestamp]("searchTime") // SUCCESS