目录
一.RDD的相关算子
RDD算子: 指的是RDD对象中提供了非常多的具有特殊功能的函数, 我们将这些函数称为算子 (大白话:指的RDD的API)
相关的算子的官方文档: Spark Core — PySpark 3.1.2 documentation
二.RDD算子的分类
整个RDD算子, 共分为两大类:Transformation转换算子和Action动作算子
Transformation转换算子:
1- 所有的转换算子在执行完成后,都会返回一个新的RDD
2- 所有的转换算子都是【Lazy惰性】,并不会立即执行,仅仅是在定义计算的规则
3- 转换算子必须遇到【Action动作算子】才会触发执行Action动作算子:
1- 动作算子在执行后,不会返回一个RDD。【要么没有返回值,要么返回的数据类型不是RDD】
2- 动作算子都是【立即执行】的,一个动作算子就会产生一个Job的任务,并且会执行这个动作算子所依赖的其他所有RDD


三.RDD的转换算子
3.1 (单)值类型算子
-
map算子:
-
格式: rdd.map(fn)
-
说明: 主要根据传入的函数,对数据进行一对一的转换操作,传入一行,返回一行
-
输入: init_rdd = sc.parallelize([0,1,2,3,4,5,6,7,8,9]) 需求: 数字加一后返回 代码: init_rdd.map(lambda num:num+1).collect() 结果: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
-
groupBy 算子:
-
格式: rdd.groupBy(fn)
-
说明: 根据传入的函数对数据进行分组操作
-
输入: init_rdd = sc.parallelize([0,1,2,3,4,5,6,7,8,9])
需求: 将数据分成奇数和偶数
代码: init_rdd.groupBy(lambda num:'o' if num%2==0 else 'j').mapValues(list).collect()
结果: [('j', [1, 3, 5, 7, 9]), ('o', [0, 2, 4, 6, 8])]
总结: mapValues(list)将kv数据类型中value转换成列表
-
filter算子:
-
格式:rdd.filter(fn)
-
说明:过滤算子,可以根据函数中指定的过滤条件,对数据进行过滤操作,条件返回True表示保留,返回False 表示过滤
-
输入: init_rdd = sc.parallelize([0,1,2,3,4,5,6,7,8,9]) 需求: 过滤掉数值<=3的数据 代码: init_rdd.filter(lambda num:num>3).collect() 结果: [4, 5, 6, 7, 8, 9]
-
flatMap算子:
-
格式:rdd.flatMap(fn)
-
说明:在map算子的基础上,加入一个压扁的操作, 主要适用于一行中包含多个内容的操作,实现一转多的操作
-
输入: init_rdd = sc.parallelize(['张三 李四 王五','赵六 周日']) 需求: 将姓名一个一个的输出 代码: init_rdd.flatMap(lambda content:content.split()).collect() 结果: ['张三', '李四', '王五', '赵六', '周日']
3.2 双值类型算子
-
union(并集) 和intersection(交集)
-
格式: rdd1.union(rdd2) rdd1.intersection(rdd2)
-
输入: rdd1 = sc.parallelize([3,2,6,8,0])
rdd2 = sc.parallelize([3,2,1,5,7])
并集: rdd1.union(rdd2).collect()
结果: [3, 2, 6, 8, 0, 3, 2, 1, 5, 7]
对并集的结果进行去重: rdd1.union(rdd2).distinct().collect()
结果: [8, 0, 1, 5, 2, 6, 3, 7]
交集: rdd1.intersection(rdd2).collect()
结果: [2, 3]
3.3 key-value数据类型算子
-
groupByKey()
-
格式: rdd.groupByKey()
-
说明: 根据key进行分组操作
-
输入: rdd = sc.parallelize([('c01','张三'),('c02','李四'),('c02','王五'),('c01','赵六'),('c03','田七'),('c03','周八'),('c02','李九')])
需求: 对学生按照班级分组统计
代码: rdd.groupByKey().mapValues(list).collect()
结果: [('c01', ['张三', '赵六']), ('c02', ['李四', '王五', '李九']), ('c03', ['田七', '周八'])]
-
reduceByKey()
-
格式: rdd.reduceByKey(fn)
-
说明: 根据key进行分组,将一个组内的value数据放置到一个列表中,对这个列表基于fn进行聚合计算操作
-
输入: rdd = sc.parallelize([('c01','张三'),('c02','李四'),('c02','王五'),('c01','赵六'),('c03','田七'),('c03','周八'),('c02','李九')])
需求: 统计每个班级学生人数
代码: rdd.map(lambda tup:(tup[0],1)).reduceByKey(lambda agg,curr:agg+curr).collect()
结果: [('c01', 2), ('c02', 3), ('c03', 2)]
-
sortByKey()算子:
-
格式:rdd.sortByKey(ascending=True|False)
-
说明: 根据key进行排序操作,默认按照key进行升序排序,如果需要降序,设置 ascending 参数的值为False
-
输入: rdd = sc.parallelize([(10,2),(15,3),(8,4),(7,4),(2,4),(12,4)])
需求: 根据key进行排序操作,演示升序
代码: rdd.sortByKey().collect()
结果: [(2, 4), (7, 4), (8, 4), (10, 2), (12, 4), (15, 3)]
需求: 根据key进行排序操作,演示降序
代码: rdd.sortByKey(ascending=False).collect()
结果: [(15, 3), (12, 4), (10, 2), (8, 4), (7, 4), (2, 4)]
输入: rdd = sc.parallelize([('a01',2),('A01',3),('a011',2),('a03',2),('a021',2),('a04',2)])
需求: 根据key进行排序操作,演示升序
代码: rdd.sortByKey().collect()
结果: [('A01', 3), ('a01', 2), ('a011', 2), ('a021', 2), ('a03', 2), ('a04', 2)]
总结: 对字符串的key排序的时候,按照ASCII码排序。大写字母排在小写字母前面;如果前面都一样,就继续比较后面的内容,没有内容的比有内容排在前面。
四.RDD的动作算子
-
collect() 算子:
-
格式: collect()
-
作用: 收集各个分区的数据,将数据汇总到一个大的列表返回
-
-
reduce() 算子:
-
格式: reduce(fn)
-
作用: 根据传入的函数对数据进行聚合操作
-
输入: rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10]) 需求: 统计所有元素之和是多少 代码: rdd.reduce(lambda num1,num2:num1+num2) 结果: 55
-
first() 算子:
-
格式: first()
-
说明: 获取第一个元素
-
输入: rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10]) 需求: 获取第一个元素 代码: rdd.first() 结果: 1
-
take() 算子
-
格式: take(N)
-
说明: 获取前N个元素 ,类似于 limit操作
-
输入: rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10]) 需求: 获取前3个元素 代码: rdd.take(3) 结果: [1, 2, 3]
-
top()算子:
-
格式: top(N,[fn])
-
说明: 对数据集进行倒序排序操作,如果kv类型,针对key进行排序,获取前N个元素
-
fn: 可以自定义排序,按照谁来排序
-
输入: rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10])
需求: 获取前3个元素
代码: rdd.top(3)
结果: [10, 9, 8]
输入: rdd = sc.parallelize([('c01',5),('c02',8),('c04',1),('c03',4)])
需求: 按照班级人数排序,取前2个
代码: rdd.top(2,lambda tup:tup[1])
结果: [('c02', 8), ('c01', 5)]
-
count() 算子
输入: rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10]) 需求: 获取一共有多少个元素 代码: rdd.count() 结果: 10
-
foreach() 算子
-
格式: foreach(fn)
-
作用: 对数据集进行遍历操作,遍历后做什么,取决于传入的fn
-
输入: rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10]) 需求: 对数据进行遍历打印 代码: rdd.foreach(lambda num:print(num)) 结果: 6 7 8 9 10 1 2 3 4 5
五.RDD的重要算子
5.1 基本算子

