Spark版本:V3.2.1
1. 键值对RDD
1.1 键值对RDD的定义
键值对RDD是一种特殊的RDD,注意Spark中并没有这种RDD类型。普通RDD支持的算子都适用于键值对RDD。键值对RDD提供了并行操作各个键或跨节点重新进行数据分组的操作接口。用户可以通过控制键值对RDD在各个节点上的分布情况,大大减少应用的通信开销。
1.2 创建键值对RDD
普通RDD的数据元素一般为数值型、字符串型,键值对RDD中保存里面存储的数据为“键值对”,其形式为(key,value)。通常可以使用map()算子将普通RDD转化为键值对RDD。具体如下:
#直接定义pairRDD
rdd_1=sc.parallelize([(1,[2,5]),(3,[4,4]),(4,[7,8])])
#使用map将普通rdd转化为键值对RDD
rdd_2=sc.parallelize(range(1,10))
pairRDD=rdd_2.map(lambda x:[x,1])
print(rdd_1.collect())
print(pairRDD.collect())
其执行结果如下:
[(1, [2, 5]), (3, [4, 4]), (4, [7, 8])]
[[1, 1], [2, 1], [3, 1], [4, 1], [5, 1], [6, 1], [7, 1], [8, 1], [9, 1]]
这里要注意,键值对RDD的每个元素的类型为(key,value)形式,即在Python中这个元素的长度必须为2。若使用如下语句,其定义的是普通RDD而不是键值对RDD。
rdd_1=sc.parallelize([(1,2,5),(3,4,4),(4,7,8)])
另外,(key,value)的形式在Python中也可以使用[key,value]。因为在Python中这两者可以转化。
2.常用算子
2.1 转化算子
- combineByKey()
combineByKey()算子可以将具有相同key的元素的value值组合成一个集合。主要包含三个参数:createCombiner, mergeValue, mergeCombiners。其具体作用如下:
(1)createCombiner: 当combineByKey()在遍历分区中的所有元素时,如果当前的元素的key从未出现过,则combineByKey()会调用一个叫作creatCombiner的函数创建该key对应的累加器的初始值。这一过程会在每个分区中各个key第一次出现时执行;
(2)mergeValue: 如果当前的key在该分区中已经出现过了,则会使用mergerValue方法将该键的累加器对应的当前值与这个新的value值合并;
(3)mergeCombiners: 将各个分区得到的结果进行合并;
(4)不是所有元素都会完整执行以上三个过程;
用法举例如下:
rdd_1=sc.parallelize([(1,[2,5]),(3,[4,4]),(4,[7,8]),(1,[9]),(3,[2,3]),(1,[2,3])])
rdd_2=rdd_1.combineByKey(createCombiner=lambda x:x,
mergeValue=lambda x,y:x+y,
mergeCombiners=lambda x,y:x+y)
print(rdd_1.glom().collect())
print(rdd_2.collect())
其具体结果如下:
[[(1, [2, 5]), (3, [4, 4]), (4, [7, 8])], [(1, [9]), (3, [2, 3]), (1, [2, 3])]]
[(4, [7, 8]), (1, [2, 5, 9, 2, 3]), (3, [4, 4, 2, 3])]
以key=1为例进行说明,从rdd_1的分区结果中可以看到元素(1,[2,5])在第1个分区中,(1,[9])和(1,[2,3])在第2个分区中。其具体执行过程如下:
(1) combineByKey()在遍历第1个分区时第1次遇到key=1的元素时,会将其value=[2,5]传入createCombiner中,其结果为[2,5]。之后继续遍历第1个分区,后续并没有key=1的元素了,所以combineByKey在第1个分区中key=1的结果为[2,5]。
(2) combineByKey()算子在遍历第2个分区时第1次遇到key=1的元素时,将其value=[9]传入createCombiner,其结果为[9]。遇到第2个key=1的元素时,会将当前累加器的值[9]和当前value值[2,3]传入到mergeValue中,其结果为[9,2,3]。则当前分区的结果为[9,2,3]。
(3) 将以上两个分区的结果[2,5]和[9,2,3]传入到mergeCombiners中,得到最终结果[2,5,9,2,3]。
对上述代码进行如下修改:
rdd_1=sc.parallelize([(1,[2,5]),(3,[4,4]),(4,[7,8]),(1,[9]),(3,[2,3]),(1,[2,3])])
rdd_2=rdd_1.combineByKey(createCombiner=lambda x:x,
mergeValue=lambda x,y:x+y,
mergeCombiners=lambda x,y:[])
print(rdd_2.collect())
其结果如下:
[(4, [7, 8]), (1, []), (3, [])]
将mergeCombiners该为[]之后,key=4的结果仍然为[7,8],key=4的元素只出现在第1个分区中,可以推测出该元素没有执行mergeValue、mergeCombiners过程。 而当key=1或key=3时,则因为mergeCombiners方法的缘故,其最终值均为[]。
- reduceByKey()算子、foldByKey()、aggregateByKey()算子
reduceByKey()算子可以将相同key的元素聚集到一起,最终把所有相同key的元素成一个元素。通过该操作,元素的key不变,value值可以聚合成一个列表或者进行求和等操作。其具体用法如下:
rdd_1=sc.parallelize([(1,[2,5]),(3,[4,4]),(4,[7,8]),(1,[9]),(3,[2,3]),(1,[2,3])])
rdd_2=rdd_1.reduceByKey(func=lambda x,y:[sum(x)+sum(y)])
print(rdd_2.collect())
rdd_3=sc.parallelize([('a',1),('b',2),('a',4),('b',4)])
rdd_4=rdd_3.reduceByKey(func=lambda x,y:x+y)
print(rdd_4.collect())
其结果如下:
[(4, [7, 8]), (1, [21]), (3, [13])]
[('b', 6), ('a', 5)]
从实验结果可以看到,key=4的所有value值并没有完成求和操作。这是因为reduceByKey(func)等价于combineByKey(lambda x:x,func,func)。而key=4只出现在一个分区中,所以始终没有机会执行func函数。若想对rdd_1的所有元素值进行累计求和,可以使用如下代码:
rdd_1=sc.parallelize([(1,[2,5]),(3,[4,4]),(4,[7,8]),(1,[9]),(3,[2,3]),(1,[2,3])])
rdd_2=rdd_1.combineByKey(createCombiner=lambda x:[sum(x)],
mergeValue=lambda x,y:[sum(x)+sum(y)],
mergeCombiners=lambda x,y:[sum(x)+sum(y)])
rdd_3=rdd_2.map(lambda x:(x[0],x[1][0]))
print(rdd_3.collect())
其结果如下:
[(4, 15), (1, 21), (3, 13)]
foldByKey()和aggregateByKey()算子与fold()、aggregate()算子效果相同,不过是按key进行聚合操作。其具体用法如下:
rdd_1=sc.parallelize([('a',1),('b',2),('b',4),('c',5),('c',10)])
rdd_2=rdd_1.foldByKey(2, func=lambda x,y:x+y)
rdd_3=rdd_1.aggregateByKey(zeroValue=(0,0),
seqFunc=lambda x,y:(x[0]+y,x[1]+1),
combFunc=lambda x,y:(x[0]+y[0],x[1]+y[1]))
print(rdd_1.glom().collect())
print(rdd_2.collect())
print(rdd_3.collect())
其结果如下:
[[('a', 1), ('b', 2)], [('b', 4), ('c', 5), ('c', 10)]]
[('b', 10), ('c', 17), ('a', 3)]
[('b', (6, 2)), ('c', (15, 2)), ('a', (1, 1))]
通过查询源码可以发现这两个算子底层也是用combineByKey()来实现的。这里重点说一下foldByKey()的计算结果。由foldByKey()的源码可以知道zeroValue只作用在combineByKey()中的createCombiner中。 所以key=a的元素只执行了createCombiner操作,所以key=a的value=2+1=3; key=b的元素分布在两个分区中,所以createCombiner执行2次,combFunc执行一次,所以key=b的value=(2+2)+(2+4)=10;key=c只在一个分区中,但有两个元素,所以createCombiner执行一次,seqFunc执行一次,所以key=c的value=(2+5)+10=17。
- groupByKey()算子
groupByKey()算子可以把所有相同key的元素合并成一个元素,即改元素的key不变,value则聚集到一个集合中。其具体用法如下:
rdd_1=sc.parallelize([(1,[2,5]),(3,[4,4]),(4,[7,8]),(1,[9]),(3,[2,3]),(1,[2,3])])
rdd_2=rdd_1.groupByKey().map(lambda x:(x[0],list(x[1])))
print(rdd_2.collect())
其结果如下:
[(4, [[7, 8]]), (1, [[2, 5], [9], [2, 3]]), (3, [[4, 4], [2, 3]])]
这里使用map()是为了把groupByKey()的结果清晰明了的展示出来。
- sortByKey()算子
sortByKey()算子可以对键值对RDD按key进行排序,使用参ascending指定升序或降序。其具体用法如下:
rdd_1=sc.parallelize([(1,[2,5]),(3,[4,4]),(4,[7,8]),(1,[9]),(3,[2,3]),(1,[2,3])])
res_1=rdd_1.sortByKey(ascending=False)
print(res_1.collect())
其具体用法如下:
[(4, [7, 8]), (3, [4, 4]), (3, [2, 3]), (1, [2, 5]), (1, [9]), (1, [2, 3])]
- sampleByKey()算子
sampleByKey()与sample()类似,不同的地方在于sampleyByKey()中fractions的参数类型为dic类型,并且键值对RDD中每个key都必须在dict中出现。其用法如下:
rdd_1=sc.parallelize([('a',1),('b',2),('b',4),('a',3),('a',10),('b',11)])
rdd_2=rdd_1.sampleByKey(withReplacement=False,fractions={'a':0.5,'b':0.5})
print(rdd_2.collect())
其结果如下:
[('a', 3), ('b', 11)]
- subtractByKey()算子
subtractByKey()算子可以对两个键值对RDD进行差集操作。这个算子只比较key,并不会对value进行比较。其具体用法如下:
rdd_1=sc.parallelize([('a',1),('b',2),('b',4)])
rdd_2=sc.parallelize([('a',4)])
rdd_3=rdd_1.subtractByKey(rdd_2)
print(rdd_3.collect())
其结果如下:
[('b', 2), ('b', 4)]
- join类算子
Spark中常用的join类算子主要由:join()算子、leftOuterJoin()算子、rightOuterJoin()算子、fullOuterJoin()算子。这些算子与数据库中的内连接、左外连接、右外连接、全外连接作用相似。其用法如下:
rdd_1=sc.parallelize([('a',1),('b',2),('b',4),('c',5)])
rdd_2=sc.parallelize([('a',2),('b',4),('d',7)])
rdd_3=rdd_1.join(rdd_2)
rdd_4=rdd_1.leftOuterJoin(rdd_2)
rdd_5=rdd_1.rightOuterJoin(rdd_2)
rdd_6=rdd_1.fullOuterJoin(rdd_2)
print(rdd_3.collect())
print(rdd_4.collect())
print(rdd_5.collect())
print(rdd_6.collect())
其结果如下:
[('b', (2, 4)), ('b', (4, 4)), ('a', (1, 2))]
[('b', (2, 4)), ('b', (4, 4)), ('c', (5, None)), ('a', (1, 2))]
[('b', (2, 4)), ('b', (4, 4)), ('a', (1, 2)), ('d', (None, 7))]
[('b', (2, 4)), ('b', (4, 4)), ('c', (5, None)), ('a', (1, 2)), ('d', (None, 7))]
- mapValues()算子、flatMapValues()算子
这两个算子与map()算子、flatMap()算子的区别在于这两个算子的操作对象为value值。其用法如下:
rdd_1=sc.parallelize([(1,[2,5]),(3,[4,4]),(4,[7,8]),(1,[9]),(3,[2,3]),(1,[2,3])])
rdd_2=rdd_1.mapValues(lambda x:[i**2 for i in x])
rdd_3=rdd_1.flatMapValues(lambda x:[i**2 for i in x])
print(rdd_2.collect())
print(rdd_3.collect())
其具体结果如下:
[(1, [4, 25]), (3, [16, 16]), (4, [49, 64]), (1, [81]), (3, [4, 9]), (1, [4, 9])]
[(1, 4), (1, 25), (3, 16), (3, 16), (4, 49), (4, 64), (1, 81), (3, 4), (3, 9), (1, 4), (1, 9)]
- keys()算子和values()算子
keys()算子和values()算子与Python中dict中的对应方法相同。具体用法如下:
rdd_1=sc.parallelize([(1,[2,5]),(3,[4,4]),(4,[7,8]),(1,[9]),(3,[2,3]),(1,[2,3])])
rdd_2=rdd_1.keys()
rdd_3=rdd_1.values()
print(rdd_2.collect())
print(rdd_3.collect())
其结果如下;
[1, 3, 4, 1, 3, 1]
[[2, 5], [4, 4], [7, 8], [9], [2, 3], [2, 3]]
这里要注意,keys()算子得到的结果并不会去重。
- cogroup()算子
cogroup()算子对两个(key,value)形式的RDD根据key进行组合,相当于根据key进行并集操作。其具体用法如下:
rdd_1=sc.parallelize([('a',1),('b',4),('c',19)])
rdd_2=sc.parallelize([('a',1),('b',6),('d',15)])
rdd_3=rdd_1.cogroup(rdd_2).mapValues(lambda x:[list(i) for i in x])
rdd_4=rdd_2.cogroup(rdd_1).mapValues(lambda x:[list(i) for i in x])
print(rdd_3.collect())
print(rdd_4.collect())
其具体结果如下:
[('b', [[4], ['6']]), ('c', [[19], []]), ('a', [[1], [1]]), ('d', [[], [15]])]
[('b', [['6'], [4]]), ('c', [[], [19]]), ('a', [[1], [1]]), ('d', [[15], []])]
2.2 行动算子
- countByKey()算子
countByKey()算子可以按key统计元素出现的次数。其用法如下:
rdd_1=sc.parallelize([(1,[2,5]),(3,[4,4]),(4,[7,8]),(1,[9]),(3,[2,3]),(1,[2,3])])
res_1=rdd_1.countByKey()
print(res_1)
其用法如下:
defaultdict(int, {1: 3, 3: 2, 4: 1})
- collectAsMap()算子
collectAsMap()算子将所有(key,value)转化为类似Python中的dict的结果。其用法如下:
rdd_1=sc.parallelize([('a',1),('b',4),('c',19),('a',[1,2])])
res_1=rdd_1.collectAsMap()
print(res_1)
其具体如下 :
{'a': [1, 2], 'b': 4, 'c': 19}
这里要注意, Python中的dict中key只能出现一次,所以collectAsMap()中的结果中每一个key也能出现一次,其对应的value为该key最后一次出现时对应的value值。
- lookup()算子
lookup()算子可以查找对应key的value值。其用法如下:
rdd_1=sc.parallelize([('a',1),('b',4),('c',19),('a',[1,2])])
res_1=rdd_1.lookup('a')
print(res_1)
其结果如下:
[1, [1, 2]]
其他类型的RDD算子:Pyspark: RDD及其常用算子_Sun_Sherry的博客-优快云博客