spark 基礎知識- spark SQL專題

一、簡介

  Spark SQL是Spark中處理結構化數據的模塊。與基礎的Spark RDD API不同,Spark SQL的接口提供了更多關於數據的結構信息和計算任務的運行時信息。在Spark內部,Spark SQL會能夠用於做優化的信息比RDD API更多一些。Spark SQL如今有了三種不同的API:SQL語句、DataFrame API和最新的Dataset API。不過真正運行計算的時候,無論你使用哪種API或語言,Spark SQL使用的執行引擎都是同一個。這種底層的統一,使開發者可以在不同的API之間來回切換,你可以選擇一種最自然的方式,來表達你的需求。
(本文針對spark1.6版本,示例語言爲Scala)
二、概念

1. SQL。Spark SQL的一種用法是直接執行SQL查詢語句,你可使用最基本的SQL語法,也可以選擇HiveQL語法。Spark SQL可以從已有的Hive中讀取數據。更詳細的請參考Hive Tables 這一節。如果用其他編程語言運行SQL,Spark SQL將以DataFrame返回結果。你還可以通過命令行command-line 或者 JDBC/ODBC 使用Spark SQL。

2. DataFrame。是一種分佈式數據集合,每一條數據都由幾個命名字段組成。概念上來說,她和關係型數據庫的表 或者 R和Python中的data frame等價,只不過在底層,DataFrame採用了更多優化。DataFrame可以從很多數據源(sources)加載數據並構造得到,如:結構化數據文件,Hive中的表,外部數據庫,或者已有的RDD。
DataFrame API支持Scala, Java, Python, and R。


3. Datasets。是Spark-1.6新增的一種API,目前還是實驗性的。Dataset想要把RDD的優勢(強類型,可以使用lambda表達式函數)和Spark SQL的優化執行引擎的優勢結合到一起。Dataset可以由JVM對象構建(constructed )得到,而後Dataset上可以使用各種transformation算子(map,flatMap,filter 等)。
Dataset API 對 Scala 和 Java的支持接口是一致的,但目前還不支持Python,不過Python自身就有語言動態特性優勢(例如,你可以使用字段名來訪問數據,row.columnName)。對Python的完整支持在未來的版本會增加進來。
三、創建並操作DataFrame

  Spark應用可以用SparkContext創建DataFrame,所需的數據來源可以是已有的RDD(existing RDD ),或者Hive表,或者其他數據源(data sources.)以下是一個從JSON文件創建並操作DataFrame的小例子:

val sc: SparkContext // 已有的 SparkContext.
val sqlContext = new org.apache.spark.sql.SQLContext(sc)

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

// 將DataFrame內容打印到stdout
df.show()
// age  name
// null Michael
// 30   Andy
// 19   Justin

// 打印數據樹形結構
df.printSchema()
// root
// |-- age: long (nullable = true)
// |-- name: string (nullable = true)

// select "name" 字段
df.select("name").show()
// name
// Michael
// Andy
// Justin

// 展示所有人,但所有人的 age 都加1
df.select(df("name"), df("age") + 1).show()
// name    (age + 1)
// Michael null
// Andy    31
// Justin  20

// 篩選出年齡大於21的人
df.filter(df("age") > 21).show()
// age name
// 30  Andy

// 計算各個年齡的人數
df.groupBy("age").count().show()
// age  count
// null 1
// 19   1
// 30   1

SQLContext.sql可以執行一個SQL查詢,並返回DataFrame結果。

val sqlContext = ... // 已有一個 SQLContext 對象
val df = sqlContext.sql("SELECT * FROM table")

三、spark SQL與RDD互操作

  Spark SQL有兩種方法將RDD轉爲DataFrame。分別爲反射機制和編程方式。
1. 利用反射推導schema。

  Spark SQL的Scala接口支持自動將包含case class對象的RDD轉爲DataFrame。對應的case class定義了表的schema。case class的參數名通過反射,映射爲表的字段名。case class還可以嵌套一些複雜類型,如Seq和Array。RDD隱式轉換成DataFrame後,可以進一步註冊成表。隨後,你就可以對錶中數據使用 SQL語句查詢了。

// sc 是已有的 SparkContext 對象
val sqlContext = new org.apache.spark.sql.SQLContext(sc)
// 爲了支持RDD到DataFrame的隱式轉換
import sqlContext.implicits._

// 定義一個case class.
// 注意:Scala 2.10的case class最多支持22個字段,要繞過這一限制,Scala-2.11已經解決這個問題

// 你可以使用自定義class,並實現Product接口。當然,你也可以改用編程方式定義schema

https://stackoverflow.com/questions/20258417/how-to-get-around-the-scala-case-class-limit-of-22-fields

