Spark SQL, DataFrames and Datasets 指导

概述

Spark SQL是一个用于结构化数据处理的Spark模块。与基本的Spark RDD API不同,Spark SQL的接口提供了更多关于数据结构和正在执行的计算信息。在内部,Spark SQL使用这些额外的信息来执行额外的优化。有几种与Spark SQL交互的方法,包括SQL和DataSet API。在计算结果时,使用相同的执行引擎,而不依赖用于表示计算的API/语言。这种统一意味着开发人员可以轻松地在不同的API之间来回切换,基于API提供了表达给定转换的最自然的方式。并且Spark可以在spark-shell、 pyspark shell 和 sparkR shell中运行。

SQL

Spark SQL的一种用法是执行SQL查询。Spark SQL还可以用于读取现有hive安装中的数据。在另一种编程语言中运行SQL时,结果将作为DataSet/DataFrame返回。你还可以使用命令行或通过jdbc/odbc与SQL接口进行交互。

Datasets和DataFrames

数据集是数据的分布式集合。DataSet是在Spark1.6中添加的一个新接口,它提供了RDDs(强大的类型,能够使用强大的lambda函数)的优点,以及SparkSQL的优化执行引擎的优点。可以从JVM对象构造数据集,然后使用函数转换(map、PlatMap、Filter等)对数据集进行操作。DataSet API可在Scala和Java中使用。Python不支持DataSet API。但是由于Python的动态特性,DataSet API的许多优点已经可用(即您可以按名称自然地访问一个行的字段row.ColumnName)。R的情况类似。
DataFrame是组织为命名列的数据集。它在概念上相当于关系数据库中的表或R/Python中的数据框架,但在幕后有更丰富的优化。DataFrames可以从多种来源构建,例如:结构化数据文件、Hive中的表、外部数据库或现有的RDD。DataFrameAPI可用Scala、Java、Python和R.在Scala和Java中,DataFrame由行数据集表示。在ScalaAPI中,DataFrame只是DataSet[Row]的一个类型别名。而在JavaAPI中,用户需要使用DataSet来表示DataFrame。

起始点:SparkSession

Spark中所有功能的入口点是SparkSession类。要创建基本的SparkSession,只需使用SparkSession.builder():

import org.apache.spark.sql.SparkSession

val spark = SparkSession
  .builder()
  .appName("Spark SQL basic example")
  .config("spark.some.config.option", "some-value")
  .getOrCreate()

// For implicit conversions like converting RDDs to DataFrames
import spark.implicits._

创建DataFrames

使用SparkSession,应用程序可以从现有的RDD、Hive表或Spark数据源创建DataFrames。例如,以下内容根据JSON文件的内容创建DataFrame:

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

// Displays the content of the DataFrame to stdout
df.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+

非类型化Dataset操作

DataFrames为Scala、Java、Python和R中的结构化数据操作提供了一种特定于域的语言,如上所述,在Spark2.0中,DataFrames只是Scala和JavaAPI中的行数据集。与强类型化Scala/Java数据集相比,这些操作也被称为“非类型化转换”。这里我们包括一些使用数据集进行结构化数据处理的基本示例:

// This import is needed to use the $-notation
import spark.implicits._
// Print the schema in a tree format
df.printSchema()
// root
// |-- age: long (nullable = true)
// |-- name: string (nullable = true)

// Select only the "name" column
df.select("name").show()
// +-------+
// |   name|
// +-------+
// |Michael|
// |   Andy|
// | Justin|
// +-------+

// Select everybody, but increment the age by 1
df.select($"name", $"age" + 1).show()
// +-------+---------+
// |   name|(age + 1)|
// +-------+---------+
// |Michael|     null|
// |   Andy|       31|
// | Justin|       20|
// +-------+---------+

// Select people older than 21
df.filter($"age" > 21).show()
// +---+----+
// |age|name|
// +---+----+
// | 30|Andy|
// +---+----+

// Count people by age
df.groupBy("age").count().show()
// +----+-----+
// | age|count|
// +----+-----+
// |  19|    1|
// |null|    1|
// |  30|    1|
// +----+-----+

以编程方式运行SQL查询

SparkSession上的SQL函数允许应用程序以编程方式运行SQL查询,并以DataFrame的形式返回结果。

// Register the DataFrame as a SQL temporary view
df.createOrReplaceTempView("people")

val sqlDF = spark.sql("SELECT * FROM people")
sqlDF.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+

Global Temporary View

SparkSQL中的临时视图是会话范围的,如果创建它的会话终止,它将消失。如果希望在所有会话之间共享一个临时视图,并在星火应用程序终止前保持活动,则可以创建一个全局临时视图。全局临时视图绑定到一个系统保存的数据库globaltemp,我们必须使用限定名来引用它,例如:SELECT * FROM global_temp.view1

// Register the DataFrame as a global temporary view
df.createGlobalTempView("people")

// Global temporary view is tied to a system preserved database `global_temp`
spark.sql("SELECT * FROM global_temp.people").show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+

// Global temporary view is cross-session
spark.newSession().sql("SELECT * FROM global_temp.people").show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+

Creating Datasets

然而,数据集类似于RDDs,而不是使用Java序列化或KRIO,而是使用专门的编码器来序列化对象,以便在网络上进行处理或传输。虽然编码器和标准序列化都负责将对象转换为字节,但编码器是动态生成的代码,使用允许SPark执行许多操作的格式,如筛选、排序和散列,而不将字节反序列化为对象。

case class Person(name: String, age: Long)

