Spark系列 - (3) Spark SQL

後端精進之路發表於2023-02-23

Spark

3. Spark SQL

3.1 Hive、Shark和Sparksql

Hive:Hadoop剛開始出來的時候,使用的是hadoop自帶的分散式計算系統 MapReduce,但是MapReduce的使用難度較大,所以就開發了Hive。Hive的出現解決了MapReduce的使用難度較大的問題,Hive的執行原理是將HQL語句經過語法解析、邏輯計劃、物理計劃轉化成MapReduce程式執行。

Shark:2011年Shark誕生,即Hive on Spark。為了實現與Hive相容,Shark在HiveQL方面重用了Hive中HiveQL的解析、邏輯執行計劃、執行計劃最佳化等邏輯;可以近似認為僅將物理執行計劃從MapReduce作業替換成了Spark作業,透過Hive 的HiveQL解析,把HiveQL翻譯成Spark上的RDD操作;Shark的出現,使得SQL-on-Hadoop的效能比Hive有了10-100倍的提高。

Shark的缺陷

  1. 執行計劃最佳化完全依賴於Hive,不方便新增新的最佳化策略

  2. 因為Spark是執行緒級並行,而MapReduce是程式級並行,因此,Spark在相容 Hive的實現上存線上程安全問題,導致Shark不得不使用另外一套獨立維護的打了補丁的Hive原始碼分支。

2014年7月,spark團隊將Shark轉給Hive進行管理,Hive on Spark是一個Hive的也就是說,Hive將不再受限於一個引擎,可以採用Map-Reduce、Tez、Spark等引擎;

Spark SQL作為Spark生態的一員誕生,不再受限於Hive,只是相容Hive。

3.2 RDD和DataFrame、DataSet

RDD:彈性(Resilient)、分散式(Distributed)、資料集(Datasets),具有隻讀、Lazy、型別安全等特點,具有比較好用的API。RDD的劣勢體現在效能限制上,它是一個JVM駐記憶體物件,這也就決定了存在GC的限制和資料增加時Java序列化成本的升高。

DataFrame:與RDD類似,DataFRame也是一個不可變的彈性分散式資料集。除了資料以外,還記錄著資料的結構資訊,即Schema。另外DataFrame API提供的是一套高層的關係操作,比函式式的RDD API要更加友好。DataFrame的查詢計劃可以透過Spark catalyst optimiser進行最佳化,即使 Spark經驗並不豐富,用dataframe寫得程式也可以儘量被轉化為高效的形式予以執行。

DataFrame只是知道欄位,但是不知道欄位的型別,所以在執行這些操作的時候是 沒辦法在編譯的時候檢查是否型別失敗的。

上圖直觀地體現了 DataFrame 和 RDD 的區別。左側的 RDD[Person]雖然以Person為型別參 數,但 Spark 框架本身不瞭解Person 類的內部結構。而右側的DataFrame卻提供了詳細的結構資訊,使得Spark SQL 可以清楚地知道該資料集中包含哪些列,每列的名稱和型別各是什麼。 DataFrame是為資料提供了Schema的檢視。可以把它當做資料庫中的一張表來對待,DataFrame也是懶執行的。效能上比 RDD 要高,主要原因:最佳化的執行計劃:查詢計劃透過 Spark catalyst optimiser 進行最佳化。

DataSet:DataSet是DataFrame的擴充套件,是Spark最新的資料抽象。Dataframe 是 Dataset 的特列,DataFrame=Dataset[Row] ,所以可以透過 as 方法將 Dataframe 轉換為 Dataset。Row 是一個型別,跟Car、Person 這些的型別一樣,所有的表結構資訊我都用 Row 來表示。DataSet 是強型別的。比如可以有 Dataset[Car],Dataset[Person]。DataFrame只是知道欄位,但是不知道欄位的型別,所以在執行這些操作的時候是沒辦法在編譯的時候檢查是否型別失敗的,比如你可以對一個String進行減法操作,在執行的時候才報錯,而DataSet不僅僅知道欄位,而且知道欄位型別,所以有更嚴格的錯誤檢查。就跟JSON物件和類物件之間的類比。

3.2.1 三者的共性

  • 都是分散式彈性資料集,為處理超大型資料提供便利;
  • 都是Lasy的,在進行建立、轉換,如map方法時,不會立即執行,只有在遇到Action如foreach時,三者才會開始遍歷運算,極端情況下,如果程式碼裡面有建立、 轉換,但是後面沒有在Action中使用對應的結果,在執行時會被直接跳過;
  • 都有partition的概念;
  • 三者有許多共同的函式,如filter,排序等;
  • DataFrame和Dataset均可使用模式匹配獲取各個欄位的值和型別;
  • 三者可以相互轉化

3.2.2 區別

RDD與DataFrame/DataSet的區別

RDD:

  • 用於Spark1.X各模組的API(SparkContext、MLLib,Dstream等)
  • 不支援sparksql操作
  • 不支援程式碼自動最佳化