class Demo(val field1: String,
    val field2: Int,
    // .. and so on ..
    val field23: String)

extends Product 
//For Spark it has to be Serializable
with Serializable {
    def canEqual(that: Any) = that.isInstanceOf[Demo]

    def productArity = 23 // number of columns

    def productElement(idx: Int) = idx match {
        case 0 => field1
        case 1 => field2
        // .. and so on ..
        case 22 => field23
    }
}

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

// 創建一個包含Person對象的RDD,並將其註冊成table
val people = sc.textFile("examples/src/main/resources/people.txt").map(_.split(",")).map(p => Person(p(0), p(1).trim.toInt)).toDF()
people.registerTempTable("people")

// sqlContext.sql方法可以直接執行SQL語句
val teenagers = sqlContext.sql("SELECT name, age FROM people WHERE age >= 13 AND age <= 19")

// SQL查詢的返回結果是一個DataFrame,且能夠支持所有常見的RDD算子
// 查詢結果中每行的字段可以按字段索引訪問:
teenagers.map(t => "Name: " + t(0)).collect().foreach(println)

// 或者按字段名訪問:
teenagers.map(t => "Name: " + t.getAs[String]("name")).collect().foreach(println)

// row.getValuesMap[T] 會一次性返回多列,並以Map[String, T]爲返回結果類型
teenagers.map(_.getValuesMap[Any](List("name", "age"))).collect().foreach(println)
// 返回結果: Map("name" -> "Justin", "age" -> 19)

2. 編程方式定義Schema。

  如果不能事先通過case class定義schema(例如,記錄的字段結構是保存在一個字符串,或者其他文本數據集中,需要先解析,又或者字段對不同用戶有所不同),那麼你可能需要按以下三個步驟,以編程方式的創建一個DataFrame:

  從已有的RDD創建一個包含Row對象的RDD,用StructType創建一個schema,和步驟1中創建的RDD的結構相匹配,把得到的schema應用於包含Row對象的RDD,調用這個方法來實現這一步:SQLContext.createDataFrame
例如:

// sc 是已有的SparkContext對象
val sqlContext = new org.apache.spark.sql.SQLContext(sc)

// 創建一個RDD
val people = sc.textFile("examples/src/main/resources/people.txt")

// 數據的schema被編碼與一個字符串中
val schemaString = "name age"

// Import Row.
import org.apache.spark.sql.Row;

// Import Spark SQL 各個數據類型
import org.apache.spark.sql.types.{StructType,StructField,StringType};

// 基於前面的字符串生成schema
val schema =
  StructType(
    schemaString.split(" ").map(fieldName => StructField(fieldName, StringType, true)))

// 將RDD[people]的各個記錄轉換爲Rows,即:得到一個包含Row對象的RDD
val rowRDD = people.map(_.split(",")).map(p => Row(p(0), p(1).trim))

// 將schema應用到包含Row對象的RDD上,得到一個DataFrame
val peopleDataFrame = sqlContext.createDataFrame(rowRDD, schema)

// 將DataFrame註冊爲table
peopleDataFrame.registerTempTable("people")

// 執行SQL語句
val results = sqlContext.sql("SELECT name FROM people")

// SQL查詢的結果是DataFrame,且能夠支持所有常見的RDD算子
// 並且其字段可以以索引訪問,也可以用字段名訪問
results.map(t => "Name: " + t(0)).collect().foreach(println)

四、spark SQL與其它數據源的連接與操作

  Spark SQL支持基於DataFrame操作一系列不同的數據源。DataFrame既可以當成一個普通RDD來操作,也可以將其註冊成一個臨時表來查詢。把 DataFrame註冊爲table之後,你就可以基於這個table執行SQL語句了。本節將描述加載和保存數據的一些通用方法,包含了不同的 Spark數據源,然後深入介紹一下內建數據源可用選項。
  在最簡單的情況下,所有操作都會以默認類型數據源來加載數據(默認是Parquet,除非修改了spark.sql.sources.default 配置)。

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

  你也可以手動指定數據源,並設置一些額外的選項參數。數據源可由其全名指定(如,org.apache.spark.sql.parquet),而 對於內建支持的數據源,可以使用簡寫名(json, parquet, jdbc)。任意類型數據源創建的DataFrame都可以用下面這種語法轉成其他類型數據格式。

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

  Spark SQL還支持直接對文件使用SQL查詢,不需要用read方法把文件加載進來。

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

1. 連接JSON數據集

  Spark SQL在加載JSON數據的時候,可以自動推導其schema並返回DataFrame。用SQLContext.read.json讀取一個包含String的RDD或者JSON文件,即可實現這一轉換。