// Encoders are created for case classes
val caseClassDS = Seq(Person("Andy", 32)).toDS()
caseClassDS.show()
// +----+---+
// |name|age|
// +----+---+
// |Andy| 32|
// +----+---+

// Encoders for most common types are automatically provided by importing spark.implicits._
val primitiveDS = Seq(1, 2, 3).toDS()
primitiveDS.map(_ + 1).collect() // Returns: Array(2, 3, 4)

// DataFrames can be converted to a Dataset by providing a class. Mapping will be done by name
val path = "examples/src/main/resources/people.json"
val peopleDS = spark.read.json(path).as[Person]
peopleDS.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+

与RDDs交互

SmarkSQL支持将现有RDDs转换为数据集的两种不同方法。第一种方法使用反射来推断包含特定对象类型的RDD的架构。这种基于反射的方法导致更简洁的代码,并且在编写SPark应用程序时,您已经知道了模式。创建数据集的第二种方法是通过编程接口,该接口允许您构造模式,然后将其应用于现有的RDD。虽然此方法更详细,但它允许在运行时才知道列及其类型时构造数据集。

利用反射推断模式(Inferring the Schema Using Reflection)

SparkSQL的Scala接口支持将包含案例类的RDD自动转换为DataFrame。case类定义表的架构。CASE类的参数名称使用反射读取,并成为列的名称。CASE类也可以嵌套或包含复杂类型,如Seq或数组。可以将此RDD隐式转换为DataFrame,然后将其注册为表。表可以在后续SQL语句中使用。

// For implicit conversions from RDDs to DataFrames
import spark.implicits._

// Create an RDD of Person objects from a text file, convert it to a Dataframe
val peopleDF = spark.sparkContext
  .textFile("examples/src/main/resources/people.txt")
  .map(_.split(","))
  .map(attributes => Person(attributes(0), attributes(1).trim.toInt))
  .toDF()
// Register the DataFrame as a temporary view
peopleDF.createOrReplaceTempView("people")

// SQL statements can be run by using the sql methods provided by Spark
val teenagersDF = spark.sql("SELECT name, age FROM people WHERE age BETWEEN 13 AND 19")

// The columns of a row in the result can be accessed by field index
teenagersDF.map(teenager => "Name: " + teenager(0)).show()
// +------------+
// |       value|
// +------------+
// |Name: Justin|
// +------------+

// or by field name
teenagersDF.map(teenager => "Name: " + teenager.getAs[String]("name")).show()
// +------------+
// |       value|
// +------------+
// |Name: Justin|
// +------------+

// No pre-defined encoders for Dataset[Map[K,V]], define explicitly
implicit val mapEncoder = org.apache.spark.sql.Encoders.kryo[Map[String, Any]]
// Primitive types and case classes can be also defined as
// implicit val stringIntMapEncoder: Encoder[Map[String, Any]] = ExpressionEncoder()

// row.getValuesMap[T] retrieves multiple columns at once into a Map[String, T]
teenagersDF.map(teenager => teenager.getValuesMap[Any](List("name", "age"))).collect()
// Array(Map("name" -> "Justin", "age" -> 19))

以编程方式指定架构(Programmatically Specifying the Schema)

当无法提前定义案例类(例如,记录的结构被编码为字符串,或者文本数据集将被解析,字段将针对不同的用户进行不同的投影)时,可以通过三个步骤以编程方式创建DataFrame。
从原始的RDD中创建一个RDD;
创建由StructType表示的模式,该架构与步骤1中创建的RDD中的行结构相匹配;通过SparkSession提供的createDataFrame方法将架构应用于行的RDD。

import org.apache.spark.sql.types._

// Create an RDD
val peopleRDD = spark.sparkContext.textFile("examples/src/main/resources/people.txt")

// The schema is encoded in a string
val schemaString = "name age"

// Generate the schema based on the string of schema
val fields = schemaString.split(" ")
  .map(fieldName => StructField(fieldName, StringType, nullable = true))
val schema = StructType(fields)

// Convert records of the RDD (people) to Rows
val rowRDD = peopleRDD
  .map(_.split(","))
  .map(attributes => Row(attributes(0), attributes(1).trim))

// Apply the schema to the RDD
val peopleDF = spark.createDataFrame(rowRDD, schema)

// Creates a temporary view using the DataFrame
peopleDF.createOrReplaceTempView("people")

// SQL can be run over a temporary view created using DataFrames
val results = spark.sql("SELECT name FROM people")

// The results of SQL queries are DataFrames and support all the normal RDD operations
// The columns of a row in the result can be accessed by field index or by field name
results.map(attributes => "Name: " + attributes(0)).show()
// +-------------+
// |        value|
// +-------------+
// |Name: Michael|
// |   Name: Andy|
// | Name: Justin|
// +-------------+

Aggregations

内置数据文件功能提供常见的聚合,如计数()、计数()、平均值()、最大值()、最小值()等。虽然这些功能是为数据文件设计的,但在Scala和Java中,SparkSQL还为其中的一些提供了类型安全的版本,以便与强类型化的数据集一起工作。此外,用户不限于预定的聚合函数并且可以创建它们自己的聚合函数。

Untyped User-Defined Aggregate Functions

import org.apache.spark.sql.{Row, SparkSession}
import org.apache.spark.sql.expressions.MutableAggregationBuffer
import org.apache.spark.sql.expressions.UserDefinedAggregateFunction
import org.apache.spark.sql.types._

