彈性分散式資料集(簡稱RDD)是Spark對資料的核心抽象。RDD其實就是分散式的元素集合。在Spark中,對資料的操作不外乎建立RDD、轉化已有RDD以及呼叫RDD操作進行求值。而在這一切背後,Spark會自動將RDD中的資料分發到叢集上,並將操作並行化執行。
3.1 RDD基礎
Spark中的RDD就是一個不可變的分散式物件集合。每個RDD都被分為多個分割槽,這些分割槽執行在叢集中的不同節點上。
例3-1:在Python中使用textFile()建立一個字串的RDD
lines = sc.textFile("README.md")
建立出來後,RDD支援兩種型別的操作:轉化操作和行動操作。轉化操作會由一個RDD生成一個新的RDD。行動操作會對RDD計算出一個結果,並把結果返回到驅動器程式中,或把結果儲存到外部儲存系統(如HDFS)中。
例3-2:呼叫轉化操作filter()
pythonLines = lines.filter(lambda line:"python" in line)
例3-3:呼叫first()行動操作
pythonLines.first()
Spark只會惰性計算這些RDD。它們只有第一次在一個行動操作中用到時,才會真正計算。Spark瞭解了完整的轉化操作鏈之後,它就可以只計算求結果時真正需要的資料。
預設情況下,Spark的RDD會在你每次對它們進行行動操作時重新計算。如果想在多個行動操作中重用同一個RDD,可以使用RDD.persist()讓Spark把這個RDD快取下來。在第一次對持久化的RDD計算之後,Spark會把RDD的內容儲存在記憶體中(以分割槽方式儲存到叢集中的各個機器上)。
例3-4:把RDD持久化到記憶體中
pythonLines.persist()
pythonLines.count()
pythonLines.first()
3.2 建立RDD
Spark提供了兩種建立RDD的方式:1讀取外部資料集,2在驅動器程式中對一個集合進行並行化。
建立RDD最簡單的方式就是把程式中一個已有的集合傳給SparkContext的parallelize()方法,這種方式用的並不多,畢竟需要把整個資料集先放在一臺機器的記憶體中。
例3-5:Python中的parallelize()方法
lines = sc.parallelize(["pandas", "i like pandas"])
例3-6:Scala中的parallelize()方法
val lines = sc.parallelize(List("pandas", "i like pandas"))
更常用的方式是從外部儲存中讀取資料來建立RDD。
例3-8 : Python中的textFile()方法
lines = sc.textFile("/path/to/README.md")
例3-9:Scala中的textFile()方法
val lines = sc.textFile("/path/to/README.md")
3.3 RDD操作
3.3.1 轉化操作
RDD的轉化操作是返回新RDD的操作。
例 3-11:用Python實現filter()轉化操作
inputRDD = sc.textFile("log.txt") errorsRDD = inputRDD.filter(lambda x: "error" in x)
例 3-12:用Scala實現filter()轉化操作
val inputRDD = sc.textFile("log.txt") val errorsRDD = inputRDD.filter(line => line.contains("error"))
filter()操作不會改變已有的inputRDD中的資料
例 3-14:用Python進行union()轉化操作
errorsRDD = inputRDD.filter(lambda x:"error" in x) warningsRDD = inputRDD.filter(lambda x:"warning" in x) badlLinesRDD = errorsRDD.union(warningsRDD)
通過轉化操作,我們從已有的RDD中派生出新的RDD,Spark會使用譜系圖來記錄這些不同RDD之間的依賴關係。Spark需要用這些資訊來按需計算每個RDD,也可以依靠譜系圖在持久化的RDD丟失部分資料時恢復所丟失的資料。
3.3.2 行動操作
行動操作會對資料集進行實際的計算,把最終求得的結果返回到驅動器程式,或者寫入外部儲存系統中。行動操作會強制執行那些求值必須用到的RDD的轉化操作。
例3-15:在Python中使用行動操作對錯誤進行計數
print "Input had" + badLinesRDD.count() + "concerting lines" for line in badLinesRDD.take(10): print line
例3-16:在Scala中使用行動操作對錯誤進行計數
println("Input had " + badLinesRDD.count() + " concerning lines") badLinesRDD.take(10).foreach(println)
每當我們呼叫一的新的行動操作時,整個RDD都會從頭開始計算。要避免這種低效的行為,我們可以將中間結果持久化。
3.3.3 惰性求值
我們不應該把RDD看作存放著特定資料的資料集,而最好把每個RDD當作我們通過轉化操作構建出來的、記錄如何生成新資料集的指令列表。
在Spark中,一個非常複雜的對映不會比使用很多簡單的連續操作獲得更好的效能。
3.4 向Spark傳遞函式
Spark的大部分轉化操作和一部分行動操作,都需要依賴使用者傳遞的函式來計算
3.4.1 Python
例 3-18:在Python中傳遞函式
word = rdd.filter(lambda s : "error" in s) def containsError(s): return "error" in s word = rdd.filter(containsError)
傳遞函式時需要小心的一點是,Python會在你不經意間把函式所在的物件也序列化傳出去。
替代方案是,只把我們所需要的欄位從物件中拿出來放到一個區域性變數中,然後傳遞這個區域性變數。
例3-20:傳遞不帶欄位引用的Python函式
class WordFunstions(object): def __init__(self, query): self.query = query def func(self, rdd): query = self.query return rdd.filter(lambda x: query in x)
3.4.2 Scala
與Python類似,傳遞一個物件的方法或者欄位時,會包含對整個物件的引用。
我們可以把需要的欄位放到一個區域性變數中,來避免傳遞包含該欄位的整個物件。
class SearchFunctions(val query: String){ def getMatchesNoReference(rdd: RDD[String]):RDD[String] = { val query_ = this.query rdd.map(x => x.split(query_)) } }