5.2 分区算子
- mapPartitions和foreachPartition
在spark中,对于map算子和foreach算子都提供了分区函数,分别是mapPartitions() 和 foreachPartition()。 map算子和foreach算子,针对的是RDD中每个分区下的每一条数据。而分区函数,则是一次性处理一个分区。RDD->多个分区,每个分区->多条数据
输入: rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10],3)
查看分区情况: rdd.glom().collect()
结果: [[1, 2, 3], [4, 5, 6], [7, 8, 9, 10]]
演示: map和mapPartitions
需求: 对数字加一
================================map==================================
自定义函数:
def fn1(num):
print(num)
return num+1rdd.map(fn1).collect()
结果:
4
5
6
1
2
3
7
8
9
10
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11]请问: fn1被调用了几次?
回答: 【10】
弊端: 【如果有消耗资源的操作,例如:数据库连接关闭、文件打开关闭,这样反复操作,运行效率低】
=============================mapPartitions===========================
自定义函数常规写法:
def fn1(list):
print(list)
new_list = []
for i in list:
new_list.append(i + 1)
return new_listrdd.mapPartitions(fn1)
自定义函数简单写法:
def fn1(list):
print(list)
for i in list:
yield i + 1
return list
rdd.mapPartitions(fn1)结果: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
说明:yield作用将结果临时保存下来
演示: foreach和foreachPartition
需求: 遍历打印
==============================foreach================================
自定义函数:
def fn1(num):
print(num)rdd.foreach(fn1)
结果:
4
5
6
1
2
3
7
8
9
10
==========================foreachPartition===========================
自定义函数:
def fn1(list):
print(list)
for i in list:
print(i)rdd.foreachPartition(fn1)
结果:
<itertools.chain object at 0x7ffba3f94340>
4
5
6
<itertools.chain object at 0x7ffba3f94a60>
1
2
3
<itertools.chain object at 0x7ffba3f949a0>
7
8
9
10
总结: 在使用map和foreach的时候,如果里面涉及到消耗资源(例如:数据库打开和关闭、文件打开和关闭)的操作,建议使用对应的分区算子mapPartitions和foreachPartition;如果没有,它们之间的效率基本相同,没有差别,选哪个都可以。
分区函数: 作用在每个分区上的 非分区函数: 作用在每条数据上的
5.3 重分区算子
repartition算子
-
格式:repartition(分区数)
-
作用:重新对RDD分区数量进行调整,得到一个新的RDD。【既可以进行增大分区,也可以减小分区,必定会触发Shuffle操作】
输入: rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10],3)
查看分区情况: rdd.glom().collect()
结果: [[1, 2, 3], [4, 5, 6], [7, 8, 9, 10]]
增大分区: rdd.repartition(15).glom().collect()
结果: [[], [1, 2, 3], [], [], [], [], [], [7, 8, 9, 10], [4, 5, 6], [], [], [], [], [], []]
减少分区: rdd.repartition(2).glom().collect()
结果: [[1, 2, 3, 7, 8, 9, 10], [4, 5, 6]]
-
coalesce算子
-
格式:coalesce(分区数,[参数2]),参数2表示的是否开启shuffle,如果开启了,即可实现增大分区;如果不开启,仅能减少分区,默认是关闭shuffle
-
作用:重新对RDD分区数量进行调整,得到一个新的RDD。默认只能减少分区数量
-
输入: rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10],3) 查看分区情况: rdd.glom().collect() 结果: [[1, 2, 3], [4, 5, 6], [7, 8, 9, 10]] 减少分区: rdd.coalesce(2).glom().collect() 结果: [[1, 2, 3], [4, 5, 6, 7, 8, 9, 10]] 增大分区: rdd.coalesce(15).glom().collect() 结果: [[1, 2, 3], [4, 5, 6], [7, 8, 9, 10]] 发现增加分区没有生效 将参数2设置为True,再增大分区: rdd.coalesce(15,shuffle=True).glom().collect() 结果: [[], [1, 2, 3], [], [], [], [], [], [7, 8, 9, 10], [4, 5, 6], [], [], [], [], [], []]
repartition 和 coalesce总结:
1、repartition 和 coalesce两个算子都【可以修改RDD的分区数量】
2、repartition本质上是coalesce的一种当参数2为True的简写方案,因为repartition底层调用coalesce函数,将参数2设置为True
3、repartition既可以【增大分区】,也可以【减小分区】,触发shuffle
4、coalesce默认【只能减小分区】,无法增大分区,不触发shuffle。如果要增大分区,需要将参数2调整为True,这时就会触发shuffle
-
partitionBy算子
-
格式:partitionBy(分区数,[参数2]) 参数2是自定义分区规则
-
作用:专门针对kv类型数据进行重新分区,得到一个新的RDD。可以增大分区,也可以减少分区,但是会产生shuffle
注意:
默认根据key进行hash取模划分操作,如果不满意这个分区方案,可以通过参数2自定义分区规则。 自定义分区规则返回的必须是一个int类型的数据,它是分区的编号,编号从0开始
-
输入: rdd = sc.parallelize([(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10)],5)
查看分区情况: rdd.glom().collect()
结果:
[
[(1, 1), (2, 2)],
[(3, 3), (4, 4)],
[(5, 5), (6, 6)],
[(7, 7), (8, 8)],
[(9, 9), (10, 10)]
]
需求: 增大分区,尝试分为11个分区
代码: rdd.partitionBy(11).glom().collect()
结果: [[], [(1, 1)], [(2, 2)], [(3, 3)], [(4, 4)], [(5, 5)], [(6, 6)], [(7, 7)], [(8, 8)], [(9, 9)], [(10, 10)]]
需求: 减少分区,尝试分为2个分区
代码: rdd.partitionBy(2).glom().collect()
结果:
[
[(2, 2), (4, 4), (6, 6), (8, 8), (10, 10)],
[(1, 1), (3, 3), (5, 5), (7, 7), (9, 9)]
]
需求: 将 key>5 放置在一个分区,剩余放置到另一个分区
代码: rdd.partitionBy(2,lambda key:0 if key>5 else 1).glom().collect()
结果:
[
[(6, 6), (7, 7), (8, 8), (9, 9), (10, 10)],
[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
]
注意:
如果数据中,某个key的数据量占了很大的比例,会出现数据倾斜
5.4 聚合算子
-
单值类型的聚合算子
-
reduce(fn1):根据传入函数对数据进行聚合处理
-
fold(defaultAgg,fn1):根据传入函数对数据进行聚合处理,同时支持给agg设置初始值
-
aggregate(defaultAgg, fn1, fn2):根据传入函数对数据进行聚合处理。defaultAgg设置agg的初始值,fn1对各个分区内的数据进行聚合计算,fn2 负责将各个分区的聚合结果进行汇总聚合
-
输入: rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10],3)
查看分区情况: rdd.glom().collect()
结果: [[1, 2, 3], [4, 5, 6], [7, 8, 9, 10]]
需求: 求和计算, 求所有数据之和
================================reduce================================
代码: rdd.reduce(lambda num1,num2:num1+num2)
结果: 55
================================fold================================
代码: rdd.fold(5,lambda num1,num2:num1+num2)
结果: 75
================================aggregate================================
代码:
def fn1(agg,curr):
return agg+curr
def fn2(a,b):
return a+b
rdd.aggregate(5,fn1,fn2)
结果: 75
总结:
reduce的底层是fold,fold底层是aggregate。如果需求能够使用reduce实现,就优先使用reduce;如果不能,再使用fold算子;如果fold算子也不行,就是使用aggregate
5.5关联算子
关联函数,主要是针对kv类型的数据,根据key进行关联操作
相关的算子:
-
join:实现两个RDD的join关联操作
-
leftOuterJoin:实现两个RDD的左关联操作
-
rightOuterJoin:实现两个RDD的右关联操作
-
fullOuterJoin:实现两个RDD的满外(全外)关联操作
输入:
rdd1 = sc.parallelize([('c01','张三'),('c02','李四'),('c02','王五'),('c01','赵六'),('c03','田七'),('c03','周八'),('c02','李九'),('c04','老张')])
rdd2 = sc.parallelize([('c01','大数据一班'),('c02','大数据二班'),('c03','大数据三班'),('c05','大数据五班')])
================================join================================
代码: rdd1.join(rdd2).collect()
结果:
[
('c01', ('张三', '大数据一班')),
('c01', ('赵六', '大数据一班')),
('c02', ('李四', '大数据二班')),
('c02', ('王五', '大数据二班')),
('c02', ('李九', '大数据二班')),
('c03', ('田七', '大数据三班')),
('c03', ('周八', '大数据三班'))
]
================================leftOuterJoin================================
代码: rdd1.leftOuterJoin(rdd2).collect()
结果:
[
('c04', ('老张', None)),
('c01', ('张三', '大数据一班')),
('c01', ('赵六', '大数据一班')),
('c02', ('李四', '大数据二班')),
('c02', ('王五', '大数据二班')),
('c02', ('李九', '大数据二班')),
('c03', ('田七', '大数据三班')),
('c03', ('周八', '大数据三班'))
]
================================rightOuterJoin================================
代码: rdd1.rightOuterJoin(rdd2).collect()
结果:
[
('c05', (None, '大数据五班')),
('c01', ('张三', '大数据一班')),
('c01', ('赵六', '大数据一班')),
('c02', ('李四', '大数据二班')),
('c02', ('王五', '大数据二班')),
('c02', ('李九', '大数据二班')),
('c03', ('田七', '大数据三班')),
('c03', ('周八', '大数据三班'))
]
================================fullOuterJoin================================
代码: rdd1.fullOuterJoin(rdd2).collect()
结果:
[
('c04', ('老张', None)),
('c05', (None, '大数据五班')),
('c01', ('张三', '大数据一班')),
('c01', ('赵六', '大数据一班')),
('c02', ('李四', '大数据二班')),
('c02', ('王五', '大数据二班')),
('c02', ('李九', '大数据二班')),
('c03', ('田七', '大数据三班')),
('c03', ('周八', '大数据三班'))
]
本文详细介绍了Spark中的RDD算子,包括转换算子和动作算子。转换算子如map、groupBy、filter、flatMap等,它们是惰性的,只有遇到动作算子如collect、reduce、first、take等时才会触发执行。文章还提到了RDD的重要算子如partitionBy、repartition、coalesce等,以及聚合算子reduce、fold和aggregate,以及不同类型的关联操作join、leftOuterJoin、rightOuterJoin和fullOuterJoin。
1750

被折叠的 条评论
为什么被折叠?