object MyAverage extends UserDefinedAggregateFunction {
  // Data types of input arguments of this aggregate function
  def inputSchema: StructType = StructType(StructField("inputColumn", LongType) :: Nil)
  // Data types of values in the aggregation buffer
  def bufferSchema: StructType = {
    StructType(StructField("sum", LongType) :: StructField("count", LongType) :: Nil)
  }
  // The data type of the returned value
  def dataType: DataType = DoubleType
  // Whether this function always returns the same output on the identical input
  def deterministic: Boolean = true
  // Initializes the given aggregation buffer. The buffer itself is a `Row` that in addition to
  // standard methods like retrieving a value at an index (e.g., get(), getBoolean()), provides
  // the opportunity to update its values. Note that arrays and maps inside the buffer are still
  // immutable.
  def initialize(buffer: MutableAggregationBuffer): Unit = {
    buffer(0) = 0L
    buffer(1) = 0L
  }
  // Updates the given aggregation buffer `buffer` with new input data from `input`
  def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
    if (!input.isNullAt(0)) {
      buffer(0) = buffer.getLong(0) + input.getLong(0)
      buffer(1) = buffer.getLong(1) + 1
    }
  }
  // Merges two aggregation buffers and stores the updated buffer values back to `buffer1`
  def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
    buffer1(0) = buffer1.getLong(0) + buffer2.getLong(0)
    buffer1(1) = buffer1.getLong(1) + buffer2.getLong(1)
  }
  // Calculates the final result
  def evaluate(buffer: Row): Double = buffer.getLong(0).toDouble / buffer.getLong(1)
}

// Register the function to access it
spark.udf.register("myAverage", MyAverage)

val df = spark.read.json("examples/src/main/resources/employees.json")
df.createOrReplaceTempView("employees")
df.show()
// +-------+------+
// |   name|salary|
// +-------+------+
// |Michael|  3000|
// |   Andy|  4500|
// | Justin|  3500|
// |  Berta|  4000|
// +-------+------+

val result = spark.sql("SELECT myAverage(salary) as average_salary FROM employees")
result.show()
// +--------------+
// |average_salary|
// +--------------+
// |        3750.0|
// +--------------+

Type-Safe User-Defined Aggregate Functions

import org.apache.spark.sql.{Encoder, Encoders, SparkSession}
import org.apache.spark.sql.expressions.Aggregator

case class Employee(name: String, salary: Long)
case class Average(var sum: Long, var count: Long)

object MyAverage extends Aggregator[Employee, Average, Double] {
  // A zero value for this aggregation. Should satisfy the property that any b + zero = b
  def zero: Average = Average(0L, 0L)
  // Combine two values to produce a new value. For performance, the function may modify `buffer`
  // and return it instead of constructing a new object
  def reduce(buffer: Average, employee: Employee): Average = {
    buffer.sum += employee.salary
    buffer.count += 1
    buffer
  }
  // Merge two intermediate values
  def merge(b1: Average, b2: Average): Average = {
    b1.sum += b2.sum
    b1.count += b2.count
    b1
  }
  // Transform the output of the reduction
  def finish(reduction: Average): Double = reduction.sum.toDouble / reduction.count
  // Specifies the Encoder for the intermediate value type
  def bufferEncoder: Encoder[Average] = Encoders.product
  // Specifies the Encoder for the final output value type
  def outputEncoder: Encoder[Double] = Encoders.scalaDouble
}

val ds = spark.read.json("examples/src/main/resources/employees.json").as[Employee]
ds.show()
// +-------+------+
// |   name|salary|
// +-------+------+
// |Michael|  3000|
// |   Andy|  4500|
// | Justin|  3500|
// |  Berta|  4000|
// +-------+------+

// Convert the function to a `TypedColumn` and give it a name
val averageSalary = MyAverage.toColumn.name("average_salary")
val result = ds.select(averageSalary)
result.show()
// +--------------+
// |average_salary|
// +--------------+
// |        3750.0|
// +--------------+

Generic Load/Save Functions

在最简单的形式中,所有操作都将使用默认数据源(parquet,除非由scapk.sql.source.默认配置)。

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

Manually Specifying Options

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

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

要加载CSV文件,可以使用:

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

在写操作期间也会使用额外的选项。例如,您可以为ORC数据源控制bloom过滤器和字典编码。下面的ORC示例将在喜爱的颜色上创建bloom过滤器,并对名称和喜爱的颜色使用字典编码。对于Parquet,也有parquet.enable.字典。要了解更多关于ORC/Parquet选项的详细信息,请访问官方的ApacheORC/Parquet网站。

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

Run SQL on files directly

与使用ReadAPI将文件加载到DataFrame并对其进行查询不同,您还可以直接使用SQL查询该文件。

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

Save Modes

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

Scala/JavaAny LanguageMeaning
SaveMode.ErrorIfExists (default)“error” or “errorifexists” (default)当将DataFrame保存到数据源时,如果数据已经存在,则会引发异常。
SaveMode.Append“append”当将DataFrame保存到数据源时,如果数据/表已经存在,则DataFrame的内容将被附加到现有数据中。
SaveMode.Overwrite“overwrite”覆盖模式意味着当将DataFrame保存到数据源时,如果数据/表已经存在,则现有数据将被DataFrame的内容覆盖。
SaveMode.Ignore“ignore”忽略模式意味着当将DataFrame保存到数据源时,如果数据已经存在,则保存操作将不会保存DataFrame的内容,也不会更改现有数据。如果SQL中不存在CREATETABLE,则类似于CREATETABLE。

Saving to Persistent Tables

