Spark SQL----Apache Avro数据源指南
自Spark 2.4发布以来,Spark SQL提供了对读写Apache Avro数据的内置支持。
一、部署
spark-avro模块是外部的,默认情况下不包含在spark-submit或spark-shell中。与任何Spark应用程序一样,spark-submit用于启动应用程序。spark-avro_2.12及其依赖可以使用–packages直接添加到spark-submit中,例如,
./bin/spark-submit --packages org.apache.spark:spark-avro_2.12:3.5.1 ...
为了在spark-shell上进行实验,你还可以使用–packages直接添加org.apache.spark:spark-avro_2.12及其依赖项,
./bin/spark-shell --packages org.apache.spark:spark-avro_2.12:3.5.1 ...
有关提交具有外部依赖项的应用程序的更多详细信息,请参阅《应用程序提交指南》。
二、Load和Save函数
由于spark-avro模块是外部的,在DataFrameReader 或DataFrameWriter中没有.avro API。要以Avro格式加载/保存数据,需要指定数据源选项格式为avro(或org.apache.spark.sql.avro)。
df = spark.read.format("avro").load("examples/src/main/resources/users.avro")
df.select("name", "favorite_color").write.format("avro").save("namesAndFavColors.avro")
三、to_avro() 和from_avro()
Avro包提供函数to_avro以Avro格式将列编码为二进制,并提供函数from_avro()将Avro二进制数据解码为列。这两个函数都将一列转换为另一列,并且输入/输出SQL数据类型可以是复杂类型或原始类型。
使用Avro记录作为列在读取或写入像Kafka这样的streaming源时非常有用。每个Kafka键值记录都会添加一些元数据,例如Kafka中的ingestion时间戳、Kafka的偏移量等。
- 如果包含数据的“value”字段在Avro中,你可以使用from_avro() 提取数据,对其进行丰富、清理,然后将其再次推送到Kafka下游或将其写入文件。
- to_avro()可用于将结构转换为avro记录。当你想在向Kafka写入数据时将多个列重新编码为一个列时,这种方法特别有用。
from pyspark.sql.avro.functions import from_avro, to_avro
# `from_avro` requires Avro schema in JSON string format.
jsonFormatSchema = open("examples/src/main/resources/user.avsc", "r").read()
df = spark\
.readStream\
.format("kafka")\
.option("kafka.bootstrap.servers", "host1:port1,host2:port2")\
.option("subscribe", "topic1")\
.load()
# 1. Decode the Avro data into a struct;
# 2. Filter by column `favorite_color`;
# 3. Encode the column `name` in Avro format.
output = df\
.select(from_avro("value", jsonFormatSchema).alias("user"))\
.where('user.favorite_color == "red"')\
.select(to_avro("user.name").alias("value"))
query = output\
.writeStream\
.format("kafka")\
.option("kafka.bootstrap.servers", "host1:port1,host2:port2")\
.option("topic", "topic2")\
.start()
四、数据源选项
Avro的数据源选项可以通过以下方法来设置:
- DataFrameReader或DataFrameWriter上的.option方法。
- 函数"from_avro"中的options参数。
Property Name | Default | Meaning | Scope | Since Version |
---|---|---|---|---|
avroSchema | None | 用户以JSON格式提供的可选schema。 ● 当读取Avro文件或调用from_avro函数时,可以将此选项设置为演进后的schema,该schema与实际的Avro schema兼容但不同。反序列化schema将与演进后的schema保持一致。例如,如果我们设置了一个包含一个默认值的附加列的演进后的schema,那么Spark中的读取结果也将包含新列。注意,当与from_avro一起使用这个选项时,你仍然需要将实际的avro schema作为参数传递给函数。● 在写入Avro时,如果预期输出的Avro schema与Spark转换的schema不匹配,则可以设置此选项。例如,一个列的预期schema是“enum”类型,而不是默认转换schema中的“string”类型。 | read, write和from_avro函数 | 2.4.0 |
recordName | topLevelRecord | 写入结果中的顶级记录名称,这在Avro规范中是必需的。 | write | 2.4.0 |
recordNamespace | “” | 在写结果中记录namespace。 | write | 2.4.0 |
ignoreExtension | true | 该选项控制在read中忽略没有.avro扩展名的文件。如果启用了该选项,将加载所有文件(扩展名为.avro的和没有.avro的)。该选项已被弃用,并将在未来的版本中删除。请使用通用的数据源选项pathGlobFilter来过滤文件名。 | read | 2.4.0 |
compression | snappy | 压缩选项允许指定写入时使用的压缩编解码器。目前支持的编解码器有uncompressed、snappy、deflate、bzip2、xz和zstandard。如果未设置该选项,则会考虑spark.sql.avro.compression.codec配置。 | write | 2.4.0 |
mode | FAILFAST | 模式选项允许为函数from_avro指定解析模式。目前支持的模式有:● FAILFAST:在处理损坏的记录时引发异常。● PERMISSIVE:损坏的记录将作为空结果进行处理。因此,数据schema被强制为完全可以为null,这可能与用户提供的schema不同 | from_avro函数 | 2.4.0 |
datetimeRebaseMode | (spark.sql.avro.datetimeRebaseModeInRead配置的值) | datetimeRebaseMode选项允许为从Julian 日历到Proleptic Gregorian日历的date、timestamp-micros、timestamp-millis逻辑类型的值指定rebase模式。目前支持的模式有:● EXCEPTION:读取两个日历之间不明确的ancient日期/时间戳失败。● CORRECTED:加载日期/时间戳而不rebase。● LEGACY:将ancient日期/时间戳从Julian日历rebase为 Proleptic Gregorian日历。 | read 和from_avro函数 | 3.2.0 |
positionalFieldMatching | false | 这可以与avroSchema选项一起使用,以调整将所提供的Avro schema中的字段与SQL schema中的那些字段匹配的行为。默认情况下,将使用字段名称执行匹配,忽略它们的位置。如果此选项设置为“true”,则匹配将基于字段的位置。 | read 和 write | 3.2.0 |
enableStableIdentifiersForUnionType | false | 如果设置为true, Avro schema被反序列化为Spark SQL schema,Avro Union类型被转换为字段名称与其各自类型保持一致的结构。结果字段名被转换为小写,例如member int或member string。如果两个用户定义的类型名称或用户定义的类型名称与内置类型名称相同,则会引发异常。但是,在其他情况下,字段名可以唯一标识。 | read | 3.5.0 |
五、配置
Avro的配置可以使用SparkSession上的setConf方法或使用SQL运行SET key=value命令来完成。
Property Name | Default | Meaning | Since Version |
---|---|---|---|
spark.sql.legacy.replaceDatabricksSparkAvro.enabled | true | 如果它被设置为true,数据源provider com.databricks.spark.avro被映射到内置但外部的Avro数据源模块以向后兼容。注意:SQL配置在Spark 3.2中已弃用,将来可能会被删除。 | 2.4.0 |
spark.sql.avro.compression.codec | snappy | 压缩编解码器用于写AVRO文件。支持的编解码器:uncompressed, deflate, snappy, bzip2, xz和zstandard。默认的编解码器是snappy。 | 2.4.0 |
spark.sql.avro.deflate.level | -1 | 用于写AVRO文件的deflate编解码器的压缩级别。有效值必须在1 ~ 9或-1之间。默认值为-1,对应于当前实现中的6级。 | 2.4.0 |
spark.sql.avro.datetimeRebaseModeInRead | EXCEPTION | date、timestamp-micros、timestamp-millis逻辑类型的值从Julian日历到Proleptic Gregorian日历的rebase模式:● EXCEPTION:如果Spark看到两个日历之间的ancient日期/时间戳不明确,它将无法读取。● CORRECTED:Spark不会按原样rebase并读取日期/时间戳。● LEGACY:Spark在读取Avro文件时,将日期/时间戳从传统混合日历(Julian + Gregorian)rebase为Proleptic Gregorian日历。只有当Avro文件的写入程序信息(如Spark、Hive)未知时,此配置才有效。 | 3.0.0 |
spark.sql.avro.datetimeRebaseModeInWrite | EXCEPTION | 从Proleptic Gregorian日历到Julian日历的date、timestamp-micros、timestamp-millis逻辑类型的值的rebase模式: ●EXCEPTION:如果在两个日历之间看到模糊的ancient日期/时间戳,Spark将写入失败。●CORRECTED: Spark不会按原样rebase并写入日期/时间戳。●LEGACY: Spark将在写入Avro文件时将日期/时间戳从Proleptic Gregorian日历rebase为legacy hybrid (Julian + Gregorian)日历。 | 3.0.0 |
spark.sql.avro.filterPushdown.enabled | true | 当为true时,启用Avro数据源的过滤器下推。 | 3.1.0 |
六、与Databricks spark-avro的兼容性
这个Avro数据源模块最初来自Databricks的开源存储库spark-avro,并与之兼容。
默认情况下,启用SQL配置spark.sql.legacy.replaceDatabricksSparkAvro.enabled后,数据源provider com.databricks.spark.avro将映射到此内置Avro模块。对于在目录元存储中用Provider属性com.databricks.spark.avro创建的Spark表,如果你使用这个内置的Avro模块,则映射对于加载这些表是必不可少的。
注意,在Databricks的 spark-avro中,为快捷方式函数.avro()创建了隐式类AvroDataFrameWriter和AvroDataFrameReader。在这个内置但外部的模块中,两个隐式类都被删除了。请在DataFrameWriter 或DataFrameReader中使用.format(“avro”),这应该足够整洁和良好。
如果你更喜欢使用自己构建的spark-avro jar文件,你可以简单地禁用配置spark.sql.legacy.replaceDatabricksSparkAvro.enabled,并在部署应用程序时使用–jars选项。有关更多详细信息,请阅读《应用程序提交指南》中的“高级依赖关系管理”部分。
七、Avro -> Spark SQL转换的支持类型
目前Spark支持读取Avro记录下的所有基本类型和复杂类型。
Avro type | Spark SQL type |
---|---|
boolean | BooleanType |
int | IntegerType |
long | LongType |
float | FloatType |
double | DoubleType |
string | StringType |
enum | StringType |
fixed | BinaryType |
bytes | BinaryType |
record | StructType |
array | ArrayType |
map | MapType |
union | See below |
除了上面列出的类型之外,它还支持读取union类型。以下三种类型被视为基本union类型:
- union(int,long)将映射到LongType。
- union(float,double)将映射到DoubleType。
- union(something,null),其中something是任何支持的Avro类型。这将被映射到与某个事物相同的Spark SQL类型,并将nullable设置为true。所有其他union类型都被认为是复杂的。根据union的成员,它们将映射到StructType,其中字段名为member0、member1等。这与在Avro和Parquet之间转换时的行为一致。
它还支持读取以下Avro逻辑类型:
Avro logical type | Avro type | Spark SQL type |
---|---|---|
date | int | DateType |
timestamp-millis | long | TimestampType |
timestamp-micros | long | TimestampType |
decimal | fixed | DecimalType |
decimal | bytes | DecimalType |
目前,它会忽略Avro文件中的文档、别名和其他属性。
八、Spark SQL -> Avro 转换的支持类型
Spark支持将所有Spark SQL类型写入Avro。对于大多数类型,从Spark类型到Avro类型的映射是直接的(例如,IntegerType转换为int);然而,也有一些特殊情况,如下所列:
Spark SQL type | Avro type | Avro logical type |
---|---|---|
ByteType | int | |
ShortType | int | |
BinaryType | bytes | |
DateType | int | date |
TimestampType | long | timestamp-micros |
DecimalType | fixed | decimal |
你还可以使用选项avroSchema指定整个输出Avro schema,以便Spark SQL类型可以转换为其他Avro类型。默认情况下不应用以下转换,并且需要用户指定的Avro schema:
Spark SQL type | Avro type | Avro logical type |
---|---|---|
BinaryType | fixed | |
StringType | enum | |
TimestampType | long | timestamp-millis |
DecimalType | bytes | decimal |