綜述
Spark SQL是Spark提供的針對結構化數據處理的模塊。不同於基本的Spark RDD API,SparkSQL提供的接口提供了更多的關於數據和計算執行的信息。在內部,SparkSQL使用這些額外信息完成額外的優化。這裏有幾種方式可以和SparkSQL相互操作,包括SQL和Dataset API。計算結果的時候使用相同的執行
本頁中所有示例使用到的樣例的數據都包含在Spark發佈中,而且都能在spark-shell,pyspark或者sparkR中運行。
SQL
Spark SQL的一種用法是執行SQL查詢。Spark SQL也可以用於從已安裝的Hive中讀取數據。更多的關於此特性的配置,請參考Hive Tables。當從內部其他編程語言執行SQL,結果將以Dataset/DataFrame形式返回。你也可以通過command-line或者JDBC/ODBC與SQL接口進行交互。
Datasets和DataFrames
Dataset是分佈式數據集合。Dataset是Spark1.6新增的接口,用以提供RDDs(強類型,有使用強大的lambda函數的能力)的優點和Spark SQL的經優化的執行引擎的優點。Dataset可以從JVM對象進行構造並通過轉換函數(如map,flatmap,filter等)進行操作。DatasetAPI支持Scala和Java。Python不支持Dataset API。但因爲Python本身的動態性,DatasetAPI的許多優點都已經可用(比如,你可以通過名字很自然的訪問一行的某一個字段,如row.columnName),R的情況與此類似。
DataFrame是Dataset組織成命名列的形式。它在概念上相當於關係型數據庫中的表,或者R/Python中的數據幀,但是在底層進行了更多的優化。DataFrames可以從多種數據源創建,例如:結構化數據文件、Hive中的表、外部數據庫或者已存在的RDDs。DataFrame API支持Scala、Java、Python和R。在Scala和Java中DataFrame其實是Dataset的RowS的形式的表示。在Scala API中,DataFrame僅僅是Dataset[Row]的別名。但在Java中,使用者需要使用Dataset<Row>來表示一個DataFrame。
在本文檔中,我們會經常將Scala/Java Dataset的RowS作爲DataFrame的參考。
開始使用
起始點:SparkSession
在Spark中所有功能的切入點是SparkSession類。直接使用SparkSession.builder()就可以創建一個基本的SaprkSession:
可以在Spark倉庫的"examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala"找到完整的代碼。
SparkSession是Spark2.0開始提供的內建了對Hive特性的支持,包括使用HiveQL寫查詢語句、調用Hive UDFs、從Hive表讀取數據的能力。你不需要事先部署Hive就能使用這些特性。
創建DataFrame
使用SparkSession,應用可以從已存在的RDD、Hive表或者Spark數據源創建DataFrame。下面的示例從一個JSON文件創建一個DataFrame:
可以在Spark倉庫的"examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala"找到完整的代碼。
無類型的Dataset操作(aka DataFrame Operations)
DataFrame在Scala、Java、Python和R中爲結構化數據操作提供了一個特定領域語言支持。
就像網文提到的,在Spark2.0中,在Scala和Java的API中,DataFrame僅僅是Dataset的RowS表示。與Scala/Java中的強類型的“帶類型轉換操作”相比,這些操作也可以看做“無類型轉換操作”。
這裏我們提供了一些使用Dataset進行結構化數據處理的基本示例:
可以在Spark倉庫的"examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala"找到完整的代碼。
可以在Dataset上執行的操作的類型的完整列表可以參考API文檔。
除了簡單的列引用和表達式外,Dataset同時有豐富的函數庫,包括字符串操作、日期算法、常用數學操作等。完整的列表可參考DataFrame Function Reference。
編程執行SQL查詢語句
Sparksession中的sql函數使得應用可以編程式執行SQL查詢語句並且已DataFrame形式返回:
可以在Spark倉庫的"examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala"找到完整的代碼。
創建Dataset
Dataset與RDD很像,不同的是它並不使用Java序列化或者Kryo,而是使用特殊的編碼器來爲網絡間的處理或傳輸的對象進行序列化。對轉換一個對象爲字節的過程來說編碼器和標準系列化器都是可靠的,編碼器的代碼是自動生成並且使用了一種格式,這種格式允許Spark在不需要將字節解碼成對象的情況下執行很多操作,如filtering、sorting和hashing等。
可以在Spark倉庫的"examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala"找到完整的代碼。
與RDD互操作
Spark SQL支持兩種將已存在的RDD轉化爲Dataset的方法。第一種方法使用反射推斷包含特定類型對象的RDD的結構。這種基於反射的方法代碼更加簡潔,並且當你在寫Spark程序的時候已經知道RDD的結構的情況下效果很好。
第二種創建Dataset的方法是通過編程接口建立一個結構,然後將它應用於一個存在的RDD。雖然這種方法更加繁瑣,但它允許你在運行之前不知道其中的列和對應的類型的情況下構建Dataset。
使用反射推斷結構
Spark SQL的Scala接口支持自動的將一個包含case class的RDD轉換爲DataFrame。這個case class定義了表結構。Caseclass的參數名是通過反射機制讀取,然後變成列名。Caseclass可以嵌套或者包含像Seq或Array之類的複雜類型。這個RDD可以隱式的轉換爲一個DataFrame,然後被註冊爲一張表。這個表可以隨後被SQL的statement使用。
可以在Spark倉庫的"examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala"in the Spark repo. 找到完整的代碼。
以編程方式指定模式
當case class不能被事先定義(比如記錄的結構被編碼爲字符串,或者對不同的用戶,文本數據集被不同的解析並進行字段投影),DataFrame可以通過以下3個步驟實現編程創建:
- 從原始RDD創建RowS形式的RDD
- 以StructType創建匹配步驟1中RowS形式的RDD的模式
- 通過SparkSession提供的createDataFrame方法將模式應用於RowS形式的RDD
例如:
可以在Spark倉庫的"examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala"找到完整的代碼。
數據源
Spark SQL通過DataFrame接口,可以支持對多種數據源的操作。DataFrame可以使用關係轉換來進行操作,而且可以用來創建臨時視圖。將DataFrame註冊爲臨時視圖可以允許你在數據上運行SQL查詢語句。本節講解使用SparkData Source加載數據和保存數據的通用方法,然後
詳細講述內部支持的數據源可用的特定操作。
通用Load/Save函數
最簡單的,默認的數據源(parquet,除非使用spark.sql.sources.default進行了配置)將被用於所有的操作。
可以在Spark倉庫的"examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala"找到完整的代碼。
手動指定選項
你可以手動指定數據源以及數據源附帶的額外選項。數據源被他們的完全限定名來指定(如,org.apache.spark.sql.parquet),但對於內部支持的數據源,你可以使用短名(json,parquet,jdbc)。DataFrame可以使用這種語法從任何可以轉換爲其他類型的數據源加載數據。
可以在Spark倉庫的"examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala"找到完整的代碼。
在文件上直接執行SQL
除了使用讀取API加載一個文件到SATAFrame然後查詢它的方式,你同樣可以通過SQL直接查詢文件。
可以在Spark倉庫的"examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala"找到完整的代碼。
保存模式
保存操作可選SaveMode,它指定了如何處理現有的數據。需要重視的一點是這些保存模式沒有使用任何的鎖,並且不具有原子性。此外,當執行Overwrite時,數據將先被刪除,然後寫出新數據。
Scala/Java |
其他語言 |
含義 |
SaveMode.ErrorIfEcists(默認) |
“error”(默認) |
保存DataFrame到數據源時,如果數據已經存在,將拋出一個異常。 |
SaveMode.Append |
“append” |
保存DataFrame到數據源時,如果數據/表存在時,DataFrame的內容將追加到已存在的數據後。 當 |
SaveMode.Overwrite |
“overwrite” |
Overwrite模式意味着當保存一個DataFrame到數據源時,如果數據/表已經存在,存在的數據將會被DataFrame的內容覆蓋。 |
SaveMode.Ignore |
“ignore” |
Ignore模式意味着當保存一個DataFrame到數據源時,如果數據已經存在,保存操作將不會保存DataFrame的內容,並且不會改變原數據。這與SQL中的CREATE TABLE IF NOT EXISTS相似。 |
保存到持久化表
也可以通過saveAsTable命令將DataFrame作爲持久化表保存到Hive元數據庫中。注意使用此特性時不需要事先部署Hive。Spark將爲你創建一個默認的本地Hive元數據庫(使用Derby)。不同於createOrReplaceTempView命令,saveAsTable將具體化DataFrame的內容並且在Hive元數據庫中創建一個指向數據的指針。在你保持你的連接是到相同的元數據庫時,當你的Spark程序重啓後持久化表依然會存在。通過在SparkSession上使用表名調用table命令,可以創建用於持久化表的DataFrame。
默認的saveAsTable將會創建一個“託管表”,意味着數據的位置醬油元數據庫控制。託管表也有他們自己的數據,當對應的表被刪除時這些數據會一併刪除。
Parquet文件
Parquet是一種被很多其他數據處理系統支持的列式文件。Spark SQL提供了可以自動保存原始數據模式的對Parquet文件讀取和寫入的操作。當寫入一個Parquet文件時,因爲兼容性原因,所有的列都會自動轉換爲nullable(可爲空的)。
編程式加載數據
使用上面例子的數據:
可以從Spark倉庫的"examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala"找到完整的代碼。
分區發現
表分區是Hive等系統中常用的優化方法。在一個分區表中,數據常常存放在不同的目錄中,根據分區列的值的不同,編碼了每個分區目錄不同的路徑。目前parquet數據源已經可以自動的發現和推斷分區信息。例如,我們可以用下面的目錄結構存儲所有我們以前經常使用的數據到分區表,只需要額外的添加兩個列gender和country作爲分區列:
使用SparkSession.read.parquet或者SparkSession.read.load加載path/to/table後,Spark SQL能夠自動的從路徑中提取分區信息。返回的DataFrame的模式結構是:
注意分區列的數據類型是自動推斷的。目前支持數值型數據和字符串型數據。有時候用戶並不想自動推斷分區列的數據類型,這種情況下,可以通過配置spark.sql.sources.partitionColumnTypeInference.enabled這個參數來配置自動類型推斷,默認情況下是true。當關閉類型推斷後,分區列的類型將爲字符串型。
從Spark1.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將作爲分區列。
模式(schema)合併
與ProtocolBuffer,Avro,和Thrift類似,Parquet同樣支持schema的演變。用戶可以以一個簡單點的schema開始,然後在需要時逐漸的添加更多列到schema。使用這種方法,用戶將最終得到由不同的但是相互兼容的schema構成的多個Parquet文件。Parquet數據源目前可以自動的檢測這種情況並且合併這些文件的schema。
由於合併schema是相對代價較大的操作,而且在大多數情況下並不需要這樣,從1.5.0開始我們默認將它關閉,你可以通過以下方法使它生效:
- 在讀取Parquet文件時(就像下面的例子)設置數據源操作mergeSchema爲true
- 設置全局SQL選項spark.sql.parquet.mergeSchema爲true
可以在Spark倉庫的"examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala"找到完整的代碼
Hive原數據
當讀寫Hive元存儲Parquet表時,爲了更好的性能,SparkSQL將試圖使用它自己支持的Parquet代替Hive SerDe。這種行爲可以通過spark.sql.hive.convertMetastoreParquet進行配置,默認已經開啓。
Hive/Parquet schema調節
從表的schema處理的角度來看,Hive和Parquet有兩點關鍵的不同之處。
- Hive是類型敏感的,而Parquet並不是
- Hive中所有列都是非空的,而Parquet中非空是很重要的特性。
因爲這個原因,當我們需要將Hive元存儲轉換爲Spark SQL Parquet表中的Parquet表時,我們需要調節Hive元存儲的schema和Parquet的schema。調節規則如下:
- 不管是否可爲空值,兩種schema中具有相同名字的字段必須具有相同的數據類型。這種調節字段應該有與Parquet一方相同的數據類型,因此可爲空值的特性很重要。
- 調節的schema準確的包含在Hive元存儲schema中定義的字段。
- 任何只在Parquet schema中出現的字段都會在調節schema中被丟棄
- 任何只出現在Hive元存儲schema中的字段都會在調節schema中被添加爲可爲空的字段
元數據更新
爲了更好的性能,Spark SQL會緩存Parquet元數據。當Hive元存儲Parquet錶轉換操作可用時,這些被轉換的表的元數據同樣被緩存。如果這些表被Hive或者外部工具更新,你需要手動更新元數據以保持其一致性。
配置
Parquet的配置可以使用SparkSession中的setConf方法進行,或者使用SQL執行SET key=value命令。
JSON數據集
Spark SQL可以自動推斷JSON數據集的schema並且加載爲Dataset[Row]。可以對String類型的RDD或者JSON文件使用SparkSession.read.json()來實現這種轉換。
注意這裏的JSON文件不是通常意義的JSON文件。每一行必須包含分離的,完整有效的JSON對象。因此,不支持常用的多行式JSON文件。
注意,RDD[String]中每一個元素必須是一個字符串形式的JSON對象。
可以在Spark倉庫的"examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala"找到完整的代碼。
Hive表
Spark SQL同樣支持從Apache Hive中讀寫數據。但是,自從Hive有大量依賴之後,這些依賴就不包括在Spark發佈版中了。如果Hive的依賴可以在環境變量中找到,Spark將自動加載它們。注意這些Hive依賴項同樣必須在每個worker節點上存在,因爲他們需要訪問Hive序列化和反序列化庫以便可以訪問Hive中存儲的數據。
配置可以在conf/目錄中的hive-site.xml, core-site.xml(安全配置),和hdfs-site.xml(HDFS配置)這幾個文件中進行配置。
當在Hive上工作時,必須實例化SparkSession對Hive的支持,包括對持久化Hive元存儲的連通性,對Hive序列化反序列化,Hive用戶自定義函數的支持。當沒有在hive-site.xml配置是,context會自動在當前目錄創建metastore_db並且創建一個被spark.sql.warehouse.dir配置的目錄,默認在spark應用啓動的當前目錄的spark-warehouse。注意從Spark2.0.0開始hive-site.xml中的hive.metastore.warehouse.dir參數被棄用。作爲替代,使用spark.sql.warehouse.dir來指定倉庫中數據庫的位置。你可能需要授權寫權限給啓動spark應用的用戶。
可以在Spark倉庫的"examples/src/main/scala/org/apache/spark/examples/sql/hive/SparkHiveExample.scala"找到完整的代碼。
不同版本的Hive元存儲的交互
Spark SQL對Hive支持的最重要的特點之一是與Hive元存儲的交互,這使得SparkSQL可以訪問Hive表的元數據。從Spark1.4.0開始,可以使用一個Spark SQL的二進制構建來查詢不同版本的Hive元存儲。Spark SQL在內部編譯Hive1.2.1並且使用這些classes用於內部執行(序列化反序列化,UDFs,UDAFs等)
可以使用下面的選項來配置用於檢索元數據的Hive的版本:
其他數據庫的JDBC
Spark SQL同樣包括可以使用JDBC從去他數據庫讀取數據的數據源。此功能優先使用JdbcRDD.這是因爲返回的結果作爲一個DataFrame並且可以輕鬆地使用Spark SQL處理或者與其他數據源進行連接。使用Java或者Python可以更容易的使用JDBC數據源因爲它們不需要用戶提供ClassTag。(注意這與Spark SQLJDBC服務器可以允許其他應用使用Spark SQL執行查詢語句不同)
在開始之前你需要將你指定的數據庫的JDBC driver包含在Spark的環境變量中。例如,爲了從Spark Shell連接到postgres,你需要執行以下命令:
遠程數據庫的表可以被加載爲DataFrame或者使用Data Sources API加載爲Spark SQL臨時表。支持以下選項:
排錯
- JDBC driver類必須在客戶端和所有執行器上對原始類加載器可見。這是因爲Java的DriverManager類在用戶打開連接時,要進行安全檢查,檢查其中的結果已經忽略了所有的對原始類加載器不可見的部分。一個方便的方法是更改所有worker節點的compute_classpath.sh使其包含你的driver的JAR包
- 一些數據庫,比如H2,要求將所有的名字轉換爲大寫,你需要在Spark SQL中使用大寫。
性能調優
對一些工作負載,可以通過將數據緩存在內存中,在某些經驗項上進行調優來提高性能。
緩存數據到內存
Spark SQL可以通過調用 spark.cacheTable("tableName")或者dataFrame.cache()來將表以列式形式緩存在內存中。然後Spark SQL可以只掃描需要的列並且可以自動調節壓縮以最小內存使用率和GC壓力。你可以調用spark.uncacheTable("tableName")來將表從內存中刪除。
可以在SparkSession上使用setConf方法來配置內存緩存,或者使用SQL執行SET key=value命令。
其他配置選項
下面的選項同樣可以用於查詢語句執行時的性能調優。在以後的發佈版本中可能會棄用這些選項,更多的將優化改爲自動執行。
分佈式SQL引擎
Spark SQL同樣可以使用JDBC/ODBC或者命令行接口來作爲一個分佈式查詢引擎。在這種模式中,終端用戶或者應用可以通過執行SQL查詢語句直接與Spark SQL進行交互,不需要寫任何代碼。
運行Thrift JDBC/ODBC服務
Thrift JDBC/ODBC服務實現了與Hive1.2.1的一致性。你可以使用任意來自Spark或者Hive1.2.1的beeline script來測試JDBC服務。
在Spark目錄中運行以下命令來啓動JDBC/ODBC服務:
這個腳本接受所有bin/spark-submit的命令行選項,再加上可以執行Hive屬性的 --hiveconf選項。你可以運行./sbin/start-thriftserver.sh--help來顯示所有可用的選項的完整列表。默認情況下,此服務在localhost:10000進行監聽。你可以通過配置環境變量來改變此運行狀態,比如:
或者系統屬性:
現在你可以使用beeline來測試Thrift JDBC/ODBC服務:
在beeline中使用以下命令來連接到JDBC/ODBC:
beeline將會詢問用戶名和密碼。在非安全模式,可以簡單地在你的機器上輸入用戶名和空白的密碼。在安全模式下,請遵照beeline documentation中給出的說明。
Hive的配置是在conf/目錄下的 hive-site.xml,core-site.xml和hdfs-site.xml文件中進行配置的。
你也可以使用來自Hive的beeline script。
Thrift JDBC服務同樣支持在HTTP傳輸上發送 thrift RPC消息。在系統屬性或者在conf/目錄中的hive-site.xml文件中進行以下設置來將模式設爲HTTP模式:
在http模式中使用beeline連接到JDBC/ODBC來進行測試:
運行Spark SQL命令行界面
Spark SQL CLI是一個在本地模式下運行Hive元存儲服務和執行從命令行輸入的查詢語句的便捷的工具。注意Spark SQL CLI和ThriftJDBC服務不能通信。
你可以在Spark目錄中運行以下命令來開始Spark SQL CLI:
Hive的配置是在conf/目錄下的hive-site.xml, core-site.xml和hdfs-site.xml文件中進行配置的。你可以運行./sbin/start-thriftserver.sh--help來顯示所有可用的選項的完整列表。