概述
本文分析Dataset中的執行計劃的處理過程。執行計劃的處理包括以下幾個過程:分析邏輯執行計劃->優化邏輯執行計劃->生成一個或多個物理執行計劃->優化物理執行計劃->生成可執行代碼。
這個過程都是在dataset的成員:QueryExecution中完成。所以本篇,就是介紹QueryExecution的實現過程。
由於QueryExecution的實現過程很多,本文主要介紹QueryExecution的概要流程。
QueryExecution要點
QueryExecution是:使用Spark執行關係查詢的主要工作流程。 通過它可以允許開發人員能夠很容易的訪問查詢執行的中間階段。
在QueryExecution中實現了:把一個邏輯計劃生成物理執行計劃的全過程。
QueryExecution的實現要點概要如下:
- QueryExecution獲取一個sparkSession.sessionState.planner,這是一個優化器,其實現類是SparkPlanner, 該planner會把一個優化後的邏輯計劃轉換成一個或多個物理執行計劃。
protected def planner = sparkSession.sessionState.planner
- 通過sparkSession.sessionState.analyzer.executeAndCheck來檢查一個邏輯執行計劃,並得到一個分析和檢查後的邏輯計劃:analyzed。
// 1.對邏輯計劃進行分析,得到分析後的邏輯計劃
lazy val analyzed: LogicalPlan = {
SparkSession.setActiveSession(sparkSession)
sparkSession.sessionState.analyzer.executeAndCheck(logical)
}
- 通過sparkSession.sharedState.cacheManager.useCachedData把analyzed進行緩存,或更新cacheManager中的檢查後的邏輯執行計劃。
// 對分析後的邏輯計劃進行緩存,更新緩存中的計劃
lazy val withCachedData: LogicalPlan = {
assertAnalyzed()
assertSupported()
sparkSession.sharedState.cacheManager.useCachedData(analyzed)
}
- 通過sparkSession.sessionState.optimizer對邏輯執行計劃進行優化,得到優化後的邏輯執行計劃:optimizedPlan。
// 2.對分析後的邏輯計劃進行優化,得到優化後的邏輯計劃
lazy val optimizedPlan: LogicalPlan = sparkSession.sessionState.optimizer.execute(withCachedData)
- 通過sparkSession.sessionState.planner的plan函數把optimizedPlan轉換成物理執行計劃,其實得到一個SparkPlan的變量:sparkPlan。
// 3.把優化後的邏輯計劃轉換成一個或多個物理執行計劃
lazy val sparkPlan: SparkPlan = {
SparkSession.setActiveSession(sparkSession)
// TODO: We use next(), i.e. take the first plan returned by the planner, here for now,
// but we will implement to choose the best plan.
planner.plan(ReturnAnswer(optimizedPlan)).next()
}
- 調用prepareForExecution函數對物理執行計劃進行優化,得到新的物理執行計劃:executedPlan。
// 4.對物理執行計劃進行優化,得到優化後的物理執行計劃
lazy val executedPlan: SparkPlan = prepareForExecution(sparkPlan)
- 定義了一個toRdd函數,該函數把物理執行計劃轉換成rdd的執行代碼:toRdd: RDD[InternalRow] = executedPlan.execute()。
// 5.把物理執行計劃轉換成rdd代碼
lazy val toRdd: RDD[InternalRow] = executedPlan.execute()
實戰
查看dataframe的queryExecution信息。就可以查看該dataframe的從邏輯計劃到物理計劃的生成過程。
如下:
scala> val df2 = df.withColumn("new_col", df("_c3"))
df2: org.apache.spark.sql.DataFrame = [_c0: string, _c1: string ... 4 more fields]
scala> val df = spark.read.csv("/usr/testdata/events")
df: org.apache.spark.sql.DataFrame = [_c0: string, _c1: string ... 3 more fields]
scala> df2.queryExecution.toString
res20: String =
== Parsed Logical Plan ==
Project [_c0#13, _c1#14, _c2#15, _c3#16, _c4#17, _c3#16 AS new_col#82]
+- Relation[_c0#13,_c1#14,_c2#15,_c3#16,_c4#17] csv
== Analyzed Logical Plan ==
_c0: string, _c1: string, _c2: string, _c3: string, _c4: string, new_col: string
Project [_c0#13, _c1#14, _c2#15, _c3#16, _c4#17, _c3#16 AS new_col#82]
+- Relation[_c0#13,_c1#14,_c2#15,_c3#16,_c4#17] csv
== Optimized Logical Plan ==
Project [_c0#13, _c1#14, _c2#15, _c3#16, _c4#17, _c3#16 AS new_col#82]
+- Relation[_c0#13,_c1#14,_c2#15,_c3#16,_c4#17] csv
== Physical Plan ==
*(1) Project [_c0#13, _c1#14, _c2#15, _c3#16, _c4#17, _c3#16 AS new_col#82]
+- *(1) FileScan csv [_c0#13,_c1#14,_c2#15,_c3#16,_c4#17] Batched: false, Format: CSV, Location: InMemoryFileIndex[hdfs://localhost:9000/usr/testdata...
參考
- https://www.slideshare.net/databricks/deep-dive-into-spark-sql-with-advanced-performance-tuning-with-xiao-li-wenchen-fan