还可以使用SaveAsTable命令将DataFrames保存为持久化表。注意,现有的Hive部署并不是使用此特性所必需的。Spark将为你创建一个默认的本地Hive metastore(使用Derby)。与createOrReplaceTempView命令不同,SaveAsTable将实现DataFrame的内容,并创建一个指向Hive转移区中数据的指针。即使在Spark重新启动之后,持久化表仍然存在,只要你保持与相同的metastore的连接。可以通过使用表名调用SparkSession上的table方法来创建持久表的DataFrame。
用于基于文件的数据源,例如文本、parquet、json等。您可以通过PATH选项指定自定义表路径,例如df.write.opt(“path”、“/SomePATH”)、.SaveAsTable(“t”)。当表被删除时,自定义表路径将不会被移除,并且表数据仍然存在。如果未指定自定义表路径,SPark将数据写入仓库目录下的默认表路径。当表被删除时,默认的表路径也将被删除。
从Spark2.1开始,持久性数据源表的每个分区元数据都存储在Hive metastore中。这带来了几个好处:
因为metastore只能返回查询所需的分区,因此不再需要在第一个查询中发现表中的所有分区;
Hive DDLs如ALTER TABLE PARTITION … SET LOCATION 使用Datasource API创建的表是可得到的。
注意,在创建外部数据源表(那些具有PATH选项的表)时,默认情况下不会收集分区信息。要同步metastore中的分区信息,可以调用MSCK REPAIR TABLE。

Bucketing, Sorting and Partitioning

对于基于文件的数据源,还可以对输出进行分类和排序.桶形和排序只适用于持久化表:

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

当使用DataSet API时,分区可以同时用于保存和保存表。

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

可以对单个表同时使用分区和存储:

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

Parquet Files

Parquet是一种由许多其他数据处理系统支持的柱状格式。SPARK SQL为自动保留原始数据架构的Parquet文件提供了读写支持。在编写Parquet文件时,由于兼容性原因,所有列都会自动转换为可空。

Loading Data Programmatically

// Encoders for most common types are automatically provided by importing spark.implicits._
import spark.implicits._

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

// DataFrames can be saved as Parquet files, maintaining the schema information
peopleDF.write.parquet("people.parquet")

// Read in the parquet file created above
// Parquet files are self-describing so the schema is preserved
// The result of loading a Parquet file is also a DataFrame
val parquetFileDF = spark.read.parquet("people.parquet")

// Parquet files can also be used to create a temporary view and then used in SQL statements
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|
// +------------+

Partition Discovery

表分区是像Hive这样的系统中常用的优化方法。在分区表中,数据通常存储在不同的目录中,分区列值编码在每个分区目录的路径中。所有内置的文件源(包括Text/CSV/JSON/ORC/Parquet)都能够自动发现和推断分区信息。
通过将path/to/table传递给SparkSession.read.parquet或SparkSession.read.load,SparkSQL将自动从路径中提取分区信息。
注意,分区列的数据类型是自动推断的。目前,支持数字数据类型、日期、时间戳和字符串类型。有时,用户可能不希望自动推断分区列的数据类型。对于这些用例,自动类型推断可以由spark.sql.sources.partitionColumnTypeInference.enabled,配置,默认为true。当类型推断被禁用时,将对分区列使用字符串类型。从Spark1.6.0开始,分区发现默认只查找给定路径下的分区。对于上面的示例,如果用户将路径/to/table/sex=男性传递给SparkSession.read.parquet或SparkSession.read.load,则性别将不被视为分区列。如果用户需要指定分区发现应该从哪个基本路径开始,他们可以在数据源选项中设置basepath。例如,当PATH/to/TABLE/DEX=MAY是数据的路径,用户将basepath设置为path/to/table/时,性别将是分区列。

Schema Merging

与协议缓冲区、Avro和Th深层一样,Parquet也支持模式演化。用户可以从一个简单的模式开始,并在需要时逐步向模式中添加更多的列。这样,用户最终可能会得到多个Parquet文件,这些文件具有不同但相互兼容的模式。Parquet数据源现在能够自动检测这种情况并合并所有这些文件的架构。由于模式合并是一种相对昂贵的操作,并且在大多数情况下不是必需的,因此我们从1.5.0开始默认关闭它。您可以在读取Parquet文件时(如下面的示例所示)将数据源选项mergeSchema设置为true,或者将全局SQL选项设置为scapk.sql.parquet.mergeSchema为true。

// This is used to implicitly convert an RDD to a DataFrame.
import spark.implicits._

// Create a simple DataFrame, store into a partition directory
val squaresDF = spark.sparkContext.makeRDD(1 to 5).map(i => (i, i * i)).toDF("value", "square")
squaresDF.write.parquet("data/test_table/key=1")

// Create another DataFrame in a new partition directory,
// adding a new column and dropping an existing column
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")

// Read the partitioned table
val mergedDF = spark.read.option("mergeSchema", "true").parquet("data/test_table")
mergedDF.printSchema()

// The final schema consists of all 3 columns in the Parquet files together
// with the partitioning column appeared in the partition directory paths
// root
//  |-- value: int (nullable = true)
//  |-- square: int (nullable = true)
//  |-- cube: int (nullable = true)
//  |-- key: int (nullable = true)

Hive metastore Parquet table conversion

当读取和写入Hive metastore Parquet表时,SparkSQL将尝试使用自己的Parquet支持,而不是HiveSerDe,以获得更好的性能。这种行为是由scapk.sql.hive.ConvertMetastoreParquet配置控制的,默认情况下是打开的。

