Spark SQL之数据源(Data Source)与保存模式(Save Modes)

本篇大纲:

(一).Generic Load/Save Functions
(二).Parquet Files
(三).ORC Files
(四).JSON Files
(五).Hive Tables
(六).JDBC To Other Databases
(七).Avro Files
(八).Troubleshooting

SparkSQL支持通过DataFrame接口对各种数据源进行操作。DataFrame可以使用关系转换进行操作,也可以用于创建临时视图。将DataFrame注册为临时视图允许你对其数据运行SQL查询。本节介绍使用Spark数据源加载和保存数据的一般方法,然后介绍可用于内置数据源的特定选项。

(一).通用的加载/保存功能

在最简单的形式中,默认数据源(除非由spark.sql.sources.default配置)将用于所有操作。

Scala代码:

val usersDF = spark.read.load("examples/src/main/resources/users.parquet")
usersDF.select("name", "favorite_color").write.save("namesAndFavColors.parquet")

Java代码:

Dataset<Row> usersDF = spark.read().load("examples/src/main/resources/users.parquet");
usersDF.select("name", "favorite_color").write().save("namesAndFavColors.parquet");

手动指定选项
你还可以手动指定要使用的数据源以及希望传递给数据源的任何额外选项。数据源由其完全限定的名称(即但是对于内置的源代码,你也可以使用它们的短名称(json、parquet、jdbc、orc、libsvm、csv、text)。从任何数据源类型加载的DataFrames都可以使用此语法转换为其他类型。

要加载JSON文件,可以使用:
Scala代码:

val peopleDF = spark.read.format("json").load("examples/src/main/resources/people.json")
peopleDF.select("name", "age").write.format("parquet").save("namesAndAges.parquet")

Java代码:

Dataset<Row> peopleDF = spark.read().format("json").load("examples/src/main/resources/people.json");
peopleDF.select("name", "age").write().format("parquet").save("namesAndAges.parquet");

要加载CSV文件,你可以使用:
Scala代码:

val peopleDFCsv = spark.read.format("csv")
  .option("sep", ";")
  .option("inferSchema", "true")
  .option("header", "true")
  .load("examples/src/main/resources/people.csv")

Java代码:

Dataset<Row> peopleDFCsv = spark.read().format("csv")
  .option("sep", ";")
  .option("inferSchema", "true")
  .option("header", "true")
  .load("examples/src/main/resources/people.csv");

在写操作期间也使用额外的选项。例如,你可以控制ORC数据源的bloom过滤器和字典编码。下面的ORC示例将在favorite_color上创建bloom过滤器,并对名称和favorite_color使用字典编码。对于Parquet,存在parquet.enable.dictionary。要找到更多关于额外的ORC/Parquet 选项的详细信息,请访问官方的Apache ORC/Parquet 网站。
Scala代码:

usersDF.write.format("orc")
  .option("orc.bloom.filter.columns", "favorite_color")
  .option("orc.dictionary.key.threshold", "1.0")
  .save("users_with_options.orc")

Java代码:

usersDF.write().format("orc")
  .option("orc.bloom.filter.columns", "favorite_color")
  .option("orc.dictionary.key.threshold", "1.0")
  .save("users_with_options.orc");

Sql代码:

CREATE TABLE users_with_options (
  name STRING,
  favorite_color STRING,
  favorite_numbers array<integer>
) USING ORC
OPTIONS (
  orc.bloom.filter.columns 'favorite_color',
  orc.dictionary.key.threshold '1.0'
)

直接在文件上运行SQL与使用read API将文件加载到DataFrame并进行查询不同,你还可以使用SQL直接查询该文件。
Scala代码:

val sqlDF = spark.sql("SELECT * FROM parquet.`examples/src/main/resources/users.parquet`")

Java代码:

Dataset<Row> sqlDF = spark.sql("SELECT * FROM parquet.`examples/src/main/resources/users.parquet`");

保存模式

Save操作可以选择采用SaveMode,它指定如果存在如何处理现有数据。重要的是要认识到这些保存模式不使用任何锁定,也不是原子模式。此外,在执行覆盖时,将在写出新数据之前删除数据。

Scala/Java Any Language 含义
SaveMode.ErrorIfExists (default) “error” or “errorifexists” (default) 当将一个DataFrame保存到数据源时,如果数据已经存在,则预期将引发异常。
SaveMode.Append “append” “append” 当将一个DataFrame保存到数据源时,如果数据/表已经存在,则希望将DataFrame的内容附加到现有数据中。
SaveMode.Overwrite “overwrite” 覆盖模式意味着,当将一个DataFrame保存到一个数据源时,如果数据/表已经存在,则预期现有数据将被DataFrame的内容覆盖。
SaveMode.Ignore “ignore” “ignore” 忽略模式意味着,当将一个DataFrame保存到一个数据源时,如果数据已经存在,那么save操作将不会保存DataFrame的内容,也不会更改现有的数据。这与SQL中不存在的CREATE表类似。

保存到持久表(Persistent Tables)
DataFrames还可以使用saveAsTable命令作为持久化表保存到Hive metastore中。请注意,使用此功能不需要使用现有的Hive部署。Spark将为您创建一个默认的本地Hive metastore(使用Derby)。与createOrReplaceTempView命令不同,saveAsTable将实现DataFrame的内容,并创建一个指向Hive metastore中的数据的指针。
即使在Spark程序重新启动之后,只要您保持到相同转移服务器的连接,持久性表仍然存在。通过使用表名在SparkSession上调用表方法,可以为持久表创建一个DataFrame。
对于基于文件的数据源,如text, parquet, json等,可以通过path选项指定自定义表路径,如df.write.option(“path”, “/some/path”).saveAsTable(“t”)。删除表时,自定义表路径不会被删除,表数据仍然存在。如果没有指定自定义表路径,Spark将把数据写入仓库目录下的默认表路径。删除表时,默认的表路径也将被删除。
从Spark 2.1开始,持久数据源表在Hive metastore中存储了每个分区的元数据。这带来了几个好处:

由于metastore只能为查询返回必要的分区,因此不再需要在第一个查询中发现表中的所有分区。
Hive DDLs,比如ALTER TABLE PARTITION…SET LOCATION现在可用于使用数据源API创建的表。

注意,在创建外部数据源表(带有path选项的表)时,默认情况下不会收集分区信息。要同步转移元数据中的分区信息,可以调用MSCK修复表。

分桶、排序及分区
对于基于文件的数据源,也可以对输出进行桶和排序或分区。嵌套和排序只适用于持久表:
Scala/Java代码:

peopleDF.write.bucketBy(42, "name").sortBy("age").saveAsTable("people_bucketed")

Sql代码:

CREATE TABLE users_bucketed_by_name(
  name STRING,
  favorite_color STRING,
  favorite_numbers array<integer>
) USING parquet
CLUSTERED BY(name) INTO 42 BUCKETS;

当使用数据集api时,分区可以与save和saveAsTable一起使用。
Scala/Java代码:

usersDF.write.partitionBy("favorite_color").format("parquet").save("namesPartByColor.parquet")

Sql代码:

CREATE TABLE users_by_favorite_color(
  name STRING,
  favorite_color STRING,
  favorite_numbers array<integer>
) USING csv PARTITIONED BY(favorite_color);

可以对单个表同时使用分区和嵌套:
Scala/Java代码:

usersDF
  .write
  .partitionBy("favorite_color")
  .bucketBy(42, "name")
  .saveAsTable("users_partitioned_bucketed")

Sql代码:

CREATE TABLE users_bucketed_and_partitioned(
  name STRING,
  favorite_color STRING,
  favorite_numbers array<integer>
) USING parquet
PARTITIONED BY (favorite_color)
CLUSTERED BY(name) SORTED BY (favorite_numbers) INTO 42 BUCKETS;

partitionBy创建分区发现部分中描述的目录结构。因此,它对具有高基数的列的适用性有限。相比之下,bucketBy将数据分布在固定数量的bucket上,并且可以在多个惟一值无界时使用。

(二).Parquet Files

Parquet 是一种列式储存的格式,它受到许多其他数据处理系统的支持。Spark SQL支持读取和写入Parquet 文件,这些文件自动保存原始数据的模式。在编写Parquet 文件时,出于兼容性原因,所有列都自动转换为空。
加载数据编程
使用上面例子中的数据:

// 大多数常见类型的编码器都是通过导入自动提供的spark的隐式转换
import spark.implicits._

val peopleDF = spark.read.json("examples/src/main/resources/people.json")

// 可以将DataFrames保存为parquet文件,以维护模式信息
peopleDF.write.parquet("people.parquet")

// 读取上面创建的parquet文件
// Parquet文件是自描述的,因此模式是保留的
//加载Parquet文件返回的结果也是一个DataFrame
val parquetFileDF = spark.read.parquet("people.parquet")

// Parquet文件还可以用于创建临时视图,然后在SQL语句中使用
parquetFileDF.createOrReplaceTempView("parquetFile")
val namesDF = spark.sql("SELECT name FROM parquetFile WHERE age BETWEEN 13 AND 19")
namesDF.map(attributes => "Name: " + attributes(0)).show()
// +------------+
// |       value|
// +------------+
// |Name: Justin|
// +------------+

分区的发现
表分区是Hive等系统中常用的优化方法。在分区表中,数据通常存储在不同的目录中,分区列值编码在每个分区目录的路径中。所有内置的文件源(包括文本/CSV/JSON/ORC/Parquet)都能够自动发现和推断分区信息。例如,我们可以使用以下目录结构将所有以前使用的人口数据存储到分区表中,其中两个额外的列,性别和国家作为分区列:

path
└── to
    └── table
        ├── gender=male
        │   ├── ...
        │   │
        │   ├── country=US
        │   │   └── data.parquet
        │   ├── country=CN
        │   │   └── data.parquet
        │   └── ...
        └── gender=female
            ├── ...
            │
            ├── country=US
            │   └── data.parquet
            ├── country=CN
            │   └── data.parquet
            └── ...

通过传递path/to/table到SparkSession.read.parquet 或 SparkSession.read.load。加载后,Spark SQL将自动从路径中提取分区信息。现在返回的DataFrame的模式变成:

root
|-- name: string (nullable = true)
|-- age: long (nullable = true)
|-- gender: string (nullable = true)
|-- country: string (nullable = true)

注意,分区列的数据类型是自动推断的。目前,支持数字数据类型、日期、时间戳和字符串类型。对于这些用例,你可以通过 spark.sql.sources.partitionColumnTypeInference.enabled方式配置自动类型推断, 默认为true。禁用类型推断时,分区列将使用string类型。

从Spark 1.6.0开始,默认情况下,分区发现只能在给定路径下找到分区。对于上面的例子,如果用户传递 path/to/table/gender=male 到SparkSession.read.parquet 或 SparkSession.read.load加载时,不会将性别视为分区列。如果用户需要指定分区发现应该开始的基本路径,他们可以在数据源选项中设置basePath。例如,当path/to/table/gender=male是数据的路径,用户将basePath设置为path/to/table/时,gender将是一个分区列。

元数据合并(Schema Merging)
像Protocol Buffer、Avro和Thrift一样,Parquet也支持模式演化。用户可以从一个简单的模式开始,然后根据需要逐渐向该模式添加更多的列。这样,用户可能会得到多个具有不同但相互兼容的模式的Parquet文件。Parquet数据源现在能够自动检测这种情况并合并所有这些文件的模式。

由于模式合并是一个相对昂贵的操作,而且在大多数情况下不是必需的,因此Spark从1.5.0版本开始默认关闭它。你可以通过:

(1).读取parquet地板文件时,将数据源选项mergeSchema设置为true(如下面的示例所示);
(2).设置全局SQL选项spark.sql.parquet.mergeSchema为true。

Scala代码:

// 这用于隐式地将RDD转换为DataFrame。
import spark.implicits._

// 创建一个简单的DataFrame,存储到分区目录中
val squaresDF = spark.sparkContext.makeRDD(1 to 5).map(i => (i, i * i)).toDF("value", "square")
squaresDF.write.parquet("data/test_table/key=1")

// 在新的分区目录中创建另一个DataFrame,添加新列并删除现有列
val cubesDF = spark.sparkContext.makeRDD(6 to 10).map(i => (i, i * i * i)).toDF("value", "cube")
cubesDF.write.parquet("data/test_table/key=2")

// 读取分区表
val mergedDF = spark.read.option("mergeSchema", "true").parquet("data/test_table")
mergedDF.printSchema()

// 最后的模式由parquet文件中的所有3列组成
// 分区列出现在分区目录路径中
// root
//  |-- value: int (nullable = true)
//  |-- square: int (nullable = true)
//  |-- cube: int (nullable = true)
//  |-- key: int (nullable = true)

Hive元数据Parquet表的转换
当从Hive元数据的Parquet 表读取和写入数据时,Spark SQL将尝试使用自己的Parquet 支持,而不是Hive SerDe,以获得更好的性能。这个行为由spark.sql.hive.convertMetastoreParquet配置,并在默认情况下打开。

Hive/Parquet元数据调和(Schema Reconciliation)
从表模式处理的角度来看,Hive和Parquet有两个关键的区别:

1.Hive是不区分大小写的,而Parquet区分大小写.
2.Hive认为所有的列都是可空的,而Parquet中的可空性是很重要的.

因此,在将Hive metastore Parquet 表转换为Spark SQL Parquet 表时,我们必须将Hive metastore Parquet 表与Parquet模式进行调和。调和规则如下:

1.无论是否为空,两个模式中具有相同名称的字段必须具有相同的数据类型。调和字段应该具有Parquet 边的数据类型,以便考虑可空性。
2.协调模式正好包含在Hive metastore模式中定义的字段:
	(1).只出现在Parquet 模式中的任何字段都将被删除到调和模式中。
	(2).只出现在Hive metastore模式中的任何字段都作为可空字段添加到调和模式中。

元数据刷新(Metadata Refreshing)
Spark SQL缓存Parquet 元数据以获得更好的性能。当启用Hive metastore Parquet 表转换时,这些转换表的元数据也会被缓存。如果这些表是由Hive或其他外部工具更新的,则需要手动刷新它们,以确保一致的元数据。
*Scala代码:

// spark is an existing SparkSession
spark.catalog.refreshTable("my_table")

Sql代码:*

REFRESH TABLE my_table;

配置(Configuration)
可以使用SparkSession上的setConf方法或使用SQL运行SET key=value命令来配置Parquet 。

属性名称 默认值 含义
spark.sql.parquet.binaryAsString false 其他一些生成Parquet的系统,特别是Impala、Hive和Spark SQL的旧版本,在编写Parquet模式时不区分二进制数据和字符串。这个标志告诉Spark SQL将二进制数据解释为字符串,以提供与这些系统的兼容性
spark.sql.parquet.int96AsTimestamp true 一些parquet生产系统,特别是Impala 和Hive,将时间戳存储到INT96中。改标志告诉Spark SQL将INT96数据解释为一个时间戳,以提供与这些系统的兼容性
spark.sql.parquet.compression.codec snappy 设置编写parquet文件时使用的压缩编解码器。如果在表特定的选项/属性中指定了“compression”或“parque.compression”,那么优先级应该是“compression”、“parque .compression”、“spark.sq .parque .compression.codec”。可接受的值包括:none、uncompression、snappy、gzip、lzo、brotli、lz4、zstd。请注意,“zstd”要求在Hadoop 2.9.0之前安装“ZStandardCodec”,“brotli”要求安装“BrotliCodec”。
spark.sql.parquet.filterPushdown true 当设置为true时,启用parquet过滤下推优化。
spark.sql.hive.convertMetastoreParquet true 当设置为false时,Spark SQL将对Parquet表使用Hive SerDe,而不是内置支持。
spark.sql.parquet.mergeSchema false 如果为真,Parquet数据源将合并从所有数据文件收集的模式,否则将从总结文件中选择模式,如果没有总结文件可用,则从随机数据文件中选择模式。
spark.sql.optimizer.metadataOnly true 如果为真,则启用仅使用表的元数据来生成分区列而不是表扫描的元数据查询优化。当扫描的所有列都是分区列且查询具有满足不同语义的聚合操作符时,该方法将适用。
spark.sql.parquet.writeLegacyFormat false 如果为真,数据将以Spark 1.4或更早的方式编写。例如,十进制值将以Apache Parquet的固定长度字节数组格式编写,Apache Hive和Apache Impala等其他系统使用这种格式。如果为假,将使用新格式的parque。例如,小数将以基于int的格式编写。如果parque输出用于不支持这种新格式的系统,则设置为true。

(三).ORC Files

从Spark 2.3开始,Spark支持一个矢量化的ORC阅读器,并为ORC文件提供一种新的ORC文件格式。为此,新添加了以下配置。当spark.sql.orc.impl被设置为native并且 spark.sql.orc.enableVectorizedReader 设置为true,向量化阅读器用于本机ORC表(例如,使用ORC子句创建的表)。对于Hive ORC serde表(例如,使用Hive选项(fileFormat ‘ORC’)创建的子句),当spark.sq . Hive.convertMetastoreOrc也设置为true时矢量阅读器被使用。

属性名称 默认值 含义
spark.sql.orc.impl native ORC实现的名称。它可以是本地和hive中的一种。native是指构建在Apache ORC 1.4上的本机ORC支持。“hive”是指hive 1.2.1中的ORC库。
spark.sql.orc.enableVectorizedReader true 在本机实现中启用向量化orc解码。如果为false,则在本机实现中使用新的非向量化ORC阅读器。对于hive实现,这将被忽略。

(四).JSON Files

Spark SQL可以自动推断JSON数据集的模式,并将其加载为数据集[Row]。这个转换可以在数据集[String]或JSON文件上使用SparkSession.read.json()来完成。
注意,作为json文件提供的文件不是典型的json文件。每行必须包含一个单独的、自包含的有效JSON对象。有关更多信息,请参见JSON行文本格式,也称为以新行分隔的JSON。

对于常规的多行JSON文件,将多行选项设置为true。
Scala代码:

// 在创建数据集时,通过导入基本类型(Int、String等)和Product类型(样例类)编码器可以得到支持。
import spark.implicits._

// JSON数据集由路径指向。路径可以是单个文本文件,也可以是存储文本文件的目录.
val path = "examples/src/main/resources/people.json"
val peopleDF = spark.read.json(path)

// 可以使用printSchema()方法可视化推断出的模式
peopleDF.printSchema()
// root
//  |-- age: long (nullable = true)
//  |-- name: string (nullable = true)

// 使用DataFrame创建临时视图
peopleDF.createOrReplaceTempView("people")

// 可以使用spark提供的SQL方法运行SQL语句
val teenagerNamesDF = spark.sql("SELECT name FROM people WHERE age BETWEEN 13 AND 19")
teenagerNamesDF.show()
// +------+
// |  name|
// +------+
// |Justin|
// +------+

// 可以为JSON数据集创建一个DataFrame,该数据集由每个字符串存储一个JSON对象的数据集[String]表示
val otherPeopleDataset = spark.createDataset(
  """{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}""" :: Nil)
val otherPeople = spark.read.json(otherPeopleDataset)
otherPeople.show()
// +---------------+----+
// |        address|name|
// +---------------+----+
// |[Columbus,Ohio]| Yin|
// +---------------+----+

(五).Hive Tables

Spark SQL还支持读取和写入存储在Apache Hive中的数据。然而,由于Hive有大量的依赖项,这些依赖项并不包含在默认的Spark发行版中。如果可以在类路径中找到Hive依赖项,Spark将自动加载它们。请注意,这些Hive依赖项还必须出现在所有工作节点上,因为它们需要访问Hive序列化和反序列化库(SerDes),以便访问存储在Hive中的数据。
Hive的配置是通过放置在Hive的conf/目录中的hive-site.xml, core-site.xml(用于安全配置)和HDFS -site.xml(用于HDFS配置)文件来完成。

在使用Hive时,必须使用Hive支持实例化SparkSession,包括连接到持久的Hive转移、支持Hive serdes和Hive用户定义的函数。没有现有Hive部署的用户仍然可以启用Hive支持。当没有配置hive-site.xml,上下文会在当前目录中自动创建metastore_db,并创建一个由spark.sql.warehouse.dir配置的目录,默认为启动Spark应用程序的当前目录中的目录Spark -warehouse。
注意: hive.metastore.warehouse.dir中的dir属性在Spark 2.0.0之后被弃用。相反,使用spark.sql.warehouse.dir指定数据库在仓库中的默认位置。您可能需要将写权限授予启动Spark应用程序的用户。

Scala代码示例:

import java.io.File
import org.apache.spark.sql.{Row, SaveMode, SparkSession}

case class Record(key: Int, value: String)

// warehouseLocation指向托管数据库和表的默认位置
val warehouseLocation = new File("spark-warehouse").getAbsolutePath

val spark = SparkSession
  .builder()
  .appName("Spark Hive Example")
  .config("spark.sql.warehouse.dir", warehouseLocation)
  .enableHiveSupport()
  .getOrCreate()

import spark.implicits._
import spark.sql

sql("CREATE TABLE IF NOT EXISTS src (key INT, value STRING) USING hive")
sql("LOAD DATA LOCAL INPATH 'examples/src/main/resources/kv1.txt' INTO TABLE src")

// 查询用HiveQL表示
sql("SELECT * FROM src").show()
// +---+-------+
// |key|  value|
// +---+-------+
// |238|val_238|
// | 86| val_86|
// |311|val_311|
// ...

// 还支持聚合查询
sql("SELECT COUNT(*) FROM src").show()
// +--------+
// |count(1)|
// +--------+
// |    500 |
// +--------+

// SQL查询的结果本身就是DataFrames,并支持所有正常的函数。
val sqlDF = sql("SELECT key, value FROM src WHERE key < 10 ORDER BY key")

// DataFrames中的项是Row类型的,这允许您按顺序访问每一列。
val stringsDS = sqlDF.map {
  case Row(key: Int, value: String) => s"Key: $key, Value: $value"
}
stringsDS.show()
// +--------------------+
// |               value|
// +--------------------+
// |Key: 0, Value: val_0|
// |Key: 0, Value: val_0|
// |Key: 0, Value: val_0|
// ...

// 您还可以使用DataFrames在SparkSession中创建临时视图
val recordsDF = spark.createDataFrame((1 to 100).map(i => Record(i, s"val_$i")))
recordsDF.createOrReplaceTempView("records")

// 然后查询可以将DataFrame数据与存储在Hive中的数据连接起来。
sql("SELECT * FROM records r JOIN src s ON r.key = s.key").show()
// +---+------+---+------+
// |key| value|key| value|
// +---+------+---+------+
// |  2| val_2|  2| val_2|
// |  4| val_4|  4| val_4|
// |  5| val_5|  5| val_5|
// ...

// 创建一个Hive管理的Parquet表,使用HQL语法而不是Spark SQL本地语法
// `USING hive`
sql("CREATE TABLE hive_records(key int, value string) STORED AS PARQUET")
// 将DataFrame保存到Hive表中
val df = spark.table("src")
df.write.mode(SaveMode.Overwrite).saveAsTable("hive_records")
// 插入之后,Hive表中现在有数据了
sql("SELECT * FROM hive_records").show()
// +---+-------+
// |key|  value|
// +---+-------+
// |238|val_238|
// | 86| val_86|
// |311|val_311|
// ...

// 准备一个parquet数据目录
val dataDir = "/tmp/parquet_data"
spark.range(10).write.parquet(dataDir)
// 创建一个Hive 外部 Parquet 表
sql(s"CREATE EXTERNAL TABLE hive_ints(key int) STORED AS PARQUET LOCATION '$dataDir'")
// Hive外部表应该已经有数据了
sql("SELECT * FROM hive_ints").show()
// +---+
// |key|
// +---+
// |  0|
// |  1|
// |  2|
// ...

// 打开Hive动态分区的标志
spark.sqlContext.setConf("hive.exec.dynamic.partition", "true")
spark.sqlContext.setConf("hive.exec.dynamic.partition.mode", "nonstrict")
// 使用DataFrame API创建一个Hive分区表
df.write.partitionBy("key").format("hive").saveAsTable("hive_part_tbl")
// 分区列' key '将被移动到视图的末尾
sql("SELECT * FROM hive_part_tbl").show()
// +-------+---+
// |  value|key|
// +-------+---+
// |val_238|238|
// | val_86| 86|
// |val_311|311|
// ...

spark.stop()

指定Hive表的存储格式
在创建Hive表时,需要定义这个表应该如何从/向文件系统读/写数据,即“输入格式”和“输出格式”。你还需要定义这个表应该如何将数据反序列化为行,或将行序列化为数据,即“serde”。以下选项可用于指定存储格式(“serde”、“input format”、“output format”),例如:CREATE TABLE src(id int) USING hive OPTIONS(fileFormat ‘parquet’).默认情况下,我们将以纯文本的形式读取表文件。注意,在创建表时还不支持Hive存储处理程序,您可以使用Hive侧的存储处理程序创建表,并使用Spark SQL读取它。

属性名称 含义
fileFormat fileFormat是一种存储格式规范的包,包括“serde”、“input format”和“output format”。目前我们支持6种文件格式:“sequencefile”,“rcfile”,“orc”,“parquet”,“textfile”和“avro”。
inputFormat, outputFormat 这两个选项将对应的“InputFormat”和“OutputFormat”类的名称指定为字符串文本,例如: org.apache.hadoop.hive.ql.io.orc.OrcInputFormat.这两个选项必须成对出现,如果已经指定了“fileFormat”选项,则不能指定它们。
serde 这个选项指定一个serde类的名称。指定“fileFormat”选项时,如果给定的“fileFormat”已经包含serde的信息,则不要指定此选项。目前“sequencefile”、“textfile”和“rcfile”不包含serde信息,您可以在这三种文件格式中使用此选项。
fieldDelim, escapeDelim, collectionDelim, mapkeyDelim, lineDelim 这些选项只能用于“textfile”文件格式。它们定义如何将带分隔符的文件读入行。

使用选项定义的所有其他属性将被视为Hive serde属性。

与不同版本的Hive元数据交互
Spark SQL的Hive支持中最重要的部分之一是与Hive metastore的交互,这使得Spark SQL能够访问Hive表的元数据。从Spark 1.4.0开始,Spark SQL的一个二进制构建可以使用下面描述的配置来查询不同版本的Hive metastores。注意,独立于用于与metastore通信的Hive版本,内部Spark SQL将针对Hive 1.2.1编译,并将这些类用于内部执行(serdes、udf、udf等)。
配置信息请点击此处

(六).JDBC To Other Databases

Spark SQL还包括一个数据源,可以使用JDBC从其他数据库读取数据。与使用JdbcRDD相比,这种功能更可取。这是因为结果是以DataFrame的形式返回的,并且它们可以在Spark SQL中轻松处理或与其他数据源连接。JDBC数据源也更容易从Java或Python中使用,因为它不需要用户提供一个ClassTag。(请注意,这与Spark SQL JDBC服务器不同,后者允许其他应用程序使用Spark SQL运行查询)。
首先,你需要在spark类路径中包含特定数据库的JDBC驱动程序。例如,要从Spark Shell连接到postgres,需要运行以下命令:

bin/spark-shell --driver-class-path postgresql-9.4.1207.jar --jars postgresql-9.4.1207.jar

可以使用数据源API将远程数据库中的表加载为DataFrame或Spark SQL临时视图。用户可以在数据源选项中指定JDBC连接属性。用户和密码通常作为登录数据源的连接属性提供。除了连接属性,Spark还支持以下不区分大小写的选项:http://spark.apache.org/docs/latest/sql-data-sources-jdbc.html
Scala示例代码如下:

// 注意:JDBC加载和保存可以通过 load/save或JDBC方法来实现
// 从JDBC源加载数据
val jdbcDF = spark.read
  .format("jdbc")
  .option("url", "jdbc:postgresql:dbserver")
  .option("dbtable", "schema.tablename")
  .option("user", "username")
  .option("password", "password")
  .load()

val connectionProperties = new Properties()
connectionProperties.put("user", "username")
connectionProperties.put("password", "password")
val jdbcDF2 = spark.read
  .jdbc("jdbc:postgresql:dbserver", "schema.tablename", connectionProperties)
// 指定读取模式的自定义数据类型
connectionProperties.put("customSchema", "id DECIMAL(38, 0), name STRING")
val jdbcDF3 = spark.read
  .jdbc("jdbc:postgresql:dbserver", "schema.tablename", connectionProperties)

// 将数据保存到JDBC源
jdbcDF.write
  .format("jdbc")
  .option("url", "jdbc:postgresql:dbserver")
  .option("dbtable", "schema.tablename")
  .option("user", "username")
  .option("password", "password")
  .save()

jdbcDF2.write
  .jdbc("jdbc:postgresql:dbserver", "schema.tablename", connectionProperties)

// 在写入时指定创建表列数据类型
jdbcDF.write
  .option("createTableColumnTypes", "name CHAR(64), comments VARCHAR(1024)")
  .jdbc("jdbc:postgresql:dbserver", "schema.tablename", connectionProperties)

(七).Avro Files

自从Spark 2.4发布以来,Spark SQL为Apache Avro数据的读写提供了内置支持。
部署(Deploying)
spark-avro模块是外部的,默认情况下不包括在spark-submit或spark-shell中。
与任何Spark应用程序一样,Spark -submit用于启动应用程序。spark-avro_2.11及其依赖项可以使用——包直接添加到spark-submit中,例如:

./bin/spark-submit --packages org.apache.spark:spark-avro_2.11:2.4.0 ...

对于在spark-shell上进行试验,您还可以使用——包直接添加org.apache.spark:spark-avro_2.11及其依赖项:

./bin/spark-shell --packages org.apache.spark:spark-avro_2.11:2.4.0 ...

有关使用外部依赖项提交应用程序的详细信息,请参阅应用程序提交指南

Load and Save Functions
由于spark-avro模块是外部的,所以DataFrameReader或DataFrameWriter中没有.avro API。
要以Avro格式加载/保存数据,需要将数据源选项格式指定为Avro(或org.apache.spark.sql.avro)。
Scala代码:

val usersDF = spark.read.format("avro").load("examples/src/main/resources/users.avro")
usersDF.select("name", "favorite_color").write.format("avro").save("namesAndFavColors.avro")

to_avro() and from_avro()
Avro包提供函数to_avro以Avro格式将列编码为二进制,from_avro()将Avro二进制数据解码为列。这两个函数都将一列转换为另一列,输入/输出SQL数据类型可以是复杂类型或原语类型。
当从Kafka这样的流源读取或写入数据时,使用Avro记录作为列非常有用。每个Kafka键值记录都将添加一些元数据,例如Kafka中的摄入时间戳、Kafka中的偏移量等等。

如果包含数据的“value”字段位于Avro中,那么可以使用from_avro()来提取数据、丰富数据、清理数据,然后再将其向下推到Kafka或将其写入文件。to_avro()可用于将结构体转换为Avro记录。当您在向Kafka写入数据时希望将多个列重新编码为单个列时,此方法尤其有用。这两个函数目前只在Scala和Java中可用。
Scala代码:

import org.apache.spark.sql.avro._

// ' from_avro '需要JSON字符串格式的Avro模式。
val jsonFormatSchema = new String(Files.readAllBytes(Paths.get("./examples/src/main/resources/user.avsc")))

val df = spark
  .readStream
  .format("kafka")
  .option("kafka.bootstrap.servers", "host1:port1,host2:port2")
  .option("subscribe", "topic1")
  .load()

// 1. 将Avro数据解码成结构;
// 2. 按列过滤' favorite_color ';
// 3. 将列“name”编码为Avro格式。
val output = df
  .select(from_avro('value, jsonFormatSchema) as 'user)
  .where("user.favorite_color == \"red\"")
  .select(to_avro($"user.name") as 'value)

val query = output
  .writeStream
  .format("kafka")
  .option("kafka.bootstrap.servers", "host1:port1,host2:port2")
  .option("topic", "topic2")
  .start()

数据源选择
可以使用DataFrameReader或DataFrameWriter上的.option方法设置Avro的数据源选项。

属性名称 默认值 含义 作用范围
avroSchema None 用户以JSON格式提供的可选Avro模式。记录字段的日期类型和命名应该匹配输入的Avro数据或Catalyst数据,否则读写操作将失败。 读和写
recordName topLevelRecord 写入结果中的顶级记录名,这在Avro规范中是必需的
recordNamespace “” 在写结果中记录命名空间
ignoreExtension true 该选项控制在read中忽略没有.avro扩展名的文件。
如果启用该选项,则加载所有文件(包括和不包括.avro扩展名)。
compression snappy 压缩选项允许指定写入中使用的压缩编解码器。目前支持的编解码器有uncompression, snappy, deflate, bzip2和xz。如果未设置此选项,则将考虑配置spark.sq .av .compression.codec配置

配置(Configuration)
可以使用SparkSession上的setConf方法或使用SQL运行SET key=value命令来配置Avro。

属性名称 默认值 含义
spark.sql.legacy.replaceDatabricksSparkAvro.enabled true 如果设置为true,则数据源提供程序com. database .spark.avro映射到内置但外部的avro数据源模块,以实现向后兼容性。
spark.sql.avro.compression.codec snappy 用于编写AVRO文件的压缩编解码器。支持编解码器:uncompression, deflate, snappy, bzip2和xz。默认的编解码器很流行
spark.sql.avro.deflate.level -1 用于编写AVRO文件的deflate编解码器的压缩级别。有效值必须在1到9之间(包括-1)。默认值是-1,对应于当前实现中的6级

相互间支持的数据类型请参见官网.

(八).故障排除(Troubleshooting)

JDBC驱动程序类必须对客户机会话和所有执行程序上的原始类装入器可见。这是因为Java的DriverManager类做了一个安全检查,当打开一个连接时,它会忽略所有原始类装入器不可见的驱动程序。一种方便的方法是修改所有工作节点上的compute_classpath.sh,以包含驱动程序jar。

有些数据库,如H2,将所有名称转换为大写。您需要使用大写字母来引用Spark SQL中的这些名称。

用户可以在数据源选项中指定特定于供应商的JDBC连接属性来进行特殊处理。例如:spark.read.format(“jdbc”).option(“url”, oracleJdbcUrl).option(“oracle.jdbc.mapDateToTimestamp”, “false”). oracle.jdbc.mapDateToTimestamp默认值为true,用户通常需要禁用此标志,以避免将Oracle日期解析为timestamp。

猜你喜欢

转载自blog.csdn.net/Thomson617/article/details/87876298
今日推荐