Spark RDD 行动算子(Action)详解

一、行动算子概述

在 Spark 中,RDD 算子分为两大类:转换算子和行动算子。理解两者的区别是掌握 Spark 编程的关键。

转换算子:返回的一定是 RDD。转换算子被调用之后,匿名函数的逻辑代码并不会立即被执行,只是由 Driver 记录算子的调用轨迹。

行动算子:返回的一定不是 RDD。当调用行动算子时,转换算子的逻辑代码才会真正被执行。

简单来说,转换算子构建执行计划,行动算子触发执行。

二、环境准备

在开始使用行动算子之前,需要先创建 SparkContext 环境。

python 复制代码
from pyspark import SparkContext, SparkConf
1、创建环境
conf = SparkConf().setAppName("Demo15_TR_mapValues").setMaster("local[*]")
sc = SparkContext(conf=conf)

三、常用行动算子详解

(1)reduce():聚合所有元素

通过 func 函数聚集 RDD 中的所有元素,先聚合分区内的数据,再聚合分区间的数据。

需求:创建一个 RDD,将所有元素聚合到结果。

python 复制代码
rdd1 = sc.parallelize(range(1, 11))
result = rdd1.reduce(lambda x1, x2: x1 + x2)
print(result)  # 55

(2)collect():收集所有元素到 Driver 端

返回一个包含此 RDD 中所有元素的列表。

需求:创建一个 RDD,并将 RDD 内容收集到 Driver 端打印。

python 复制代码
rdd1 = sc.parallelize(range(1, 11))
arr = rdd1.collect()
print(type(arr))  # <class 'list'>
for i in arr:
    print(i)

(3)collectAsMap():转换为字典

将二元组类型的 RDD 转换成一个字典。

需求:创建一个二元组 RDD,并收集到 Driver 端打印。

python 复制代码
rdd_kv = sc.parallelize([("zs", 11), ("lisi", 22), ("ww", 33), ("zl", 44)], 2)
dict = rdd_kv.collectAsMap()
print(type(dict))
for k, v in dict.items():
    print(k, v)

(4)count():统计元素个数

返回 RDD 中元素的个数。

python 复制代码
rdd1 = sc.parallelize(range(1, 101))
i = rdd1.count()
print(i)  # 100

(5)take(n):获取前 n 个元素

返回一个由 RDD 的前 n 个元素组成的数组。

python 复制代码
rdd1 = sc.parallelize([4, 5, 23, 342, 312, 455, 6])
list1 = rdd1.take(3)
print(list1)  # [4, 5, 23]

(6)saveAsTextFile(path):保存到文件

将 RDD 中的元素以文本文件的形式保存到指定路径。

python 复制代码
rdd1 = sc.parallelize(range(1, 11))
rdd1.saveAsTextFile("output/rdd_result")

(7)foreach(func):遍历每个元素

对 RDD 中的每个元素执行 func 函数,常用于打印或写入外部存储。

python 复制代码
rdd1 = sc.parallelize(range(1, 11))
rdd1.foreach(lambda x: print(x))

(8)foreachPartition(func):按分区遍历

对 RDD 的每个分区执行 func 函数,分区内的所有元素作为一个整体传入。适合批量操作,如批量写入数据库。

python 复制代码
rdd1 = sc.parallelize(range(1, 11), 2)
def process_partition(iterator):
for item in iterator:
print(item)
rdd1.foreachPartition(process_partition)

四、RDD 缓存与持久化

当 RDD 被多次使用时,可以将其缓存或持久化,避免重复计算。

python 复制代码
rdd1.cache()   # 存储级别:MEMORY_ONLY
rdd1.persist()  # 存储级别:可指定

Spark 提供了多种存储级别,适用于不同场景:

  • DISK_ONLY:存储在磁盘,副本数为 1,适用于数据超大、内存完全放不下的场景。
  • MEMORY_ONLY:存储在内存,副本数为 1,适用于数据量小、内存充足的场景。
  • MEMORY_AND_DISK:可以存储在内存和磁盘,副本数为 1,适用于数据较大、内存可能放不下的场景。
python 复制代码
# DISK_ONLY:存储在磁盘,副本数为1
StorageLevel.DISK_ONLY = StorageLevel(True, False, False, False)
StorageLevel.DISK_ONLY_2 = StorageLevel(True, False, False, False, 2)
StorageLevel.DISK_ONLY_3 = StorageLevel(True, False, False, False, 3)
MEMORY_ONLY:存储在内存,副本数为1
StorageLevel.MEMORY_ONLY = StorageLevel(False, True, False, False)
StorageLevel.MEMORY_ONLY_2 = StorageLevel(False, True, False, False, 2)
MEMORY_AND_DISK:存储在内存 + 磁盘,副本数为1
StorageLevel.MEMORY_AND_DISK = StorageLevel(True, True, False, False)
StorageLevel.MEMORY_AND_DISK_2 = StorageLevel(True, True, False, False, 2)
StorageLevel.OFF_HEAP = StorageLevel(True, True, True, False, 1)
StorageLevel.MEMORY_AND_DISK_DESER = StorageLevel(True, True, False, True)

四、foreachPartition 实战:写入 MySQL

foreachPartition 除了用于打印,更常见的场景是批量写入外部存储,例如 MySQL。下面演示如何通过 foreachPartition 将 RDD 中的数据批量插入数据库。

需求:创建一个二元组 RDD,并通过 foreachPartition 将数据写入 MySQL 的 t_student 表。