Hive/Parquet Schema协调

从表模式处理的角度来看,Hive和Parquet有两个关键的区别。单元格不区分大小写,而Parquet不是Hive认为所有列都为空,而Parquet中的空性由于这个原因很重要,因此在将Hive转移Parquet表转换为SPark SQL Parquet表时,必须将Hive转移模式与Parquet模式进行协调。协调规则是:在两个模式中具有相同名称的字段必须具有相同的数据类型,而不管是否为空。协调字段应该具有Parquet侧的数据类型,这样才能尊重空值。调和的模式恰恰包含在Hive metastore模式中定义的字段。只出现在Parquet架构中的任何字段都会在协调架构中删除。任何仅出现在Hive metastore模式中的字段都将作为可空字段添加到协调架构中。

Metadata Refreshing

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

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

Configuration

Parquet的配置可以在SparkSession上使用setConf方法完成,也可以通过使用SQL运行set key=value命令来完成。

Property NameDefaultMeaning
spark.sql.parquet.binaryAsStringfalse其他一些生成Parquet的系统,特别是Impala、Hive和SparkSQL的旧版本,在编写Parquet模式时不区分二进制数据和字符串。此标志告诉SparkSQL将二进制数据解释为字符串,以提供与这些系统的兼容性。
spark.sql.parquet.int96AsTimestamptrue一些生产Parquet的系统,特别是Impala和Hive,将时间戳存储在INT 96中。此标志告诉SparkSQL将INT 96数据解释为时间戳,以提供与这些系统的兼容性。
spark.sql.parquet.compression.codecsnappy设置写入Parquet文件时使用的压缩编解码器。如果在表特定的选项/属性中指定了“压缩”或“parquet.压缩”,则优先级为“压缩”、“parquet.压缩”、“scapk.sql.parquet.pression.codect”。可接受的值包括:无,未压缩,snappy,gzip,lzo,brotli,lz 4,zstd。请注意,`zstd‘要求在Hadoop 2.9.0之前安装“ZStandardCodec”,“brotli”要求安装“BrotliCodec”。
spark.sql.parquet.filterPushdowntrue当设置为true时,启用Parquet过滤器下推优化。
spark.sql.hive.convertMetastoreParquettrue当设置为false时,SparkSQL将使用HiveSerDe作为parquet表,而不是内置的支持。
spark.sql.parquet.mergeSchemafalse如果为true,Parquet数据源将合并从所有数据文件收集的架构,否则将从摘要文件中选择架构,如果没有摘要文件,则从随机数据文件中选择架构。
spark.sql.optimizer.metadataOnlytrue如果为true,则启用仅使用元数据的查询优化,该优化使用表的元数据生成分区列,而不是表扫描。当扫描的所有列都是分区列且查询具有满足不同语义的聚合运算符时,它就会应用。
spark.sql.parquet.writeLegacyFormatfalse如果为真,数据将以Spark1.4和更早版本的方式编写。例如,十进制值将以ApacheParquet的固定长度字节数组格式编写,其他系统(如ApacheHive和ApacheImpala)使用这种格式。如果错误,将使用Parquet中较新的格式。例如,小数将以基于int的格式编写.如果Parquet输出打算用于不支持这种更新格式的系统,则设置为true。

ORC Files

从Spark2.3开始,SPark支持一个矢量化的ORC读取器,为ORC文件提供了一个新的ORC文件格式。为此,新添加了以下配置。矢量化读取器用于本机ORC表(例如,使用ORC子句创建的表),当scamk.sql.orc.impl设置为本机,并将spack.sql.orc.enableVectorizedReader设置为true时。对于Hive ORC serde表(例如,使用hive选项的子句创建的表(fileFormat‘ORC’),当scapk.sql.hive.ConvertMetastoreOrc也被设置为true时,将使用矢量化读取器。

Property NameDefaultMeaning
spark.sql.orc.implnativeORC实现的名称。它可以是一个土生土长的Hive。本机是指构建在ApacheORC 1.4之上的本机ORC支持。“Hive”是指Hive1.2.1中的ORC图书馆。
spark.sql.orc.enableVectorizedReadertrue在本机实现中启用矢量化orc解码。如果为false,则在本机实现中使用新的非向量化ORC读取器.对于Hive实现,这将被忽略。

JSON Files

SparkSQL可以自动推断JSON数据集的架构,并将其加载为DataSet[Row]。这种转换可以在DataSet[String]或JSON文件上使用SparkSession.read.json()完成。
注意,作为json文件提供的文件不是典型的JSON文件。每一行必须包含一个独立的、有效的JSON对象。

// Primitive types (Int, String, etc) and Product types (case classes) encoders are
// supported by importing this when creating a Dataset.
import spark.implicits._

// A JSON dataset is pointed to by path.
// The path can be either a single text file or a directory storing text files
val path = "examples/src/main/resources/people.json"
val peopleDF = spark.read.json(path)

// The inferred schema can be visualized using the printSchema() method
peopleDF.printSchema()
// root
//  |-- age: long (nullable = true)
//  |-- name: string (nullable = true)

// Creates a temporary view using the DataFrame
peopleDF.createOrReplaceTempView("people")

// SQL statements can be run by using the sql methods provided by spark
val teenagerNamesDF = spark.sql("SELECT name FROM people WHERE age BETWEEN 13 AND 19")
teenagerNamesDF.show()
// +------+
// |  name|
// +------+
// |Justin|
// +------+

// Alternatively, a DataFrame can be created for a JSON dataset represented by
// a Dataset[String] storing one JSON object per 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

SmarkSQL还支持读取和写入存储在ApacheHive中的数据。但是,由于Hive有大量的依赖项,这些依赖项不包含在默认的SPark发行版中。如果在类路径上可以找到Hive依赖项,SPark将自动加载它们。请注意,这些Hive依赖项也必须存在于所有工作节点上,因为它们需要访问Hive序列化和反序列化库(SerDes),以便访问存储在Hive中的数据。
配置Hive的方法是将hive-site.xml、core-site.xml(用于安全配置)和hdfs-site.xml(用于HDFS配置)文件放在conf/中。
在使用Hive时,必须使用Hive支持实例化SparkSession,包括连接到持久的Hive metastore、支持Hive SERDES和Hive用户定义的函数。没有现有Hive部署的用户仍然可以启用Hive支持。当没有由hive-site.xml配置时,上下文将在当前目录中自动创建metastore_db,并创建一个由scapk.sql.warehouse.dir配置的目录,该目录默认为启动SPark应用程序的当前目录中的目录spark-warehouse。请注意,hive-site.xml中的hive.asionore.warehouse.dir属性在Spark2.0.0之后就不再受欢迎了。相反,使用scapk.sql.warehouse.dir指定仓库中数据库的默认位置。您可能需要将写权限授予启动SPark应用程序的用户。

import java.io.File

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

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

// warehouseLocation points to the default location for managed databases and tables
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")

// Queries are expressed in HiveQL
sql("SELECT * FROM src").show()
// +---+-------+
// |key|  value|
// +---+-------+
// |238|val_238|
// | 86| val_86|
// |311|val_311|
// ...

// Aggregation queries are also supported.
sql("SELECT COUNT(*) FROM src").show()
// +--------+
// |count(1)|
// +--------+
// |    500 |
// +--------+

// The results of SQL queries are themselves DataFrames and support all normal functions.
val sqlDF = sql("SELECT key, value FROM src WHERE key < 10 ORDER BY key")

// The items in DataFrames are of type Row, which allows you to access each column by ordinal.
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|
// ...

// You can also use DataFrames to create temporary views within a SparkSession.
val recordsDF = spark.createDataFrame((1 to 100).map(i => Record(i, s"val_$i")))
recordsDF.createOrReplaceTempView("records")

// Queries can then join DataFrame data with data stored in 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|
// ...

// Create a Hive managed Parquet table, with HQL syntax instead of the Spark SQL native syntax
// `USING hive`
sql("CREATE TABLE hive_records(key int, value string) STORED AS PARQUET")
// Save DataFrame to the Hive managed table
val df = spark.table("src")
df.write.mode(SaveMode.Overwrite).saveAsTable("hive_records")
// After insertion, the Hive managed table has data now
sql("SELECT * FROM hive_records").show()
// +---+-------+
// |key|  value|
// +---+-------+
// |238|val_238|
// | 86| val_86|
// |311|val_311|
// ...

// Prepare a Parquet data directory
val dataDir = "/tmp/parquet_data"
spark.range(10).write.parquet(dataDir)
// Create a Hive external Parquet table
sql(s"CREATE EXTERNAL TABLE hive_ints(key int) STORED AS PARQUET LOCATION '$dataDir'")
// The Hive external table should already have data
sql("SELECT * FROM hive_ints").show()
// +---+
// |key|
// +---+
// |  0|
// |  1|
// |  2|
// ...

// Turn on flag for Hive Dynamic Partitioning
spark.sqlContext.setConf("hive.exec.dynamic.partition", "true")
spark.sqlContext.setConf("hive.exec.dynamic.partition.mode", "nonstrict")
// Create a Hive partitioned table using DataFrame API
df.write.partitionBy("key").format("hive").saveAsTable("hive_part_tbl")
// Partitioned column `key` will be moved to the end of the schema.
sql("SELECT * FROM hive_part_tbl").show()
// +-------+---+
// |  value|key|
// +-------+---+
// |val_238|238|
// | val_86| 86|
// |val_311|311|
// ...

spark.stop()

指定Hive表的存储格式

创建Hive表时,需要定义表应该如何从文件系统读取/写入数据,即“输入格式”和“输出格式”。您还需要定义此表应如何将数据反序列化为行,或将行序列化为数据,即“serde”。以下选项可用于指定存储格式(“serde”、“输入格式”、“输出格式”),例如使用hive选项(fileFormat‘parquet’)创建表src(Id Int)。默认情况下,我们将以纯文本形式读取表文件。注意,在创建表时还不支持Hive存储处理程序,您可以使用Hive端的存储处理程序创建一个表,并使用SparkSQL读取它。

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

与不同版本的Hive Metastore交互

SparkSQL的Hive支持的最重要部分之一是与Hive转移的交互,这使SparkSQL能够访问Hive表的元数据。从Spark1.4.0开始,可以使用SparkSQL的单个二进制构建来查询不同版本的Hive转移,使用下面描述的配置。请注意,与用于与metastore对话的Hive版本无关,内部SparkSQL将针对Hive1.2.1编译,并将这些类用于内部执行(SERDES、UDF、UDAFs等)。
以下选项可用于配置用于检索元数据的Hive版本:

Property NameDefaultMeaning
spark.sql.hive.metastore.version1.2.1Hive metastore的版本备选方案为0.12.0-2.3.3
spark.sql.hive.metastore.jarsbuiltin用于实例化HiveMetastoreClient的JAR的位置。这个属性可以是三个选项之一:内置使用Hive 1.2.1,当启用-Phive时,它与SPark程序集捆绑在一起。选择此选项时,spack.sql.hive.asiore.version必须为1.2.1或未定义。Maven使用从Maven存储库下载的指定版本的Hive JAR。这种配置通常不推荐用于生产部署。JVM的标准格式的类路径。这个类路径必须包含所有Hive及其依赖项,包括Hadoop的正确版本。这些JAR只需要显示在驱动程序上,但是如果您在纱线集群模式下运行,则必须确保它们与应用程序一起打包。
spark.sql.hive.metastore.sharedPrefixescom.mysql.jdbc,

org.postgresql,
com.microsoft.sqlserver,
oracle.jdbc | 一个以逗号分隔的类前缀列表,应该使用SparkSQL和一个特定版本的Hive之间共享的类加载器来加载。应该共享的类的一个例子是JDBC驱动程序,这些驱动程序是与metastore通信所需的。其他需要共享的类是那些与已经共享的类交互的类。例如,log4j使用的自定义追加程序。 |
| spark.sql.hive.metastore.barrierPrefixes | (empty) | 一个逗号分隔的类前缀列表,这些前缀应该显式地重新加载到Spark SQL正在与之通信的每个配置单元。例如,在通常将共享的前缀(即,org.apache.spark.*)中声明的HiveUFS。 |

JDBC To Other Databases

SparkSQL还包括数据源,该数据源可以使用JDBC从其他数据库读取数据。此功能应优于使用JDBCRDD。这是因为结果将作为数据文件返回,并且可以在Spark SQL中轻松处理,或者与其他数据源连接。JDBC数据源也更易于从Java或Python中使用,因为它不要求用户提供ClassStag。(请注意,这不同于SparkSQLJDBC服务器,它允许其他应用程序使用SparkSQL运行查询)。
可以使用数据源API将远程数据库中的表作为DataFrame或SparkSQL临时视图加载。用户可以在数据源选项中指定JDBC连接属性。用户和密码通常作为登录到数据源的连接属性提供。除了连接属性之外,Spark还支持以下不区分大小写的选项:

Property NameMeaning
url要连接到的JDBCURL。源特定的连接属性可以在URL中指定.例如,jdbc:postgresql://localhost/test?user=fred&password=secret
dbtable应该读取或写入的JDBC表。注意,在读取路径中使用它时,可以使用SQL查询的FROM子句中的任何有效内容。例如,您也可以使用括号中的子查询来代替完整的表。不允许同时指定dbtable‘和query’选项。
query用于将数据读入Spark的查询。指定的查询将被括号化,并用作FROM子句中的子查询。SPARK还将为子查询子句分配别名。例如,SPARK将向JDBCSource发出以下形式的查询。【 SELECT FROM (<user_specified_query>) spark_gen_alias 】 下面是使用此选项时的几个限制。不允许同时指定’dbtable’和’query’选项。不允许同时指定’query’和’PartionColumn’选项。当需要指定’PartitionColumn’选项时,可以使用’dbtab’选项来指定子查询,并且可以使用作为’dbtable’一部分提供的子查询别名对分区列进行限定。如:spark.read.format(“jdbc”) &nbsp&nbsp .option(“dbtable”, “(select c1, c2 from t1) as subq”) &nbsp&nbsp .option(“partitionColumn”, “subq.c1” &nbsp&nbsp .load()
driver用于连接到此URL的JDBC驱动程序的类名。
partitionColumn, lowerBound, upperBound如果指定了任何选项,则必须全部指定这些选项。此外,必须指定numPartitions。它们描述了如何在多个工作人员并行读取时对表进行分区。分区列必须是有关表中的数字、日期或时间戳列。注意,低边界和上界只是用来决定分区步长,而不是过滤表中的行。因此,表中的所有行都将被分区并返回。此选项仅适用于阅读。
numPartitions表读取和写入中可用于并行处理的最大分区数。这也决定了并发JDBC连接的最大数量。如果要写入的分区数超过此限制,则在编写之前调用Colesce(NumPartitions)将其减少到此限制。
queryTimeout驱动程序将等待语句对象执行到给定秒数的秒数。零表示没有限制。在写路径中,此选项取决于JDBC驱动程序如何实现API setQueryTimeout,例如,H2 JDBC驱动程序检查每个查询的超时,而不是检查整个JDBC批处理。默认为0。
fetchsizeJDBC FETCH大小,它确定每次往返要取多少行。这可以提高JDBC驱动程序的性能,后者默认为低获取大小(例如。(Oracle有10行)。此选项仅适用于阅读。
batchsizeJDBC批处理大小,它确定每个往返要插入多少行。这可以提高JDBC驱动程序的性能。此选项仅适用于写作。默认为1000。
isolationLevel事务隔离级别,适用于当前连接。它可以是“无”、“读”、“未提交”、“可重复读”或“SERIALIZABLE”之一,对应于JDBC的连接对象定义的标准事务隔离级别,缺省值为read_unCOMMIT。此选项仅适用于写作。请参阅java.sql.Connection中的文档。
sessionInitStatement在向远程DB打开每个数据库会话之后,在开始读取数据之前,此选项将执行自定义SQL语句(或PL/SQL块)。使用它来实现会话初始化代码。示例:选项(“sessionInitStatement”、“开始执行立即的”ALTERSession SET“)_串行_Directread”=true‘;end;“)
truncate这是一个与JDBC编写器相关的选项。启用SaveMode.overwrite时,此选项将导致SPark截断现有表,而不是删除和重新创建它。这可以提高效率,并防止删除表元数据(例如索引)。但是,在某些情况下,例如当新数据有不同的模式时,它将无法工作。默认为false。此选项仅适用于写作。
cascadeTruncate这是一个JDBC编写器相关选项。如果启用并受JDBC数据库支持(此时PostgreSQL和Oracle),则此选项允许执行TRUNCATE TABLE t CASCADE(在PostgreSQL的情况下,执行TRUNCATE TABLE ONLY t CASCADE,以防止意外截断后代表)。这将影响其他表格,因此应谨慎使用。此选项仅适用于写入。它默认为所讨论的JDBC数据库的默认级联截断行为,在每个JDBCt方言中的iscascedctruncate中指定。
createTableOptions这是一个与JDBC编写器相关的选项。如果指定,此选项允许在创建表时设置特定于数据库的表和分区选项(例如,CREATE TABLE t (name string) ENGINE=InnoDB.)此选项仅适用于写作。
createTableColumnTypes创建表时要使用的数据库列数据类型而不是默认值。数据类型信息应该以与CREATETABLE列语法相同的格式指定(例如:“name char(64),注释VARCHAR(1024)”)。指定的类型应该是有效的SparkSQL数据类型。此选项仅适用于写作。
customSchema用于从JDBC连接器读取数据的自定义架构。例如,“id DECIMAL(38,0),名称字符串”。还可以指定部分字段,其他字段使用默认类型映射。例如,“id DECIMAL(38,0)”。列名应该与JDBC表的相应列名相同。用户可以指定SparkSQL的相应数据类型,而不是使用默认值。此选项仅适用于阅读。
pushDownPredicate启用或禁用谓词下推到JDBC数据源的选项。默认值为true,在这种情况下,Spark将尽可能地将筛选器向下推到JDBC数据源。否则,如果设置为false,则不会将筛选器向下推到JDBC数据源,因此所有筛选器都将由Spark处理。当Spark比JDBC数据源更快地执行谓词筛选时,通常会关闭谓词下推。
// Note: JDBC loading and saving can be achieved via either the load/save or jdbc methods
// Loading data from a JDBC source
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)
// Specifying the custom data types of the read schema
connectionProperties.put("customSchema", "id DECIMAL(38, 0), name STRING")
val jdbcDF3 = spark.read
  .jdbc("jdbc:postgresql:dbserver", "schema.tablename", connectionProperties)

// Saving data to a JDBC source
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)

// Specifying create table column data types on write
jdbcDF.write
  .option("createTableColumnTypes", "name CHAR(64), comments VARCHAR(1024)")
  .jdbc("jdbc:postgresql:dbserver", "schema.tablename", connectionProperties)

Avro Data Source Guide

Deploying

spark-avro模块是外部的,默认情况下不包括在spark-submit or spark-shell中.与任何Spark应用程序一样,spark-submit也用于启动您的应用程序。spark-avro_2.11及其依赖项可以直接添加到spark-submit using --packages中,例如:

./bin/spark-submit --packages org.apache.spark:spark-avro_2.11:2.4.0 ...
./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.spk.sql.avro)。

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数据类型可以是复杂类型或基本类型。
使用Avro记录作为列在读取或写入像Kafka这样的流源时非常有用。每个Kafka键值记录都会增加一些元数据,如将时间戳摄入Kafka,Kafka的偏移量等。如果包含数据的“value”字段位于Avro中,则可以使用From_Avro()提取数据、丰富数据、清理数据,然后再将其向下推到Kafka或将其写入文件。to_Avro()可用于将结构转换为Avro记录。当您想要在将数据写入Kafka时将多个列重新编码为一个列时,此方法尤其有用。这两个函数目前只在Scala和Java中可用。

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

// `from_avro` requires Avro schema in JSON string format.
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. Decode the Avro data into a struct;
// 2. Filter by column `favorite_color`;
// 3. Encode the column `name` in Avro format.
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()

Data Source Option

Property NameDefaultMeaningScope
avroSchemaNone用户以JSON格式提供的可选Avro模式。记录字段的日期类型和命名应该与输入的Avro数据或促进数据匹配,否则读/写操作将失败。read and write
recordNametopLevelRecord在写结果中的顶级记录名,这是Avro规范所要求的。write
recordNamespace“”在写结果中记录命名空间。write
ignoreExtensiontrue该选项控制忽略读取中没有.avro扩展名的文件。如果启用该选项,则将加载所有文件(有.avro扩展名和不具有.avro扩展名)。read
compressionsnappy压缩选项允许指定写入中使用的压缩编解码器。当前支持的编解码器是未压缩的、快的、放气的、bzip 2和xz。如果未设置该选项,则将考虑配置Spark.sql.avro.压缩.codec配置。write

Configuration

Avro的配置可以在SparkSession上使用setConf方法完成,也可以通过使用SQL运行set key=value命令来完成。

Property NameDefaultMeaning
spark.sql.legacy.replaceDatabricksSparkAvro.enabledtrue如果将其设置为true,则数据源提供程序com.databricks.scamk.avro将映射到内置但外部的Avro数据源模块,以实现向后兼容性。
spark.sql.avro.compression.codecsnappy用于编写Avro文件的压缩编解码器。支持的编解码器:未压缩,放气,开关,bzip 2和xz。默认编解码器是快速的。
spark.sql.avro.deflate.level-1用于编写Avro文件的压缩编解码器的压缩级别。有效值必须在1到9(含-1)的范围内。默认值为-1,对应于当前实现中的6个级别。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值