Spark RDD

Spark 的核心是RDD(Resilient Distributed Dataset),即彈性分佈式數據集,是由AMPLab實驗室提出的概念,屬於一種分佈式的內存系統數據集應用。Spark的主要優勢來自RDD本身的特性,RDD能夠與其他系統兼容,可以導入外部存儲系統的數據集,例如HDFS、HBase、或者其他hadoop數據源。

1.1、RDD特性

  • RDD 的3種基本運算
RDD運算類型 說明
“轉換”運算Transformation RDD執行“轉換”運算的結果,會產生另外一個RDD;<br> RDD具有lazy特性,所有“轉換”運算並不會立刻實際執行,等到執行“動作”運算纔會實際執行;
“動作”運算Action RDD執行“動作”運算後不會產生另一個RDD,而是產生數值、數組或寫入文件系統;<br> RDD執行“動作”運算會立刻實際執行,而且連同之前的“轉換”運算一起執行;
“持久化”Persistence 對於那些會重複使用的RDD,可以將RDD“持久化”在內存中作爲後續使用,以提高執行性能;
  • Lineage 機制具備容錯的特性

RDD本身具有Lineage機制,它會記錄每個RDD與其父代RDD之間的關聯,還會記錄通過什麼操作由父代RDD而得到該RDD的信息。<br>
RDD本身的immutable(不可變)特性,再加上Lineage機制,使得Spark具備容錯的特性。如果某個節點的機器出現了故障,那麼存儲在這個節點上的RDD損毀後會重新執行一連串的“轉換”命令,產生新的輸出數據,以避免因爲某個節點的故障而造成整個系統無法允許的問題。

1.2、RDD基本操作

1.2.1、基本RDD"轉換"運算

創建RDD使用SparkContext的parallelize方法;

  • 創建intRDD
In [1]: intRDD = sc.parallelize([1,2,3,3,2,5,4,9,0,8,5])

In [2]: intRDD.collect()
Out[2]: [1, 2, 3, 3, 2, 5, 4, 9, 0, 8, 5]

intRDD執行collect()後會轉換爲List,這是一個“動作”運算,所以會馬上執行;

  • 創建stringRDD
In [3]: stringRDD = sc.parallelize(["Apple", "Orange", "Apple", "Grape", "Banana"])

In [4]: stringRDD.collect()
Out[4]: ['Apple', 'Orange', 'Apple', 'Grape', 'Banana']
  • map 運算
In [5]: intRDD.map(lambda x:x+1).collect()
Out[5]: [2, 3, 4, 4, 3, 6, 5, 10, 1, 9, 6]
In [6]: stringRDD.map(lambda x:"fruit:"+x).collect()
Out[6]: ['fruit:Apple', 'fruit:Orange', 'fruit:Apple', 'fruit:Grape', 'fruit:Banana']
  • filter數字運算
In [7]: intRDD.filter(lambda x:x>5).collect()
Out[7]: [9, 8]

In [8]: intRDD.filter(lambda x:x<=5 and x>3).collect()
Out[8]: [5, 4, 5]
  • filter字符串運算
In [9]: stringRDD.filter(lambda x:"ra" in x).collect()
Out[9]: ['Orange', 'Grape']
  • distinct 去重運算
In [11]: intRDD.distinct().collect()
Out[11]: [4, 0, 8, 1, 5, 9, 2, 3]

In [12]: stringRDD.distinct().collect()
Out[12]: ['Orange', 'Apple', 'Grape', 'Banana']
  • randomSplit分割運算
In [13]: splitRDD = intRDD.randomSplit([0.4, 0.6])

In [14]: splitRDD[0].collect()
Out[14]: [2, 0, 5]

In [15]: splitRDD[1].collect()
Out[15]: [1, 2, 3, 3, 5, 4, 9, 8]
  • groupBy 運算
In [17]: groupRDD = intRDD.groupBy(lambda x:"even" if (x%2==0) else "odd").collect()

