Spark SQL中對Json支援的詳細介紹

過往記憶的部落格發表於2015-02-04

在這篇文章中,我將介紹一下Spark SQL對Json的支援,這個特性是Databricks的開發者們的努力結果,它的目的就是在Spark中使得查詢和建立JSON資料變得非常地簡單。隨著WEB和手機應用的流行,JSON格式的資料已經是WEB Service API之間通訊以及資料的長期儲存的事實上的標準格式了。但是使用現有的工具,使用者常常需要開發出複雜的程式來讀寫分析系統中的JSON資料集。而Spark SQL中對JSON資料的支援極大地簡化了使用JSON資料的終端的相關工作,Spark SQL對JSON資料的支援是從1.1版本開始釋出,並且在Spark 1.2版本中進行了加強。

現有Json工具實踐

在實踐中,使用者往往在處理現代分析系統中JSON格式的資料中遇到各種各樣的困難。如果使用者需要將資料集寫成JSON格式的話,他們需要編寫複雜的邏輯程式來轉換他們的資料集到JSON格式中。如果需要讀取或者查詢JSON資料集,他們通常需要預先定義好資料結構並用它來轉換JSON資料。在這種情況下,使用者必須等待這些資料處理完成之後,才能夠使用他們生成的JSON資料。無論是在寫或者是讀,預先定義和維護這些模式往往使得ETL工作變得非常地繁重!並且可能消除掉JSON這種半結構化(semi-structured)的資料格式的好處。如果使用者想消費新的資料,他們不得不在建立外部表的時候定義好相關的模式,並使用自定義的JSON serialization/deserialization依賴庫,或者是在查詢JSON資料的時候使用UDF函式。

作為一個例子,如果有下面的一些JSON資料模式

{"name":"Yin", "address":{"city":"Columbus","state":"Ohio"}}
{"name":"Michael", "address":{"city":null, "state":"California"}}

在類似於Hive的系統中,這些JSON物件往往作為一個值儲存到單個的列中,如果需要訪問這個資料,我們需要使用UDF來抽取出我們需要的資料。在下面的SQL查詢例子中,外層的欄位(name和address)被抽取出來,巢狀在內層的address欄位也被進一步的抽取出來:

/**
 * User: 過往記憶
 * Date: 15-02-04
 * Time: 上午07:30
 * bolg: http://www.iteblog.com
 * 本文地址:http://www.iteblog.com/archives/1260
 * 過往記憶部落格,專注於hadoop、hive、spark、shark、flume的技術部落格,大量的乾貨
 * 過往記憶部落格微信公共帳號:iteblog_hadoop
 */

SELECT
  v1.name, v2.city, v2.state 
FROM people
  LATERAL VIEW json_tuple(people.jsonObject, 'name', 'address') v1 
     as name, address
  LATERAL VIEW json_tuple(v1.address, 'city', 'state') v2
     as city, state;

Spark SQL中對JSON的支援

Spark SQL提供了內建的語法來查詢這些JSON資料,並且在讀寫過程中自動地推斷出JSON資料的模式。Spark SQL可以解析出JSON資料中巢狀的欄位,並且允許使用者直接訪問這些欄位,而不需要任何顯示的轉換操作。上面的查詢語句如果使用Spark SQL的話,可以這樣來寫:

SELECT name, age, address.city, address.state FROM people

在Spark SQL中載入和儲存JSON資料集

為了能夠在Spark SQL中查詢到JSON資料集,唯一需要注意的地方就是指定這些JSON資料儲存的位置。這些資料集的模式是直接可以推斷出來,並且內建就有相關的語法支援,不需要使用者顯示的定義。在程式設計中使用API中,我們可以使用SQLContext提供的jsonFile和jsonRDD方法。使用這兩個方法,我們可以利用提供的JSON資料集來建立SchemaRDD 物件。並且你可以將SchemaRDD 註冊成表。下面是一個很好的例子:

/**
 * User: 過往記憶
 * Date: 15-02-04
 * Time: 上午07:30
 * bolg: http://www.iteblog.com
 * 本文地址:http://www.iteblog.com/archives/1260
 * 過往記憶部落格,專注於hadoop、hive、spark、shark、flume的技術部落格,大量的乾貨
 * 過往記憶部落格微信公共帳號:iteblog_hadoop
 */
// Create a SQLContext (sc is an existing SparkContext)
val sqlContext = new org.apache.spark.sql.SQLContext(sc)
// Suppose that you have a text file called people with the following content:
// {"name":"Yin", "address":{"city":"Columbus","state":"Ohio"}}
// {"name":"Michael", "address":{"city":null, "state":"California"}}
// Create a SchemaRDD for the JSON dataset.
val people = sqlContext.jsonFile("[the path to file people]")
// Register the created SchemaRDD as a temporary table.
people.registerTempTable("people")

當然,我們也可以使用純的SQL語句來建立JSON資料集。例如

CREATE TEMPORARY TABLE people
USING org.apache.spark.sql.json
OPTIONS (path '[the path to the JSON dataset]')

在上面的例子中,因為我們沒有顯示地定義模式,Spark SQL能夠自動地掃描這些JSON資料集,從而推斷出相關的模式。如果一個欄位是JSON物件或者陣列,Spark SQL將使用STRUCT 型別或者ARRAY型別來代表這些欄位。即使JSON數是半結構化的資料,並且不同的元素肯恩好擁有不同的模式,但是Spark SQL仍然可以解決這些問題。如果你想知道JSON資料集的模式,你可以通過使用返回來的SchemaRDD 物件中提供的printSchema()函式來列印出相應的模式,或者你也可以在SQL中使用DESCRIBE [table name] 。例如上面的people資料集的模式可以通過people.printSchema() 列印出:

root
 |-- address: struct (nullable = true)
 |    |-- city: string (nullable = true)
 |    |-- state: string (nullable = true)
 |-- name: string (nullable = true)

當然,使用者在利用 jsonFile 或 jsonRDD建立表的時候也可以顯示的指定一個模式到JSON資料集中。在這種情況下,Spark SQL將把這個模式和JSON資料集進行繫結,並且將不再會去推測它的模式。使用者不需要了解JSON資料集中所有的欄位。指定的模式可以是固定資料集的一個子集,也可以包含JSON資料集中不存在的欄位。

當使用者建立好代表JSON資料集的表時,使用者可以很簡單地利用SQL來對這個JSON資料集進行查詢,就像你查詢普通的表一樣。在Spark SQL中所有的查詢,查詢的返回值是SchemaRDD物件。例如:

val nameAndAddress = sqlContext.sql("SELECT name, address.city, address.state FROM people")
nameAndAddress.collect.foreach(println)

查詢的結果可以直接使用,或者是被其他的分析任務使用,比如機器學習。當然,JSON資料集可以通過Spark SQL內建的記憶體列式儲存格式進行儲存,也可以儲存成其他格式,比如Parquet或者 Avro。

將SchemaRDD物件儲存成JSON檔案

在Spark SQL中,SchemaRDDs可以通過toJSON 方法儲存成JSON格式的檔案。因為SchemaRDD中已經包含了相應的模式,所以Spark SQL可以自動地將該資料集轉換成JSON,而不需要使用者顯示地指定。當然,SchemaRDDs可以通過很多其他格式的資料來源進行建立,比如Hive tables、 Parquet檔案、 JDBC、Avro檔案以及其他SchemaRDD的結果。這就意味著使用者可以很方便地將資料寫成JSON格式,而不需要考慮到源資料集的來源。

相關文章