撰寫本文時 Spark 的最新版本為 2.0.0
概述
Spark SQL 是 Spark 用來處理結構化數據的一個模塊。與基礎的 Spark RDD API 不同,Spark SQL 提供了更多數據與要執行的計算的信息。在其實現中,會使用這些額外信息進行優化。可以使用 SQL 語句和 Dataset API 來與 Spark SQL 模塊交互。無論你使用哪種語言或 API 來執行計算,都會使用相同的引擎。這讓你可以選擇你熟悉的語言(現支持 Scala、Java、R、Python)以及在不同場景下選擇不同的方式來進行計算。
SQL
一種使用 Spark SQL 的方式是使用 SQL。Spark SQL 也支持從 Hive 中讀取數據,如何配置將會在下文中介紹。使用編碼方式來執行 SQL 將會返回一個 Dataset/DataFrame。你也可以使用命令行,JDBC/ODBC 與 Spark SQL 進行交互。
Datasets 和 DataFrames
Dataset 是一個分布式數據集合。Dataset 是自 Spark 1.6開始提供的新接口,能同時享受到 RDDs 的優勢(強類型,能使用強大的 lambda 函數)以及 Spark SQL 優化過的執行引擎。Dataset 可以從 JVM 對象(s)創建而來并且可以使用各種 transform 操作(比如 map,flatMap,filter 等)。目前 Dataset API 支持 Scala 和 Java。Python 暫不支持 Dataset API。不過得益于 Python 的動態屬性,可以享受到許多 DataSet API 的益處。R 也是類似情況。
DataFrame 是具有名字的列。概念上相當于關系數據庫中的表或 R/Python 下的 data frame
,但有更多的優化。DataFrames(Dataset 亦是如此) 可以從很多數據中構造,比如:結構化文件、Hive 中的表,數據庫,已存在的 RDDs。DataFrame API 可在 Scala、Java、Python 和 R 中使用。在 Scala 和 Java 中,DataFrame 由一個元素為 Row 的 Dataset 表示。在 Scala API 中,DataFrame 只是 Dataset[Row]
的別名。在 Java API 中,類型為 Dataset<Row>
。
在本文剩余篇幅中,會經常使用 DataFrame 來代指 Scala/Java 元素為 Row 的 Dataset。
開始
起始點:SparkSession
SparkSession 類是到 Spark SQL 所有功能的入口點,只需調用 SparkSession.builder()
即可創建:
import org.apache.spark.sql.SparkSession
val spark = SparkSession
.builder()
.appName("Spark SQL Example")
.config("spark.some.config.option", "some-value")
.getOrCreate()
// 包含隱式轉換(比如講 RDDs 轉成 DataFrames)API
import spark.implicits._
Spark 2.0中的 SparkSession對于 Hive 的各個特性提供了內置支持,包括使用 HiveQL 編寫查詢語句,使用 Hive UDFs 以及從 Hive 表中讀取數據。
創建 DataFrames
使用 SparkSession,可以從已經在的 RDD、Hive 表以及 Spark 支持的數據格式創建。下面這個例子就是讀取一個 Json 文件來創建一個 DataFrames:
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 操作(又名 DataFrame 操作)
DataFrames提供特定于域的語言結構化數據操作。如上所述,在 Spark 2.0 中,DataFrames 是元素為 Row 的 Dataset 在 Scala 和 Java API 中。相較于強類型的 Scala/Java Dataset 的“有類型操作”,DataFrame 上的操作又被稱為“無類型操作”。
下面給出一些使用 Dataset 處理結構化數據的基本例子:
// 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|
// +----+-----+
要查看 Dataset 支持的所有操作,請移步 Dataset API 文檔。
除了簡單的列引用和表達式,Datasets 豐富的函數庫還提供了包括字符串操作,日期操作,內容匹配操作等函數。完整的列表請移步DataFrame 函數列表
創建 Datasets
Dataset 與 RDD 類似,但它使用一個指定的編碼器進行序列化來代替 Java 自帶的序列化方法或 Kryo 序列化。盡管該編碼器和標準序列化是負責將對象轉換成字節,編碼器是動態生成的,并提供一種格式允許 Spark 直接執行許多操作,比如 filter、sort 和 hash 等而不用將字節數據反序列化成對象。
// Note: Case classes in Scala 2.10 can support only up to 22 fields. To work around this limit,
// you can use custom classes that implement the Product interface
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)
// 通過提供一個類,類各個成員名與 Row 各個字段名相對應,DataFrames可以轉換為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 互操作
Spark SQL 支持兩種不同的方式將 RDDs 轉換為 Datasets。第一種方法是使用反射來推斷包含指定類對象元素的 RDD 的模式。利用這種方法能讓代碼更簡潔。
創建 Datasets 的第二種方法通過接口構造一個模式來應用于現有的 RDD。雖然這種方法要少復雜一些,但允許在列及其類型直到運行時才知道的情況下構造 Datasets。
使用反射來推斷模式
Spark SQL 的 Scala 接口支持將元素類型為 case class 的 RDD 自動轉為 DataFrame。case class 定義了表的模式。case class 的參數名將變成對應列的列名。case class 可以嵌套,也可以包含復合類型,比如 Seqs 或 Arrays。元素為 case class 的 RDD 可以轉換成 DataFrame 并可以注冊為表進而執行 sql 語句查詢。
import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder
import org.apache.spark.sql.Encoder
// 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, Int]] = 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))
編碼指定模式
如果不能預先定義 case class(比如,每條記錄都是字符串,不同的用戶會使用不同的字段),那么可以通過以下三步來創建 DataFrame:
- 將原始 RDD 轉換為 Row RDD
- 根據步驟1中的 Row 的結構創建對應的 StructType 模式
- 通過 SparkSession 提供的 createDataFrame 來把第2步創建的模式應用到第一步轉換得到的 Row 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|
// +-------------+
數據源
Spark SQL 通過 DataFrame 可以操作多種類型數據。DataFrame 可以創建臨時表,創建了臨時表后就可以在上面執行 sql 語句了。本節主要介紹 Spark 數據源的加載與保存以及一些內置的操作。
通用的 Load/Sava 函數
最簡單的方式是調用 load 方法加載文件,默認的格式為 parquet(可以通過修改 spark.sql.sources.default
來指定默認格式)
val usersDF = spark.read.load("examples/src/main/resources/users.parquet")
usersDF.select("name", "favorite_color").write.save("namesAndFavColors.parquet")
手動指定格式
也可以手動指定加載數據的格式以及要保存的數據的格式
val peopleDF = spark.read.format("json").load("examples/src/main/resources/people.json")
peopleDF.select("name", "age").write.format("parquet").save("namesAndAges.parquet")
在文件夾上執行 SQL
除了使用 read API,還可以在對文件夾的所有文件執行 SQL 查詢
val sqlDF = spark.sql("SELECT * FROM parquet.`examples/src/main/resources/users.parquet`")
保存模式
執行保存操作時可以指定一個 SaveMode,SaveMode 指定了如果指定的數據已存在該如何處理。需要意識到保存操作不使用鎖也不是原子操作。另外,如果指定了覆蓋模式,會在寫入新數據前將老數據刪除
Scala/Java | 其他語言 | 含義 |
---|---|---|
SaveMode.ErrorIfExists (default) | "error" (default) | 當保存一個DataFrame 數據至數據源時,如果該位置數據已經存在,則會拋出一個異常 |
SaveMode.Append | "append" | 當保存一個DataFrame 數據至數據源時,如果該位置數據已經存在,則將DataFrame 數據追加到已存在的數據尾部 |
SaveMode.Overwrite | "overwrite" | 當保存一個DataFrame 數據至數據源時,如果該位置數據已經存在,則覆蓋元數據(先刪除元數據,再保存 DataFrame 數據) |
SaveMode.Ignore | "ignore" | 當保存一個DataFrame 數據至數據源時,如果該位置數據已經存在,則不執行任何操作;若不存在,則保存 DataFrame(有點像 CREATE TABLE IF NOT EXISTS) |
保存數據到永久表
DataFrame 也可以通過調用 saveAsTable
方法將數據保存到 Hive 表中。注意,當前已部署的 hive 不會受到影響。Spark 會創建本地的 metastore(使用 Derby)。與 createOrReplaceTempView
不同,saveAsTable
會持久化數據并指向 Hive metastore。在你重啟 Spark Application 后,永久表依舊存在,只要你連接了保存時相同的 metastore 依舊能訪問到完整的數據。用來保存數據到永久表的 DataFrame 可以通過調用 SparkSession 的 table 方法來創建。
saveAsTable
默認會創建一個 “受管理表”,意味著數據的位置都是受 metastore 管理的。當 “受管理表” 被刪除,其對應的數據也都會被刪除。
Parquet 格式
Parquet 是很多數據處理系統都支持的列存儲格式,其相對于行存儲具有以下優勢:
- 可以跳過不符合條件的數據,只讀取需要的數據,降低 IO 數據量
- 壓縮編碼可以降低磁盤存儲空間。由于同一列的數據類型是一樣的,可以使用更高效的壓縮編碼進一步節省存儲空間
- 只讀取需要的列,支持向量運算,能夠獲取更好的掃描性能
Spark SQL 支持讀寫 Parquet 格式數據。當寫 Parquet 數據時,為了兼容性,所有的列會自動轉為 nullable
編碼讀寫 Parquet 文件
// 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|
// +------------+
分區發現
表分區是像 Hive 的這種系統常用的優化方法。在一個分區的表中,數據往往存儲在不同的目錄,分區列被編碼存儲在各個分區目錄。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 的模式如下:
root
|-- name: string (nullable = true)
|-- age: long (nullable = true)
|-- gender: string (nullable = true)
|-- country: string (nullable = true)
注意,用來分區的列的數據類型是自動推斷的,當前支持數字類型和 String 類型。如果你不希望自動推斷分區列的類型,將 spark.sql.sources.partitionColumnTypeInference.enabled
設置為 false 即可,該值默認為 true。若設為 false,則會禁用分區列類型推斷而直接設置為 String 類型。
自 Spark 1.6.0 起,分區發現只會發現指定路徑下的分區。在上面的例子中,如果用戶傳入路徑 path/to/table/gender=male
,則 gender 將不會成為一個分區列。如果用戶即只想訪問 path/to/table/gender=male
下的數據,又希望 gender 能成為分區列,可以使用 basePath 選項,如將 basePath 設置為 path/to/table/
,依舊傳 path/to/table/gender=male
給spark.sql.sources.partitionColumnTypeInference.enabled
,那么 gender 依舊會成為分區列。
合并模式
與 ProtocolBuffer,Avro 和 Thrift 類似,Parquet 也支持模式演進。用戶可以從簡單的模式開始,之后根據需要逐步增加列。通過這種方式,最終可能會形成不同但互相兼容的多個 Parquet 文件。Parquet 數據源現在可以自動檢測這種情況并合并這些文件。
由于模式合并是消耗比較高的操作,而且在大多數情況下都不是必要的,自 1.5.0 開始默認關閉該功能。你可以通過以下方式啟用:
- 當讀取 Parquet 文件時,將
mergeSchema
選項設置為 true,下面代碼中有示例,或 - 設置
spark.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 表
Spark SQL 也支持從 Hive 中讀取數據以及保存數據到 Hive 中。然后,由于 Hive 有大量依賴,默認部署的 Spark 不包含這些依賴。可以將 Hive 的依賴添加到 classpath,Spark 將自動加載這些依賴。注意,這些依賴也必須分發到各個節點,因為需要通過 Hive 序列化和反序列化庫來讀取 Hive 數據和將數據寫入 Hive。
配置上需要做的是將 hive-site.xml, core-site.xml (如果有安全相關配置) 以及 hdfs-site.xml拷貝到 $SPARK_HOME/conf
目錄下。
當和 Hive 協作時,需要實例化一個支持 Hive 的 SparkSession。即使沒有現成部署好的 Hive 依舊可以啟用 Hive 支持。當沒有使用 hive-site.xml 進行配置時,會自動的在當前目錄創建 metastore_db 并在 spark.sql.warehouse.dir
指定的目錄創建一個目錄,用作 spark-warehouse
目錄。需要注意的是,hive-site.xml 中的 hive.metastore.warehouse.dir
自 Spark 2.0.0 依賴被啟用,取而代之,使用 spark.sql.warehouse.dir
來指定 warehouse 數據庫的默認目錄。另外,你需要給啟動該 Application 的用戶這個目錄的寫權限。
import org.apache.spark.sql.Row
import org.apache.spark.sql.SparkSession
case class Record(key: Int, value: String)
// warehouseLocation points to the default location for managed databases and tables
val warehouseLocation = "file:${system:user.dir}/spark-warehouse"
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)")
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 DaraFrames 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 HiveContext.
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|
// | 2| val_2| 2| val_2|
// | 4| val_4| 4| val_4|
// ...
通過 JDBC 連接其他數據庫
Spark SQL 也支持通過 JDBC 來訪問其他數據庫的數據。使用這種方式將返回 DataFrame,并且 Spark SQL 可以輕易處理或與其他數據做 join 操作,所以我們應該優先使用這種方式而不是 JdbcRDD。
在使用時,需要將對應數據庫的 JDBC driver 包含到 spark classpath 中。比如下面的例子是通過 Spark Shell 鏈接 postgre 數據庫:
bin/spark-shell --driver-class-path postgresql-9.4.1207.jar --jars postgresql-9.4.1207.jar
遠程數據庫中的數據可以被加載為 DataFrame 或 Spark SQL 臨時表,支持以下選項:
選項 | 含義 |
---|---|
url | 要連接的 JDBC url |
dbtable | 要讀取的 JDBC 庫和表。任何在 SQL 查詢的 FROM 子句中支持的形式都支持,比如,用括號包括的 SQL 子查詢 |
driver | 用來連接 JDBC url 的 JDBC driver 的類名 |
partitionColumn, lowerBound, upperBound, numPartitions | 只要為這其中的一個選項指定了值就必須為所有選項都指定值。這些選項描述了多個 workers 并行讀取數據時如何分區。lowerBound 和 upperBound 用來指定分區邊界,而不是用來過濾表中數據的,因為表中的所有數據都會被讀取并分區 |
fetchSize | 定義每次讀取多少條數據,這有助于提升讀取的性能和穩定性。如果一次讀取過多數據,容易因為網絡原因導致失敗 |
一個簡單的示例如下:
val jdbcDF = spark.read.format("jdbc").options(
Map("url" -> "jdbc:postgresql:dbserver",
"dbtable" -> "schema.tablename")).load()
性能調優
對于很多 Application,我們可以通過緩存數據至內存或調整一些選項來進行性能調優。
緩存數據至內存
Spark SQL 通過調用 spark.cacheTable
或 dataFrame.cache()
來將表以列式形式緩存到內存。Spark SQL會只會緩存需要的列并且會進行壓縮以減小內存消耗和 GC 壓力。可以調用 spark.uncacheTable("tableName")
將表中內存中移除。
可以調用 SparkSession 的 setConf
方法來設置內存緩存的參數:
選項 | 默認值 | 含義 |
---|---|---|
spark.sql.inMemoryColumnarStorage.compressed | true | 若設置為 true,Spark SQL 會根據每列的類型自動為每列選擇一個壓縮器進行數據壓縮 |
spark.sql.inMemoryColumnarStorage.batchSize | 10000 | 設置一次處理多少 row,更大的值有助于提升內存使用率和壓縮率,但要注意避免 OOMs |
其他配置項
調整以下選項也能改善查詢性能,由于一些優化可能會在以后的版本中自動化,所以以下選項可能會在以后被棄用
選項名 | 默認值 | 含義 |
---|---|---|
spark.sql.files.maxPartitionBytes | 134217728 (128 MB) | 一個分區的最大 size,單位為字節 |
spark.sql.autoBroadcastJoinThreshold | 10485760 (10 MB) | 自動廣播的小表的最大 size,以字節為單位。設置為 -1 可禁用廣播表。注意,當前只支持執行了 ANALYZE TABLE <tableName> COMPUTE STATISTICS noscan 的 Hive Metastore 表 |
spark.sql.shuffle.partitions | 200 | 執行 join 和聚合操作時,shuffle 操作的分區數 |
分布式 SQL 引擎
使用 JDBC/ODBC 或命令行接口,Spark SQL 還可以作為一個分布式查詢引擎。在該模式下,終端用戶或 Application 可以直接執行 SQL 查詢,而不用寫任何代碼。
JDBC/ODBC thrift 服務
這里的 JDBC/ODBC 服務對應于 Hive 1.2.1 中的 HiveServer2,可以通過 beeline 腳本來測試特服務。首先執行下面的命令啟動 JDBC/ODBC 服務:
./sbin/start-thriftserver.sh
該腳本接受所有 bin/spark-submit
的參數,另外還可以通過 --hiveconf
選項來指定 Hive 屬性。該服務默認監聽 localhost:10000
,可以通過設置環境變量值來修改:
export HIVE_SERVER2_THRIFT_PORT=<listening-port>
export HIVE_SERVER2_THRIFT_BIND_HOST=<listening-host>
./sbin/start-thriftserver.sh \
--master <master-uri> \
...
或通過 --hiveconf
設置:
./sbin/start-thriftserver.sh \
--hiveconf hive.server2.thrift.port=<listening-port> \
--hiveconf hive.server2.thrift.bind.host=<listening-host> \
--master <master-uri>
...
然后使用 beeline 來測試 JDBC/ODBC 服務:
./bin/beeline
使用 beeline 連接 JDBC/ODBC 服務:
beeline> !connect jdbc:hive2://localhost:10000
Beeline 需要你提供一個用戶名和密碼。在非安全模式中,鍵入機器用戶名和空密碼即可;在安全模式中,可以按照 beeline 進行設置
Thrift JDBC server 也支持通過 HTTP 傳輸 RPC 消息,如下設置系統參數或 hive-site.xml 啟用 HTTP 模式:
hive.server2.transport.mode - Set this to value: http
hive.server2.thrift.http.port - HTTP port number fo listen on; default is 10001
hive.server2.http.endpoint - HTTP endpoint; default is cliservice
使用 beeline 來連接 HTTP 模式下的 JDBC/ODBC thrift server:
beeline> !connect jdbc:hive2://<host>:<port>/<database>?hive.server2.transport.mode=http;hive.server2.thrift.http.path=<http_endpoint>
運行 Spark SQL CLI
Spark SQL CLI 是一個很方便的工具,用來以 local 模式執行 Hive metastore 服務和執行查詢。注意,Spark SQL CLI 無法和 JDBC thrift server,執行下面命令啟動 Spark SQL CLI:
./bin/spark-sql
與 Hive 的兼容性
Spark SQL 被設計成與 Hive Metastore、SerDes 和 UDFs 兼容,并且可以與 Hive 各個版本寫作(從0.12.0到1.2.1)。
Spark SQL thrift server 可以與現有已安裝的 Hive 兼容,不需要修改當前的 Hive Metastore 或表數據的存放位置。
支持及不支持的 Hive 特性以及具體的數據類型請移步:
https://spark.apache.org/docs/latest/sql-programming-guide.html#compatibility-with-apache-hive
歡迎關注我的微信公眾號:FunnyBigData