DataFrame與DataSet:

  • 用於Spark2.X各模組的API(SparkSession、ML、StructuredStreaming等等)
  • 支援SparkSql操作,比如select,groupby之類,還能註冊臨時表/視窗,進行 sql語句操作
  • 支援一些方便的儲存方式,比如儲存成csv、json等格式
  • 基於sparksql引擎構建,支援程式碼自動最佳化

DataFrame與DataSet的區別

DataFrame:

  • DataFrame每一行的型別固定為Row,只有透過解析才能獲取各個欄位的值, 每一列的值沒法直接訪問。
  • DataFrame編譯器缺少型別安全檢查。
testDF.foreach{ 
   line => val col1=line.getAs[String]("col1") 
   println(col1) 
   val col2=line.getAs[String]("col2") 
   println(col2) 
}

DataSet:

  • DataFrame和DataSet之間,可以看成JSON物件和類物件之間的類比。
  • DataSet是型別安全的。

3.2.3 Sql、dataframe、DataSet的型別安全

  • 如果使用Spark SQL的查詢語句,要直到執行時你才會發現有語法錯誤(這樣做代價很大)。
  • 如果使用DataFrame,你在也就是說,當你在 DataFrame 中呼叫了 API 之外的函式時,編譯器就可以發現這個錯。但如果此時,使用了一個不存在欄位的名字,則只能到執行時才能發現錯誤;
  • 如果用的是DataSet[Person],所有不匹配的型別引數都可以在編譯時發現;

3.2.4 什麼時候使用DataFrame或DataSet

下面的情況可以考慮使用DataFrame或Dataset,

  • 如果你需要豐富的語義、高階抽象和特定領域專用的 API,那就使用 DataFrame 或 Dataset;
  • 如果你的處理需要對半結構化資料進行高階處理,如 filter、map、aggregation、 average、sum、SQL 查詢、列式訪問或使用 lambda 函式,那就使用 DataFrame 或 Dataset;
  • 如果你想在編譯時就有高度的型別安全,想要有型別的 JVM 物件,用上 Catalyst 最佳化,並得益於 Tungsten 生成的高效程式碼,那就使用 Dataset;
  • 如果你想在不同的 Spark 庫之間使用一致和簡化的 API,那就使用 DataFrame 或 Dataset;
  • 如果你是R或者Python使用者,就用DataFrame;

除此之外,在需要更細緻的控制時就退回去使用RDD;

3.2.5 RDD、DataFrame、DataSet之間的轉換

1. RDD轉DataFrame、Dataset

  • RDD轉DataFrame:一般用元組把一行的資料寫在一起,然後在toDF中指定欄位名。

  • RDD轉Dataset:需要提前定義欄位名和型別。

2. DataFrame轉RDD、Dataset

  • DataFrame轉RDD:直接轉 val rdd = testDF.rdd
  • DataFrame轉Dataset:需要提前定義case class,然後使用as方法。

3. Dataset轉RDD、DataFrame

  • DataSet轉RDD:直接轉 val rdd = testDS.rdd
  • DataSet轉DataFrame:直接轉即可,spark會把case class封裝成Row。

3.3 Spark SQL最佳化

Catalyst是spark sql的核心,是一套針對spark sql 語句執行過程中的查詢最佳化框架。因此要理解spark sql的執行流程,理解Catalyst的工作流程是理解spark sql的關鍵。而說到Catalyst,就必須提到下面這張圖了,這張圖描述了spark sql執行的全流程。其中,中間四步為catalyst的工作流程。

參考:https://www.jianshu.com/p/0aa4b1caac2e

SQL語句首先透過Parser模組被解析為語法樹,此棵樹稱為Unresolved Logical Plan;Unresolved Logical Plan透過Analyzer模組藉助於Catalog中的表資訊解析為Logical Plan;此時,Optimizer再透過各種基於規則的最佳化策略進行深入最佳化,得到Optimized Logical Plan;最佳化後的邏輯執行計劃依然是邏輯的,並不能被Spark系統理解,此時需要將此邏輯執行計劃轉換為Physical Plan。

Spark常見的最佳化策略有下面幾類:

  1. Combine Limits:合併Limit,就是將兩個相鄰的limit合為一個。
  2. Constant Folding:常量疊加
  • NullPropagation:空格處理
  • BooleanSimplification:布林表示式簡化
  • ConstantFolding:常量疊加
  • SimplifyFilters:Filter簡化
  • LikeSimplification:like表示式簡化。
  • SimplifyCasts:Cast簡化
  • SimplifyCaseConversionExpressions:CASE大小寫轉化表示式簡化
  1. Filter Pushdown Filter下推
  • CombineFilters Filter合併
  • PushPredicateThroughProject:透過Project下推
  • PushPredicateThroughJoin:透過Join下推
  • ColumnPruning:列剪枝

搜尋『後端精進之路』並關注,立刻獲取文章合集和麵試攻略,還有價值數千元的面試大禮包等你拿。

後端精進之路.png

相關文章