1. 一般的Load/Save方法
Spark SQL最簡單的也是默認的數據源格式是Parquet(除非使用了spark.sql.sources.default配置修改),它將會被用於所有的操作。以下是一般的Load/Save方法:
// generic load/save functions
Dataset<Row> usersDF = spark.read().load("examples/src/main/resources/users.parquet");
usersDF.select("name", "favorite_color").write().save("namesAndFavColors.parquet");
* 如果是使用windows系統的話,需要把對應版本編譯的hadoop.dll複製到C:\Windows\System32,否則會遇到以下錯誤:
java.lang.UnsatisfiedLinkError: org.apache.hadoop.io.nativeio.NativeIO$Windows.createFileWithMode0(Ljava/lang/String;JJJI)Ljava/io/FileDescriptor;
at org.apache.hadoop.io.nativeio.NativeIO$Windows.createFileWithMode0(Native Method)
at org.apache.hadoop.io.nativeio.NativeIO$Windows.createFileOutputStreamWithMode(NativeIO.java:559)
at org.apache.hadoop.fs.RawLocalFileSystem$LocalFSFileOutputStream.<init>(RawLocalFileSystem.java:219)
at org.apache.hadoop.fs.RawLocalFileSystem$LocalFSFileOutputStream.<init>(RawLocalFileSystem.java:209)
at org.apache.hadoop.fs.RawLocalFileSystem.createOutputStreamWithMode(RawLocalFileSystem.java:307)
at org.apache.hadoop.fs.RawLocalFileSystem.create(RawLocalFileSystem.java:296)
at org.apache.hadoop.fs.RawLocalFileSystem.create(RawLocalFileSystem.java:328)
at org.apache.hadoop.fs.ChecksumFileSystem$ChecksumFSOutputSummer.<init>(ChecksumFileSystem.java:398)
at org.apache.hadoop.fs.ChecksumFileSystem.create(ChecksumFileSystem.java:461)
at org.apache.hadoop.fs.ChecksumFileSystem.create(ChecksumFileSystem.java:440)
at org.apache.hadoop.fs.FileSystem.create(FileSystem.java:911)
at org.apache.hadoop.fs.FileSystem.create(FileSystem.java:892)
at org.apache.hadoop.fs.FileSystem.create(FileSystem.java:789)
at org.apache.parquet.hadoop.ParquetFileWriter.<init>(ParquetFileWriter.java:223)
......
1.1 手動指定選項
我們也可以通過完整的全名(例如:org.apache.spark.sql.parquet)來指定數據源的類型,對於那些內置的數據源類型,也可以使用簡稱,例如:json, parquet, jdbc, orc, libsvm, csv, text。從任何數據源類型加載的DataFrames可以轉換爲其它的類型格式,例如:
// manually load options
Dataset<Row> peopleDF = spark.read().format("json").load("examples/src/main/resources/people.json");
peopleDF.select("name", "age").write().format("parquet").save("namesAndAges.parquet");
1.2 直接執行SQL
我們也可以直接執行SQL查詢而不需要使用API加載文件爲DataFrame然後再查詢,例如:
// run SQL on files directly
Dataset<Row> sqlDF = spark.sql("SELECT * FROM parquet.`examples/src/main/resources/users.parquet`");
sqlDF.show();
// +------+--------------+----------------+
// | name|favorite_color|favorite_numbers|
// +------+--------------+----------------+
// |Alyssa| null| [3, 9, 15, 20]|
// | Ben| red| []|
// +------+--------------+----------------+
* 注意parquet.後面的路徑前後有個`的字符(與鍵盤~一起的那個逗點)
1.3 保存模式
保存操作可以選擇性地使用SaveMode指定如何處理存在的數據。需要注意的是這些保存模式不使用任何鎖和不是原子操作的。此外,當使用Overwrite模式時,原數據會在寫入新數據之前就會被刪除。以下是SaveMode的選項,當保存一個DataFrame到指定數據源的時候,如果輸出路徑已經存在:
SaveMode.ErrorIfExists(默認) 拋出異常
SaveMode.Append 數據會以追加的方式保存
SaveMode.Overwrite 新數據會覆蓋原數據(先刪除原數據,再保存新數據)
SaveMode.Ignore 不保存新數據,相當於SQL語句的CREATE TABLE IF NOT EXISTS
例如:
usersDF.select("name", "favorite_color").write().mode(SaveMode.Overwrite).save("namesAndFavColors.parquet");
1.4 持久化到表
我們也可以使用saveAsTable方法把DataFrames保存爲持久化的表到Hive metastore。值得注意的是我們不需要部署Hive的環境,Spark會創建一個默認本地的Hive metastore(使用Derby)。與createOrReplaceTempView方法不同,saveAsTable會實質化DataFrame的內容,然後在Hive metastore創建它的指針。只要連接到相同metastore的連接不中斷,即使Spark程序重新啓動,持久化的表也會一直存在。
默認地saveAsTable方法將創建一個“管理表”(managed table),表示數據的位置是由metastore來控制管理的。當持久化的表被刪除時,managed table將會自動刪除相應的數據。
2. Parquet文件
Parquet是一種被其它多種數據處理系統支持的縱列格式。Spark SQL提供了讀寫Parquet文件的功能,保存的Parquet文件會自動保留原始數據的schema。當保存Parquet文件時,基於兼容性考慮,所有的列會被自動轉換爲允許空值。
2.1 以編程方式加載數據
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.MapFunction;
import org.apache.spark.sql.Encoders;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
Dataset<Row> 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
Dataset<Row> 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");
Dataset<Row> namesDF = spark.sql("SELECT name FROM parquetFile WHERE age BETWEEN 13 AND 19");
Dataset<String> namesDS = namesDF.map(row -> "Name: " + row.getString(0), Encoders.STRING());
namesDS.show();
// +------------+
// | value|
// +------------+
// |Name: Justin|
// +------------+
2.2 分區推斷
對錶進行分區是對數據進行優化的方式之一。在一個分區的表內,數據通常是通過分區列將數據存儲在不同的目錄裏面。Parquet數據源現在能夠自動地發現並推斷分區信息。例如,可以使用下面的目錄結構存儲人口數據到分區表裏面,分區列爲gender和country:
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的Schema如下:
root
|-- name: string (nullable = true)
|-- age: long (nullable = true)
|-- gender: string (nullable = true)
|-- country: string (nullable = true)
需要注意的是,分區列的數據類型是自動解析的。目前,數值類型和字符串類型是支持的。如果不想分區列的數據類型被自動解析,可以通過配置spark.sql.sources.partitionColumnTypeInference.enabled=false,默認是true。當該配置被設爲false時,分區列數據類型會使用string類型。
從Spark 1.6.0版本開始,默認地,分區信息解析只會作用於指定路徑下面的分區。例如上面的例子,如果用戶傳遞path/to/table/gender=male給SparkSession.read.parquet或SparkSession.read.load,gender將不會是分區列。如果用戶需要指定基礎的路徑作爲分區信息解析的開始路徑,那麼可以在數據源選項設置basePath。例如,path/to/table/gender=male是數據的路徑,用戶設置了basePath=path/to/table/,那麼gender將會是分區列。
2.3 Schema合併
像ProtocolBuffer、Avro和Thrift一樣,Parquet也支持schema evolution(schema演變)。用戶可以先定義一個簡單的schema,然後根據需要逐漸地向schema中增加列。通過這種方式,用戶可以有多個不同的schemas但它們是互相兼容的Parquet文件。Parquet數據源現在能夠自動檢測這種情況併合並這些文件的schemas。
因爲Schema合併是一個相對高消耗的操作,在大多數的情況下並不需要,所以從Spark SQL 1.5.0版本開始,默認關閉了該功能。可以通過下面兩種方式開啓:
- 當讀取Parquet文件時,設置數據源選項mergeSchema=true(例如下面的例子)
- 設置全局SQL選項spark.sql.parquet.mergeSchema=true
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
public static class Square implements Serializable {
private int value;
private int square;
// Getters and setters...
}
public static class Cube implements Serializable {
private int value;
private int cube;
// Getters and setters...
}
List<Square> squares = new ArrayList<>();
for (int value = 1; value <= 5; value++) {
Square square = new Square();
square.setValue(value);
square.setSquare(value * value);
squares.add(square);
}
// Create a simple DataFrame, store into a partition directory
Dataset<Row> squaresDF = spark.createDataFrame(squares, Square.class);
squaresDF.write().parquet("data/test_table/key=1");
List<Cube> cubes = new ArrayList<>();
for (int value = 6; value <= 10; value++) {
Cube cube = new Cube();
cube.setValue(value);
cube.setCube(value * value * value);
cubes.add(cube);
}
// Create another DataFrame in a new partition directory,
// adding a new column and dropping an existing column
Dataset<Row> cubesDF = spark.createDataFrame(cubes, Cube.class);
cubesDF.write().parquet("data/test_table/key=2");
// Read the partitioned table
Dataset<Row> 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)
2.4 Hive metastore Parquet錶轉換
當讀寫Hive metastore Parquet表時,基於性能考慮,Spark SQL會先嚐試使用自帶的Parquet SerDe(序列化與反序列化,Serialize/Deserilize的簡稱),而不是Hive的SerDe。這個優化選項可以通過spark.sql.hive.convertMetastoreParquet配置,默認爲開啓。
2.4.1 Hive/Parquet Schema一致化
從表schema處理的角度來看,Hive和Parquet有2個主要的不同點:
- Hive不區分大小寫,Parquet區分大小寫
- Hive認爲所有的列都可以爲空,而Parquet的空值性是有重要意義的(Hive considers all columns nullable, while nullability in Parquet is significant)
- 兩個schema中,忽略空值性,具有相同名字的字段必須具有相同的數據類型。一致化後的字段類型應該與Parquet的字段類型一致,以便遵守空值性原則
- 一致化後的schema只包含在Hive metastore schema定義的字段:
i. 把只在Hive metastore schema定義的字段設爲允許爲空
2.4.2 元數據刷新
爲了提高性能,Spark SQL會緩存Parquet元數據(metadata)。當Hive metastore Parquet錶轉換的選項開啓時,轉換後的表元數據也會被緩存。如果這些表被Hive或者其它外部工具更新,則需要手動刷新緩存以確保元數據的一致性。
// spark is an existing SparkSession
spark.catalog().refreshTable("my_table");
2.5 配置
Parquet的配置可以使用SparkSession的setConf方法或者使用SQL執行SET key=value命令。詳細的配置參數如下:
3. JSON Datasets
Spark SQL可以自動推斷JSON數據集的schema並加載爲Dataset<Row>。此轉換可以使用SparkSession.read().json()方法讀取一個String類型的RDD或者一個JSON文件。需要注意的是,這裏的JSON文件不是典型的JSON格式。這裏的JSON文件每一行必須包含一個獨立有效的JSON對象,也稱爲換行符分割JSON文件。因此,一個規則的多行JSON文件會導致讀取出錯。讀取JSON數據集例子如下:
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
// A JSON dataset is pointed to by path.
// The path can be either a single text file or a directory storing text files
Dataset<Row> people = spark.read().json("examples/src/main/resources/people.json");
// The inferred schema can be visualized using the printSchema() method
people.printSchema();
// root
// |-- age: long (nullable = true)
// |-- name: string (nullable = true)
// Creates a temporary view using the DataFrame
people.createOrReplaceTempView("people");
// SQL statements can be run by using the sql methods provided by spark
Dataset<Row> namesDF = spark.sql("SELECT name FROM people WHERE age BETWEEN 13 AND 19");
namesDF.show();
// +------+
// | name|
// +------+
// |Justin|
// +------+
// Alternatively, a DataFrame can be created for a JSON dataset represented by
// an RDD[String] storing one JSON object per string.
List<String> jsonData = Arrays.asList(
"{\"name\":\"Yin\",\"address\":{\"city\":\"Columbus\",\"state\":\"Ohio\"}}");
JavaRDD<String> anotherPeopleRDD =
new JavaSparkContext(spark.sparkContext()).parallelize(jsonData);
Dataset<Row> anotherPeople = spark.read().json(anotherPeopleRDD);
anotherPeople.show();
// +---------------+----+
// | address|name|
// +---------------+----+
// |[Columbus,Ohio]| Yin|
// +---------------+----+
* 參考Spark SQL官方鏈接:http://spark.apache.org/docs/latest/sql-programming-guide.html#data-sources
TO BE CONTINUED...O(∩_∩)O