在 Apache Spark 中,转换构成了数据在分布式集群中处理的核心。Spark 中的转换是一种操作,它从现有的 RDD 或 DataFrame 生成一个新的 RDD 或 DataFrame,从而为分布式数据处理做好准备。总体而言,这些转换分为两类——窄转换(Narrow Transformations)和宽转换(Wide Transformations)——每种转换都会影响数据流、效率以及 Spark 应用的整体性能表现。
在这篇文章中,我们将深入探讨这两种转换,解释它们的区别并展示如何操作,并通过RDD和DataFrame中的代码示例来具体展示每种转换的过程。
1. 狭义转换所谓的窄转换是指每个输出分区仅依赖于一个输入分区。这意味着生成输出分区所需的数据就存在于输入数据所在的分区中。因此,Spark 可以在不移动不同节点之间的数据的情况下处理这些转换,从而减少网络开销,提高处理速度。
窄变换的主要特点:- 节点间无数据混洗。
- 分区内的计算。
- 因减少网络I/O,执行更快。
映射() (用于将函数应用于集合中每个项目的操作)一些窄变换的例子
map()函数会将给定的函数处理每个元素,无需在分区之间传输任何数据。这里指的元素是RDD或DataFrame中的每个元素。
RDD 示例
# 假设 SparkContext (sc) 已经被初始化
rdd = sc.parallelize([1, 2, 3, 4, 5])
mapped_rdd = rdd.map(lambda x: x * 2)
print(mapped_rdd.collect()) # 打印映射后的 RDD 收集结果:[2, 4, 6, 8, 10]
数据框示例
from pyspark.sql import SparkSession
from pyspark.sql.functions import col
spark = SparkSession.builder.appName("NarrowTransformationExample").getOrCreate()
df = spark.createDataFrame([(1,), (2,), (3,), (4,), (5,)], ["value"])
mapped_df = df.withColumn("doubled_value", col("value") * 2)
mapped_df.show() # 这里显示的是原始值及其两倍的值
filter
filter变换会保留符合特定条件的元素,对每个分片单独进行处理。
RDD 示例(这里RDD指的是分布式数据集)
过滤后的结果如下:
filtered_rdd = rdd.filter(lambda x: x % 2 == 0)
print(filtered_rdd.collect()) # 输出结果为: [2, 4]
DataFrame 示例
filtered_df = df.filter(col("value") % 2 == 0)
filtered_df.show() # 显示包含偶数值的行,即value列中的偶数
flatMap 方法
与 map()
类似,但 flatMap()
让每个输入元素产生零个、一个或多个输出元素,在各自分区中独立操作。
RDD 示例
注:RDD(弹性分布式数据集)是Apache Spark中的一种基本的数据抽象。
rdd = sc.parallelize(["hello world", "spark transformations"])
flatMappedRDD = rdd.flatMap(lambda x: x.split(" ")) # 将字符串列表转换为单词列表
print(flatMappedRDD.collect()) # 输出结果:['hello', 'world', 'spark', 'transformations']
# 创建一个RDD,包含两个字符串
# 扁平化后的RDD
Convert a list of strings into a list of words
输出结果:['hello', 'world', 'spark', 'transformations']
Create an RDD containing two strings
扁平化后的RDD
DataFrame 示例
DataFrame 直接不支持 flatMap()
,但我们可以通过使用 explode()
来达到类似的效果,尤其是在处理数组这种情况时。
从pyspark.sql.functions导入explode和split函数
df = spark.createDataFrame([("hello world",), ("spark transformations",)], ["sentence"])
flat_mapped_df = df.withColumn("word", explode(split(col("sentence"), " ")))
flat_mapped_df.show() # 输出会把每个单词单独显示成一行
- 广泛转型
一次大规模的转换需要来自多个分区的数据来创建单个输出分区。这通常会触发一次数据重分区,即Spark在集群中重新分发数据的过程,从而增加了网络I/O。这种转换通常更耗费资源,并且由于需要在各个节点之间进行协调,可能会影响性能。
广泛变换的主要特点:- 数据在节点间移动,从而增加了网络和计算的负担。
- 跨分区的依赖关系。
- 通常因为需要重新分配数据而变慢。
groupByKey() - 将具有相同键的元素分组在一起.一些宽变换的例子
groupByKey() 这个操作是基于键来分组数据的。它会把每个键在各个分区中的所有值收集起来,从而触发一次数据洗牌。
RDD 示例(示例说明)
rdd = sc.parallelize([("a", 1), ("b", 2), ("a", 3), ("b", 4)])
# 将rdd按键分组并映射值
grouped_rdd = rdd.groupByKey().mapValues(list)
print(grouped_rdd.collect()) # 输出: [('a', [1, 3]), ('b', [2, 4])]
DataFrame 例子
df = spark.createDataFrame([("a", 1), ("b", 2), ("a", 3), ("b", 4)], ["key", "value"])
grouped_df = df.groupBy("key").agg({"value": "collect_list"})
grouped_df.show() # 这里会输出根据键分组后的值列表
reduceByKey()
reduceByKey()
转换类似于groupByKey()
,但在数据洗牌过程中应用了一个聚合函数,通过在洗牌前局部聚合值来优化数据移动过程。
RDD 示例
reduced_rdd = rdd.reduceByKey(lambda x, y: x + y)
print(reduced_rdd.collect()) # 输出结果: [('a', 4), ('b', 6)]
数据框
导入pyspark.sql.functions中的sum
reduced_df = df.groupBy("key").agg(sum("value").alias("sum_value"))
reduced_df.show() # 这将显示每个键对应的值的总和
join() 或连接(join)
join()操作基于键值合并两个数据集,这一步需要重新排序以在不同分区中对齐匹配的键。
一个RDD示例(弹性分布式数据集示例)
rdd1 = sc.parallelize([("a", 1), ("b", 2)])
rdd2 = sc.parallelize([("a", 3), ("b", 4)])
joined_rdd = rdd1.join(rdd2)
print(joined_rdd.collect()) # 结果为: [('a', (1, 3)), ('b', (2, 4))]
DataFrame 示例(示例代码)
df1 = spark.createDataFrame([("a", 1), ("b", 2)], ["key", "value1"])
df2 = spark.createDataFrame([("a", 3), ("b", 4)], ["key", "value2"])
joined_df = df1.join(df2, "key")
joined_df.show() # 输出如下:基于key连接的DataFrame
关键差异:窄表转换与宽表转换中的数据重新排序在 Spark 中,典型的连接操作会触发昂贵的跨分区洗牌,而广播连接通过将较小的数据集广播到所有工作节点来优化性能,从而实现本地连接而无需网络开销——使其成为执行高效窄连接转换的强大工具。
窄转换和宽转换之间的主要区别在于数据洗牌。在窄转换中,Spark 在每个分区内部处理数据,避免了网络传输。不过,宽转换就需要重新分配数据到不同的分区,从而引发网络 I/O。例如:
map()
操作在每个分区内部独立地应用函数,而reduceByKey()
则需要先做一次 shuffle,以便相同键的值能够被一起处理。
在 Spark 中,理解转换的类型——无论是窄转换还是宽转换——对于优化分布式数据处理过程至关重要。窄转换通过局部计算提升性能,而宽转换对于需要跨分区的数据聚合或对齐的操作来说至关重要。通过巧妙地结合这两种类型的转换,你可以创建高效且可扩展的 Spark 应用程序,更好地利用分布式数据处理。