RDD、DataFrame和DataSet的區(qū)別是什么
RDD、DataFrame和DataSet是容易產(chǎn)生混淆的概念,必須對其相互之間對比,才可以知道其中異同。
RDD和DataFrame
RDD-DataFrame
上圖直觀地體現(xiàn)了DataFrame和RDD的區(qū)別。左側(cè)的RDD[Person]雖然以Person為類型參數(shù),但Spark框架本身不了解 Person類的內(nèi)部結(jié)構(gòu)。而右側(cè)的DataFrame卻提供了詳細(xì)的結(jié)構(gòu)信息,使得Spark SQL可以清楚地知道該數(shù)據(jù)集中包含哪些列,每列的名稱和類型各是什么。DataFrame多了數(shù)據(jù)的結(jié)構(gòu)信息,即schema。RDD是分布式的 Java對象的集合。DataFrame是分布式的Row對象的集合。DataFrame除了提供了比RDD更豐富的算子以外,更重要的特點(diǎn)是提升執(zhí)行效率、減少數(shù)據(jù)讀取以及執(zhí)行計(jì)劃的優(yōu)化,比如filter下推、裁剪等。
提升執(zhí)行效率
RDD API是函數(shù)式的,強(qiáng)調(diào)不變性,在大部分場景下傾向于創(chuàng)建新對象而不是修改老對象。這一特點(diǎn)雖然帶來了干凈整潔的API,卻也使得Spark應(yīng)用程序在運(yùn)行期傾向于創(chuàng)建大量臨時(shí)對象,對GC造成壓力。在現(xiàn)有RDD API的基礎(chǔ)之上,我們固然可以利用mapPartitions方法來重載RDD單個(gè)分片內(nèi)的數(shù)據(jù)創(chuàng)建方式,用復(fù)用可變對象的方式來減小對象分配和GC的開銷,但這犧牲了代碼的可讀性,而且要求開發(fā)者對Spark運(yùn)行時(shí)機(jī)制有一定的了解,門檻較高。另一方面,Spark SQL在框架內(nèi)部已經(jīng)在各種可能的情況下盡量重用對象,這樣做雖然在內(nèi)部會打破了不變性,但在將數(shù)據(jù)返回給用戶時(shí),還會重新轉(zhuǎn)為不可變數(shù)據(jù)。利用 DataFrame API進(jìn)行開發(fā),可以免費(fèi)地享受到這些優(yōu)化效果。
減少數(shù)據(jù)讀取
分析大數(shù)據(jù),最快的方法就是 ——忽略它。這里的“忽略”并不是熟視無睹,而是根據(jù)查詢條件進(jìn)行恰當(dāng)?shù)募糁Α?/p>
上文討論分區(qū)表時(shí)提到的分區(qū)剪 枝便是其中一種——當(dāng)查詢的過濾條件中涉及到分區(qū)列時(shí),我們可以根據(jù)查詢條件剪掉肯定不包含目標(biāo)數(shù)據(jù)的分區(qū)目錄,從而減少IO。
對于一些“智能”數(shù)據(jù)格 式,Spark SQL還可以根據(jù)數(shù)據(jù)文件中附帶的統(tǒng)計(jì)信息來進(jìn)行剪枝。簡單來說,在這類數(shù)據(jù)格式中,數(shù)據(jù)是分段保存的,每段數(shù)據(jù)都帶有***值、最小值、null值數(shù)量等 一些基本的統(tǒng)計(jì)信息。當(dāng)統(tǒng)計(jì)信息表名某一數(shù)據(jù)段肯定不包括符合查詢條件的目標(biāo)數(shù)據(jù)時(shí),該數(shù)據(jù)段就可以直接跳過(例如某整數(shù)列a某段的***值為100,而查詢條件要求a > 200)。
此外,Spark SQL也可以充分利用RCFile、ORC、Parquet等列式存儲格式的優(yōu)勢,僅掃描查詢真正涉及的列,忽略其余列的數(shù)據(jù)。
執(zhí)行優(yōu)化
人口數(shù)據(jù)分析示例
為了說明查詢優(yōu)化,我們來看上圖展示的人口數(shù)據(jù)分析的示例。圖中構(gòu)造了兩個(gè)DataFrame,將它們join之后又做了一次filter操作。如果原封不動地執(zhí)行這個(gè)執(zhí)行計(jì)劃,最終的執(zhí)行效率是不高的。因?yàn)閖oin是一個(gè)代價(jià)較大的操作,也可能會產(chǎn)生一個(gè)較大的數(shù)據(jù)集。如果我們能將filter 下推到 join下方,先對DataFrame進(jìn)行過濾,再join過濾后的較小的結(jié)果集,便可以有效縮短執(zhí)行時(shí)間。而Spark SQL的查詢優(yōu)化器正是這樣做的。簡而言之,邏輯查詢計(jì)劃優(yōu)化就是一個(gè)利用基于關(guān)系代數(shù)的等價(jià)變換,將高成本的操作替換為低成本操作的過程。
得到的優(yōu)化執(zhí)行計(jì)劃在轉(zhuǎn)換成物 理執(zhí)行計(jì)劃的過程中,還可以根據(jù)具體的數(shù)據(jù)源的特性將過濾條件下推至數(shù)據(jù)源內(nèi)。最右側(cè)的物理執(zhí)行計(jì)劃中Filter之所以消失不見,就是因?yàn)槿苋肓擞糜趫?zhí)行最終的讀取操作的表掃描節(jié)點(diǎn)內(nèi)。
對于普通開發(fā)者而言,查詢優(yōu)化 器的意義在于,即便是經(jīng)驗(yàn)并不豐富的程序員寫出的次優(yōu)的查詢,也可以被盡量轉(zhuǎn)換為高效的形式予以執(zhí)行。
RDD和DataSet
- DataSet以Catalyst邏輯執(zhí)行計(jì)劃表示,并且數(shù)據(jù)以編碼的二進(jìn)制形式被存儲,不需要反序列化就可以執(zhí)行sorting、shuffle等操作。
- DataSet創(chuàng)立需要一個(gè)顯式的Encoder,把對象序列化為二進(jìn)制,可以把對象的scheme映射為SparkSQl類型,然而RDD依賴于運(yùn)行時(shí)反射機(jī)制。
通過上面兩點(diǎn),DataSet的性能比RDD的要好很多。
DataFrame和DataSet
Dataset可以認(rèn)為是DataFrame的一個(gè)特例,主要區(qū)別是Dataset每一個(gè)record存儲的是一個(gè)強(qiáng)類型值而不是一個(gè)Row。因此具有如下三個(gè)特點(diǎn):
DataSet可以在編譯時(shí)檢查類型
并且是面向?qū)ο蟮木幊探涌?。用wordcount舉例:
//DataFrame // Load a text file and interpret each line as a java.lang.String val ds = sqlContext.read.text("/home/spark/1.6/lines").as[String] val result = ds .flatMap(_.split(" ")) // Split on whitespace .filter(_ != "") // Filter empty words .toDF() // Convert to DataFrame to perform aggregation / sorting .groupBy($"value") // Count number of occurences of each word .agg(count("*") as "numOccurances") .orderBy($"numOccurances" desc) // Show most common words first
后面版本DataFrame會繼承DataSet,DataFrame是面向Spark SQL的接口。
//DataSet,完全使用scala編程,不要切換到DataFrame val wordCount = ds.flatMap(_.split(" ")) .filter(_ != "") .groupBy(_.toLowerCase()) // Instead of grouping on a column expression (i.e. $"value") we pass a lambda function .count()
DataFrame和DataSet可以相互轉(zhuǎn)化, df.as[ElementType] 這樣可以把DataFrame轉(zhuǎn)化為DataSet, ds.toDF() 這樣可以把DataSet轉(zhuǎn)化為DataFrame。