In [18]: print(groupRDD[0][0],sorted(groupRDD[0][1]))
even [0, 2, 2, 4, 8]

In [19]: print(groupRDD[1][0],sorted(groupRDD[1][1]))
odd [1, 3, 3, 5, 5, 9]

1.2.2、多個RDD"轉換"運算

  • 創建3個RDD
In [20]: intRDD1 = sc.parallelize([1,3,5,7,9])

In [21]: intRDD2 = sc.parallelize([2,4,6,8,10])

In [22]: intRDD3 = sc.parallelize([0,1,2,3,4,5,6,7,8,9])
  • union函數進行並集運算
In [23]: intRDD1.union(intRDD2).union(intRDD3).collect()
Out[23]: [1, 3, 5, 7, 9, 2, 4, 6, 8, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  • intersection 交集運算
In [24]: intRDD1.intersection(intRDD2).collect()
Out[24]: []

In [25]: intRDD1.intersection(intRDD3).collect()
Out[25]: [1, 9, 3, 5, 7]

In [26]: intRDD2.intersection(intRDD3).collect()
Out[26]: [8, 2, 4, 6]
  • subtract 差集運算
In [27]: intRDD3.subtract(intRDD1).collect()
Out[27]: [0, 8, 2, 4, 6]

In [28]: intRDD3.subtract(intRDD2).collect()
Out[28]: [0, 1, 9, 3, 5, 7]
  • cartesian笛卡爾乘積運算
In [29]: print(intRDD1.cartesian(intRDD2).collect())
[(1, 2), (1, 4), (1, 6), (1, 8), (1, 10), (3, 2), (3, 4), (3, 6), (3, 8), (3, 10), (5, 2), (5, 4), (5, 6), (5, 8), (5, 10), (7, 2), (9, 2), (7, 4), (9, 4), (7, 6), (9, 6), (7, 8), (7, 10), (9, 8), (9, 10)]

1.2.3、基本"動作"運算

基本“動作”運算會馬上執行,產生結果。

In [30]: intRDD.collect()
Out[30]: [1, 2, 3, 3, 2, 5, 4, 9, 0, 8, 5]

In [31]: intRDD.first()
Out[31]: 1

In [32]: intRDD.take(1)
Out[32]: [1]

In [33]: intRDD.take(2)
Out[33]: [1, 2]

In [34]: intRDD.takeOrdered(3)
Out[34]: [0, 1, 2]

In [35]: intRDD.takeOrdered(3,key=lambda x:-x)
Out[35]: [9, 8, 5]

In [36]: intRDD.stats()
Out[36]: (count: 11, mean: 3.8181818181818183, stdev: 2.6566616720368104, max: 9.0, min: 0.0)

In [37]: intRDD.min()
Out[37]: 0

In [38]: intRDD.max()
Out[38]: 9

In [39]: intRDD.stdev()
Out[39]: 2.6566616720368104

In [40]: intRDD.count()
Out[40]: 11

In [41]: intRDD.sum()
Out[41]: 42

In [42]: intRDD.mean()
Out[42]: 3.8181818181818183

1.2.4、RDD key-value 基本"轉換"運算

Spark RDD 支持鍵值(key-value)的運算,這也是Map/Reduce的基礎;

  • 創建 key-value RDD
In [44]: kvRDD1 = sc.parallelize([(1,2),(3,4),(5,6),(7,8),(9,10)])

In [45]: kvRDD1.collect()
Out[45]: [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]
  • 列出全部key值
In [46]: kvRDD1.keys().collect()
Out[46]: [1, 3, 5, 7, 9]
  • 列出全部的value值
In [47]: kvRDD1.values().collect()
Out[47]: [2, 4, 6, 8, 10]
  • 使用filter 篩選key
In [48]: kvRDD1.filter(lambda keyValue:keyValue[0]<5).collect()
Out[48]: [(1, 2), (3, 4)]
  • 使用filter 篩選 value
In [49]: kvRDD1.filter(lambda keyValue:keyValue[1]<5).collect()
Out[49]: [(1, 2), (3, 4)]
  • mapValue 運算
In [50]: kvRDD1.mapValues(lambda x:x*x).collect()
Out[50]: [(1, 4), (3, 16), (5, 36), (7, 64), (9, 100)]
  • sortByKey 按照key排序
In [54]: kvRDD1.sortByKey().collect()
Out[54]: [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]

In [55]: kvRDD1.sortByKey(ascending=False).collect()
Out[55]: [(9, 10), (7, 8), (5, 6), (3, 4), (1, 2)]
  • reduceByKey key的相同值做reduce
In [56]: kvRDD1.reduceByKey(lambda x,y:x+y).collect()
Out[56]: [(1, 2), (5, 6), (9, 10), (3, 4), (7, 8)]

1.2.5、多個RDD key-value 基本"轉換"運算

  • 創建多個 key-value RDD
In [58]: kvRDD1 = sc.parallelize([(1,2),(3,4),(5,6),(7,8),(9,10),(1,0),(2,8),(3,6)])

In [59]: kvRDD2 = sc.parallelize([(0,2),(2,4),(4,6),(6,8),(8,10),(8,0),(6,8),(4,6)])

In [60]: kvRDD1.collect()
Out[60]: [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10), (1, 0), (2, 8), (3, 6)]