注意,通常所說的json文件只是包含一些json數據的文件,而不是我們所需要的JSON格式文件。JSON格式文件必須每一行是一個獨立、完整的的JSON對象。因此,一個常規的多行json文件經常會加載失敗。

// sc是已有的SparkContext對象
val sqlContext = new org.apache.spark.sql.SQLContext(sc)

// 數據集是由路徑指定的
// 路徑既可以是單個文件,也可以還是存儲文本文件的目錄
val path = "examples/src/main/resources/people.json"
val people = sqlContext.read.json(path)

// 推導出來的schema,可由printSchema打印出來
people.printSchema()
// root
//  |-- age: integer (nullable = true)
//  |-- name: string (nullable = true)

// 將DataFrame註冊爲table
people.registerTempTable("people")

// 跑SQL語句吧!
val teenagers = sqlContext.sql("SELECT name FROM people WHERE age >= 13 AND age <= 19")

// 另一種方法是,用一個包含JSON字符串的RDD來創建DataFrame
val anotherPeopleRDD = sc.parallelize(
  """{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}""" :: Nil)
val anotherPeople = sqlContext.read.json(anotherPeopleRDD)

2. 連接Hive表

  Spark SQL支持從Apache Hive讀 寫數據。然而,Hive依賴項太多,所以沒有把Hive包含在默認的Spark發佈包裏。要支持Hive,需要在編譯spark的時候增加-Phive和 -Phive-thriftserver標誌。這樣編譯打包的時候將會把Hive也包含進來。注意,hive的jar包也必須出現在所有的worker節 點上,訪問Hive數據時候會用到(如:使用hive的序列化和反序列化SerDes時)。
  Hive配置在conf/目錄下hive-site.xml,core-site.xml(安全配置),hdfs-site.xml(HDFS配 置)文件中。請注意,如果在YARN cluster(yarn-cluster mode)模式下執行一個查詢的話,lib_mananged/jar/下面的datanucleus 的jar包,和conf/下的hive-site.xml必須在驅動器(driver)和所有執行器(executor)都可用。一種簡便的方法是,通過 spark-submit命令的–jars和–file選項來提交這些文件。
  如果使用Hive,則必須構建一個HiveContext,HiveContext是派生於SQLContext的,添加了在Hive Metastore裏查詢表的支持,以及對HiveQL的支持。用戶沒有現有的Hive部署,也可以創建一個HiveContext。如果沒有在 hive-site.xml裏配置,那麼HiveContext將會自動在當前目錄下創建一個metastore_db目錄,再根據HiveConf設置 創建一個warehouse目錄(默認/user/hive/warehourse)。所以請注意,你必須把/user/hive/warehouse的 寫權限賦予啓動spark應用程序的用戶。

// sc是一個已有的SparkContext對象
val sqlContext = new org.apache.spark.sql.hive.HiveContext(sc)

sqlContext.sql("CREATE TABLE IF NOT EXISTS src (key INT, value STRING)")
sqlContext.sql("LOAD DATA LOCAL INPATH 'examples/src/main/resources/kv1.txt' INTO TABLE src")

// 這裏用的是HiveQL
sqlContext.sql("FROM src SELECT key, value").collect().foreach(println)

3. 用JDBC連接其他數據庫

  Spark SQL也可以用JDBC訪問其他數據庫。這一功能應該優先於使用JdbcRDD。因爲它返回一個DataFrame,而DataFrame在Spark SQL中操作更簡單,且更容易和來自其他數據源的數據進行交互關聯。JDBC數據源在java和python中用起來也很簡單,不需要用戶提供額外的 ClassTag。(注意,這與Spark SQL JDBC server不同,Spark SQL JDBC server允許其他應用執行Spark SQL查詢)
  首先,你需要在spark classpath中包含對應數據庫的JDBC driver,下面這行包括了用於訪問postgres的數據庫driver
SPARK_CLASSPATH=postgresql-9.3-1102-jdbc41.jar bin/spark-shell

val jdbcDF = sqlContext.read.format("jdbc").options(
  Map("url" -> "jdbc:postgresql:dbserver",
  "dbtable" -> "schema.tablename")).load()

注意:

    JDBC driver class必須在所有client session或者executor上,對java的原生classloader可見。這是因爲Java的DriverManager在打開一個連接之 前,會做安全檢查,並忽略所有對原聲classloader不可見的driver。最簡單的一種方法,就是在所有worker節點上修改 compute_classpath.sh,幷包含你所需的driver jar包。
    一些數據庫,如H2,會把所有的名字轉大寫。對於這些數據庫,在Spark SQL中必須也使用大寫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章