为了账号安全,请及时绑定邮箱和手机立即绑定

Apache Spark中的窄转换和宽转换详解

掌握 Apache Spark 转换的基础知识,通过探索窄转换和宽转换之间的关键差异来了解它们如何影响数据流和性能,并通过实际示例学习,这些示例包括 RDD 和 DataFrame。

在 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()  # 输出会把每个单词单独显示成一行
  1. 广泛转型

一次大规模的转换需要来自多个分区的数据来创建单个输出分区。这通常会触发一次数据重分区,即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 应用程序,更好地利用分布式数据处理。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消