python 复制代码
from pyspark import SparkContext, SparkConf
import pymysql  # pip install pymysql
1、在 mysql 中创建表
创建一个操作 mysql 的函数
def func_mysql(iter):
# 连接 mysql 信息
conn = pymysql.connect(user="root", password="123456", host="master", database="bigdata", port=3306)
# 创建游标
cursor = conn.cursor()
# 循环遍历 iter 执行 sql 语句
for i in iter:
cursor.execute("insert into t_student values(%s,%s)", i)
# 提交
conn.commit()
# 关闭连接
conn.close()
if name == 'main':
conf = SparkConf().setAppName("ToMySQL").setMaster("local[*]")
sc = SparkContext(conf=conf)
# 创建一个 RDD
data = sc.parallelize([(1, "zhangsan"), (2, "lisi"), (3, "wangwu")], 2)
# 调用函数
data.foreachPartition(func_mysql)
# 关闭环境
sc.stop()

通过 foreachPartition,每个分区只建立一次数据库连接,分区内的所有数据批量提交,相比逐条插入效率更高。

五、广播变量(Broadcast)

广播变量用于高效分发较大的对象,向所有工作节点发送一个较大的只读值,以供一个或多个 Spark 操作使用。

需求:读取 HDFS 文件中的单词,然后进行筛选,最终保留的数据为 Set 集合中规范的单词。

python 复制代码
from pyspark import SparkContext, SparkConf
if name == 'main':
conf = SparkConf().setAppName("ToMySQL").setMaster("local[*]")
sc = SparkContext(conf=conf)
# 定义一个集合
set = {"hadoop", "hdfs"}
# broadcast:将只读变量广播到集群,返回一个 Broadcast 对象,
# 以便在分布式函数中读取它。该变量只会发送到每个集群一次。
bc = sc.broadcast(set)
# 从 hdfs 上读取数据
rdd1 = sc.textFile("hdfs://master:9000/aaa.txt")
rdd2 = rdd1.flatMap(lambda x: x.split(",")).filter(lambda i: i not in bc.value).map(lambda v: (v, 1)).reduceByKey(lambda x1, x2: x1 + x2)
list1 = rdd2.collect()
print(list1)
sc.stop()

注意事项:

  • RDD 不能被广播,可以将 RDD 的计算结果广播出去。
  • 广播变量只能在 Driver 端定义,不能定义在 Executor 端。
  • 在 Driver 端可以修改广播变量的值,在 Executor 端无法修改广播变量的值。

六、累加器(Accumulator)

累加器用来把 Executor 端变量聚合到 Driver 端。在 Driver 程序定义的变量,在 Executor 端的每个 Task 都会得到这个变量的一份新的副本,每个 task 更新这些副本的值后传回 Driver 进行 merge。

需求:对搜狗日志的数据进行处理,统计 10 点搜索的数据一共有多少条。

python 复制代码
from pyspark import SparkContext, SparkConf
SparkContext(RDD 时代的入口)
conf = SparkConf().setAppName("accumulator").setMaster("local[*]")
sc = SparkContext(conf=conf)
模拟数据
data = [
"10:00:01\tabc123\t[python教程]\t1\t1\thttp://a.com",
"10:00:05\tdef123\t[大数据]\t2\t1\thttp://b.com",
"10:00:12\tghi123\t[spark]\t1\t2\thttp://c.com",
"10:00:23\tjkl123\t[日志分析]\t3\t1\thttp://d.com",
"10:00:34\tmno123\t[hadoop]\t1\t1\thttp://e.com",
"10:00:45\tpqr123\t[scala]\t2\t2\thttp://f.com",
"10:00:56\tstu123\t[累加器]\t4\t1\thttp://g.com",
"10:01:07\tvwx123\t[日志统计]\t1\t1\thttp://h.com",
"10:01:18\tyz0123\t[foreach]\t2\t3\thttp://i.com",
"10:01:29\tabc987\t[driver]\t1\t1\thttp://j.com",
# 以下为非 10 点的数据,用于验证过滤逻辑
"11:00:01\txxx111\t[测试1]\t1\t1\thttp://k.com",
"09:00:01\tyyy222\t[测试2]\t1\t1\thttp://l.com",
]
rdd = sc.parallelize(data, 2)
rdd2 = rdd.map(lambda l: l.split("\t"))
print(rdd2.collect())  # ['10:00:01', 'abc123', '[python教程]', '1', '1', 'http://a.com']
定义一个变量 sum
_sum = 0
使用累加器变量进行定义
acc = sc.accumulator(0)
定义一个函数
def getNum(tup):
if tup[0][0:2] == "10":
acc.add(1)
rdd2.foreach(getNum)
print(f"10点中搜索条数是:{acc.value}")
关闭环境
sc.stop()

五、关闭环境

程序执行完毕后,需要关闭 SparkContext 释放资源。

python 复制代码
sc.stop()
相关推荐
dlhto1 小时前
Windows 使用 Rufus 制作 AlmaLinux 10.2 启动 U 盘:从 ISO 下载到服务器安装
运维·服务器·windows
小矮强2 小时前
输入凭证也没用?Windows 共享文件夹“来宾访问被安全策略阻止”
windows·经验分享
skywalk81633 小时前
Virtualbox 从7.2.20降级到7.2.18 @FreeBSD系统
windows·virtualbox·freebsd
DogDaoDao3 小时前
Windows 双 Shell 全面对比:CMD 与 PowerShell 的技术剖析
windows·shell·测试·powershell·cmd·程序员开发
长不大的杨瑞杰3 小时前
Windows 安装 OpenCode v2:原生、WSL、Desktop 怎么选?完整配置与避坑指南
人工智能·windows·ai agent·opencode
Misnice5 小时前
Python 内置函数大全
开发语言·windows·python
运维踩坑日记7 小时前
【无标题】
linux·运维·windows
pjj198547 小时前
Spark RDD 核心知识详解:算子、依赖与执行模型
大数据·分布式·spark