Spark SQL 基礎

一、爲什麼要用SQL

  爲什麼大數據需要SQL。爲什麼SQL已經誕生這麼長時間還在使用。爲什麼說一個框架不能落地到SQL上就不是一個好的框架。其實這個和SQL有關。

  • SQL是用來統計數據信息的一種方式。比如統計一個班有多少男生,多少人上課遲到。大數據也需要處理這些統計信息,所以需要SQL。
  • MySQL,Oracle等都是單機文件存儲,因爲數據量逐漸變大,性能跟不上,所以雲化成大數據,以前的業務也需要隨之雲化,所以需要SQL。
  • 轉變一個傳統RDBMS工程師成爲一個大數據工程師的成本比業務SQL化高太多,同時也會增加社會不定因數,所以需要SQL。
  • 方便,不需要重新編譯執行,SQL方便維護。

  等等,都是一個好的SQL框架萬衆期待的原因。但是Spark SQL並不只是一個SQL。

二、SQL on Hadoop

  有哪些SQL框架可以跑在Hadoop生態之上。

  • Hive:facebook開源的,可以運行MapReduce/Tez/Spark引擎之上。
  • Impala:cloudare開源的,使用CM安裝會非常方便。
  • Presto:JD正在使用
  • Drill:可以混合Hbase,Hadoop的編程
  • Phonenix:使得Hbase可以用SQL查詢
  • Spark SQL:Spark處理結構化數據的模塊

三、Saprk SQL

  Spark SQl不僅僅是寫SQL,而是Spark處理結構化數據的一個模塊。所以Spark SQL中不僅寫SQL,還有DF(DataFrames)API、DS(Datasets)API。
  Spark RDD的入口是SparkContext,SparkSQL的入口是SparkSession。
  Spark SQL架構:
在這裏插入圖片描述

四、Spark SQL操作

4.1 配置和啓動

  常常聽到說Spark SQL操作Hive,這裏說的並不是要啓動Hive然後Spark去連接Hive,操作SQL。而是說Hive在HDFS上建立的一些表等可以Spark SQL連接meta得到信息,並且操作和配置起來像Hive一樣。這都要歸功於通用meta。
  既然需要Hive的meta信息,所以配置的時候需要將hive的hive-site.xml拷貝到${SPARK_HOME/conf/}下。因爲Hive的meta連接到的是MySQL,所以還需要配置MySQL的驅動。添加這個驅動放到${SPARK_HOME/jars}下面,或者可以在手動添加:

./spark-shell --master local[2] --jars  ~/mysql-connector-java-5.1.46-bin.jar

  有一個小小的意外,–jars 不一定能包含到classpath中,這樣在啓動任務的時候會各種報錯,所以可以使用 --driver-class-path來修正。
  測試一下:

spark.sql("show databases").show

  使用spark-shell編程,需要加如SQL的一些API接口,如spqrk.sql等,不是很方便,也可以使用spark-sql,後面帶的參數和spark-shell一樣。

4.2 catch

  在業務場景中需要緩存一張表到內存怎樣緩存呢。可以使用 spark.catalog.catchTable(" table name") 或者 dataframe.catch( )。如果在spark-sql中,使用catch table tableName。
  Spark Core中catch是一個lazy的一個過程,但是在Spark SQL中這是一個實時過程。

五、Spark SQL外部數據源

  什麼是外部數據源。就是在不同的文件系統(HDFS/S3/jdbc等)的文件(json、parquet等)或者有帶壓縮需要加載到spark內做計算。Spark SQL需要知道這些格式獲取、寫入數據,這就是外部數據源。
  簡單使用,比如讀取parquet的文件然後轉化成json數據:

val parquetDF = sparkContext.read.format("parquet").load("file:///home/a.parquet")
parquetDF.write.format("json").save("file:///home/")

  大體上分爲兩類內建的外部數據源API(如HDFS、OSS、S3等)、第三方數據源(如redis、MongoDB等,可以訪問spark-packages.org查找)。
  如果需要自定義外部數據源,需要自定義類,其中關鍵的接口如下:

BaseRelation

  這個類作用就是裝schema的,這個是基礎,通過繼承這個類,重寫獲取schema的方法。DataFrame = RDD + schema,所以schema獲取這個還是比較重要的。這個類中帶有過濾,實現過濾條件的功能。

TableScan

  trait類,全表掃描外部數據源的方式。

PrunedScan

  trait類,列裁剪方式掃描外部數據源。

PrunedFilteredScan

  trait類,這個是filter+pruned,行過濾和列裁剪方法掃描外部數據源方式。

InsertableRelation

  trait類,寫回的功能,這個方法將DF寫入外部數據源,上訴的那幾個都是將該數據源讀出來轉化成DF。

RelationProvider

  trait類,用來創建BaseRelation,他的名字可以看出,這是個Relation(或者說schema)的提供者。

六、DF與DS

  DS(DataSet)是一個分佈式數據集;DF(DataFrame)是DataSet[Row],可以理解成RDBMS中的一張表。所以對於DF來說schema變得尤爲重要。
  如何創建DF。DF是Spark SQL的編程基礎,所以先要創建Spark SQL入口。Spark SQL的入口點就是SparkSession,創建的方式