In [61]: kvRDD2.collect()
Out[61]: [(0, 2), (2, 4), (4, 6), (6, 8), (8, 10), (8, 0), (6, 8), (4, 6)]
  • key-value RDD join 運算
In [62]: kvRDD1.join(kvRDD2).collect()
Out[62]: [(2, (8, 4))]
  • key-value RDD leftOuterJoin運算
In [63]: kvRDD1.leftOuterJoin(kvRDD2).collect()
Out[63]: 
[(1, (2, None)),
 (1, (0, None)),
 (9, (10, None)),
 (2, (8, 4)),
 (3, (4, None)),
 (3, (6, None)),
 (5, (6, None)),
 (7, (8, None))]
  • key-value RDD rightOuterJoin運算
In [64]: kvRDD1.rightOuterJoin(kvRDD2).collect()
Out[64]: 
[(0, (None, 2)),
 (8, (None, 10)),
 (8, (None, 0)),
 (2, (8, 4)),
 (4, (None, 6)),
 (4, (None, 6)),
 (6, (None, 8)),
 (6, (None, 8))]
  • key-value RDD subtratByKey運算
In [65]: kvRDD1.subtract(kvRDD2).collect()
Out[65]: [(2, 8), (1, 2), (5, 6), (9, 10), (1, 0), (3, 4), (7, 8), (3, 6)]

1.2.6、RDD key-value "動作"運算

  • key-value first 運算
In [67]: kvRDD1.first()
Out[67]: (1, 2)
  • 獲取第一項數據的元素
In [68]: kvRDD1.first()
Out[68]: (1, 2)

In [69]: kvRDD1.first()[0]
Out[69]: 1
  • 計算RDD中的每一個key的值的項數
In [71]: kvRDD1.countByKey()
Out[71]: defaultdict(int, {1: 2, 3: 2, 5: 1, 7: 1, 9: 1, 2: 1})
  • collectAsMap創建Key-Value的字典
In [75]: kvRDD1.collectAsMap()
Out[75]: {1: 0, 3: 6, 5: 6, 7: 8, 9: 10, 2: 8}
  • key-value lookup 運算 通過可以查找value的值
In [76]: kvRDD1.lookup(1)
Out[76]: [2, 0]

In [77]: kvRDD1.lookup(3)
Out[77]: [4, 6]

1.2.7、共享變量

1.2.7.1、Broadcast 廣播變量

