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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章