val spark = SparkSession.builder().appName().master().getOrCreate();
//讀數據生成DF
val df = spark.read.format("json").load(path)
//將DF數據寫出到文件
val df = spark.write.format("text").save(path)

6.1 DF一些簡單方法

//查看Schema信息
df.printSchema()
//顯示df裏面數據
df.show(); //這個方法默認顯示前20行的數據
//僅顯示某個列的數據
df.select("name","age").show()
df.select($"name",$"age"+1).show()
df.select('name).show()
//篩選
df.filter($"age" > 21).show()
//分組
df.groupBy("age").count().show()
//排序
df.sort($"name".desc)
//join
df1.join(df2, df1.col("id") === df2.col("id"), "left")

6.2 RDD轉化成DF

  因爲RDD只是一個數據集沒有Schema,所以要導入Schema到DF。一共兩種方式。

//1、利用java的反射得到
import spark.implicits._
val df = spark.sparkContext.textFile("file:///d:/a.txt").map(x=> x.splite(","))
   .map(x => student(x(0).toInt,x(1),x(2).toInt)).toDF()

case class student(id:Int, name:String, age:Int)
//2、利用代碼編程,推薦這種
val info = spark.sparkContext.textFile("file:///d:/a.txt").map(x=> x.splite(","))
val df = info.map(x=> Row(x(0).toInt,x(1),x(2).toInt))

//得到Schema
val schema = StructType(Array(
   StructField("id",IntegerType,true),
   StructField("name",StringType,true),
   StructField("id",IntegerType,true)
))
   
//註冊在一起
val df = spark.createDataFrame(info,schema)

6.3 DF高級用法

//1、將DF按照age分組輸出
df.write.format("json").mode("overwirte").option("comparession","none").partitionBy("age").save("/a/b/")
/*
輸出按照每個年齡分組輸出到不同的文件夾下。
*/

七、Function

  Spark SQL中也有像Hive一樣的函數,應該說是像SQL一樣的函數。因爲沒有Function的SQL很多功能是做不了的,比如時間截斷,字符串的截斷等。Hive的Function在我的其他的博文中有提到,下面來說明一下Spark SQL的Function。

7.1 內建函數

  內建函數都在 org.apache.spark.sql.functions._ 這個包下面。

val Rdd = spark.sparkContext.parallelize(a.txt)
import spark.implicits._
val Df = Rdd.map(_.split(",")).map(x => Data(x(0),x(1))).toDF

import org.apache.spark.sql.functions._
df.groupBy($"col1").agg(count("col2").as("otherName")).select("col1","otherName").show(false)

  上面這個agg的功能就是去取分組後的數據進行聚合內的運算(agg是聚合函數的英文縮寫),業務上比如取分組後的平均分,最大分,就可寫成:

df.groupBy($"score").agg(Map("score"->"avg","score"->"max"))

7.2 自建函數

  內置的函數只能完成一部分通用的業務邏輯,如果想要更好的完成業務功能,肯定是需要自定義的函數。

//註冊一個自定義函數
spark.udf.register("functionName", UDFFunction)
spark.sql("select functionName() from xxx").show(false)

  看着簡單,就是向spark中註冊一個UDF的函數,然後之後的Spark SQL就可以運行此函數。有兩個問題,第一:如果不用spark.sql方式去調用,用API的方式如何運行? 第二:這種方式是否是臨時的,只能在某個spark任務中使用,如何使之成爲永久函數,就如同內置函數一樣(重新編譯?註冊jar包?)? 這些問題日後再解答。

八、Spark SQL調優

spark.sql.shuffle.partitions

  在Spark SQL中如果產生了shuffle,那麼在Spark SQL的shuffle 的partition的數量是由spark.sql.shuffle.partitions決定,這個值默認是200,所以小數據量的shuffle的就會產生大量的無用partition,所以有實力的公司就會根據數據量自動適配這個shuffle partition數量。

九、Spark SQL願景

  更少代碼、更少數據量、優化交給底層。比如json的外部數據源讀取,很好的兼容了字段多少,字段的數據類型的匹配。上層應用只需要read就可以了。

十、catalog

  Spark中的DataSet和Dataframe API支持結構化分析。結構化分析的一個重要的方面是管理元數據。這些元數據可能是一些臨時元數據(比如臨時表)、SQLContext上註冊的UDF以及持久化的元數據(比如Hivemeta store或者HCatalog)。
  通過使用catalog的API的使用,可以訪問元數據信息。

十一、DF與DS區別

  上面有說過DF=DS[Row]。DF和DS都可以使用高級API,這些功能都是基於他有schema。所以DF,DS可以說在這方面運用是一樣的。但是兩者是有差別的。
  DF=DS[Row],Row數據類型是一個弱類型,就是他不會在編譯的時候檢查column的正確性,但是DS的一個強類型(強類型就是指列類型是一個明確類型的數據),他的column必須是輸入正確,否則就報錯。

//df獲取列,只能在運行時檢查col1名字對不對
val df = spark.read.format("json").load("xxxx")
df.select("col1","col2").show(false)

//ds方式獲取的兩種方式
val ds = spark.read.format("json").load("xxxx").as[People]
ds.select("col1","col2").show(false)
ds.map(_.col1).show(false)

  所以說DF是DS的一個特例,就是將DS弱類型化,兩者的高級的API基本相同。

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