Broadcast 廣播變量使用規則如下:

 可以使用SparkContext.broadcast([初始值])創建;
 使用.value的方法來讀取廣播變量的值;
 Broadcast 廣播變量被創建後不能修改
  • 創建帶編號的key-value RDD
In [78]: kvFruit = sc.parallelize([(1,"apple"),(2,"banana"),(3,"orange"),(4,"grape")])
  • collectAsMap創建字典
In [80]: furitMap = kvFruit.collectAsMap()

In [81]: print(furitMap)
{1: 'apple', 2: 'banana', 3: 'orange', 4: 'grape'}
  • 將字典轉變爲廣播變量
In [84]: bcFuritMap = sc.broadcast(furitMap)
  • 創建fruitIds 數字編號
In [85]: frultIds = sc.parallelize([1,3,2,4])
  • 使用廣播變量的字典轉換
In [86]: print(frultIds.map(lambda x:bcFuritMap.value[x]).collect())
['apple', 'orange', 'banana', 'grape']

1.2.7.2、accumulator 累加器

計算綜合是MapReduce常用的計算,Spark提供了accumulator累加器共享變量,使用規則如下:

accumulator累加器可以使用SparkContext.accumulator([初始值])來創建;
使用.add()方法進行累計;
在task中,例如foreach循環中,不能讀取累加器的值;
只有驅動程序,也就是循環外,纔可以使用.value來讀取累加器的值;
  • 創建RDD
In [87]: intRDD = sc.parallelize([1,3,5,7,9])
  • 創建total累加器
In [88]: total = sc.accumulator(0.0)
  • 創建num累加器,初始值爲0,int類型
In [89]: num = sc.accumulator(0)
  • 使用foreach循環.add()累加
In [90]: intRDD.foreach(lambda i:[total.add(i), num.add(1)])

In [91]: total.value
Out[91]: 25.0

In [92]: num.value
Out[92]: 5

1.2.8、RDD Persistene持久化

Spark RDD持久化機制可以用於將需要重複運算的RDD存儲在內存中,以便大幅提升運算效率,持久化使用的方法如下:

RDD.persist(存儲等級) —— 可以指定存儲等級,默認是MEMORY_ONLY,也就是內存;
RDD.unpersist() —— 取消持久化

持久化存儲等級有如下:

MEMORY_ONLY:默認選項。存儲RDD的方式是以Java對象反串行化在jvm內存中,如果RDD太大無法完全存儲在內存,多餘的RDD partitions 不會緩存在內存中,而是需要時候再重新計算;
MEMORY_AND_DISK:存儲RDD的方式是以Java對象反串行化在jvm內存中,如果RDD太大無法完全存儲在內存,多餘的RDD partitions 會存儲在硬盤中,需要時候在從硬盤讀取;
MEMORY_ONLY_SER:與MEMORY_ONLY類似,但是存儲RDD以Java對象反串行化,因爲需要再進行反串行化才能使用,所以會多使用CPU的計算資源,但是比較省內存空間。多餘的RDD partitions 不會緩存在內存中,而是需要時候再重新計算;
MEMORY_AND_DISK_SER:與MEMORY_ONLY_SER類似,多餘的RDD partitions 會存儲在硬盤中,需要時候在從硬盤讀取;
DISK_ONLY:RDD存儲在硬盤上;
MEMORY_ONLY_2,MEMORY_AND_DISK_2,etc:RDD存儲在硬盤上,但是每一個RDD partitions都複製到兩個節點;
  • 使用RDD.persist()持久化
In [93]: intRddMemory = sc.parallelize([1,3,5,7,9])

In [94]: intRddMemory.persist()
Out[94]: ParallelCollectionRDD[186] at parallelize at PythonRDD.scala:184
  • 查看是否已經緩存
In [95]: intRddMemory.is_cached
Out[95]: True
  • 使用RDD.unpersist()取消持久化
In [96]: intRddMemory.unpersist()
Out[96]: ParallelCollectionRDD[186] at parallelize at PythonRDD.scala:184

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