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

Apache Spark常犯的错误及解决方法……

zh:

Spark 是一个用于处理大数据的框架。在第一部分中,我们重点介绍了 Spark 的基础知识以及 为什么它这么快

  • 在这篇博客中,我们将重点讨论一些常见的错误及其修复和优化方法,这些方法将用于提升我们Spark应用程序的性能和内存利用率。
  • 这些内容包括集群的优化、调整配置值、代码层面的优化等。
错误 1. 不了解惰性计算

Spark 并不是像传统脚本那样一行行地执行代码。

    data = spark.read.csv("large_file.csv")  
    data.filter(data["age"] > 30)  
    print('年龄筛选完成。')

这里会执行打印语句,但 Spark 还未执行过滤操作。

Spark 使用惰性计算,也就是说它只会在触发动作(如 .collect().saveAsTextFile())时执行转换操作。如果你期望立即看到结果,这种情况下可能会导致困惑。

  • 弄清楚转换(延迟操作)和行动(触发)之间的区别。
data = spark.read.csv("large_file.csv")  
filtered_data = data.filter(data["age"] > 30)  
filtered_data.show() # 这会触发执行
错误 2: 没有考虑到数据分布,使用默认的分区方式。

数据分区不当可能导致工作分配不均,从而导致某些任务会变慢甚至系统崩溃。

spark/apache-spark-优化技巧

  • 忽略分区操作
    数据分区对于性能至关重要,尤其是在连接数据集时更是如此。如果不进行适当的分区,Spark 可能会反复进行数据重排,从而导致不必要的性能损失。

并行处理在优化Spark作业时扮演着非常重要的角色。每个分区或任务都需要一个核心来处理。

  • 使用过多或过少的分区
    过多的分区会增加不必要的开销,而过少的分区会导致集群资源利用率不高。这需要根据数据量和集群资源来调整。通常建议分区数量是核心数的2到3倍。

不调整大型数据集的分区可能导致处理不均衡。

    large_data = spark.read.csv("large_file.csv")  
    print(large_data.rdd.getNumPartitions()) # 这可能会显示默认的分区数量,这个数字可能较高或较低

2.2 使用 .repartition() .coalesce() 来调整分区数量

    partitioned_data = large_data.repartition(10)  
    print(partitioned_data.rdd.getNumPartitions()) # 当前设置为10个分区

**.重新分区(numPartitions)**

  • 用于增加或减少分区的数量。它会在集群中的节点之间重新分配数据,因此这是一项昂贵的操作。
  • 当你需要更多分区或在完成连接操作后,用于均匀分配数据
  • 使用 .repartition() 函数,当你需要增加分区或需要完全重新洗牌时。

**.coalesce(numPartitions)**:将分区数量减少到指定数目。

  • 主要用于减少分区数量。它通过在同一个节点上合并分区来避免数据重新分配,因此它比**.repartition()**更经济
  • 在处理结束时用于合并分区,特别适合准备输出时。示例:data.coalesce(5)
  • 当你想要减少分区而不进行数据重新分配以提高效率时,使用.coalesce()
错误 3. 没有正确地使用缓存/Persist

未对多次重复使用的数据进行缓存。
Spark 每次都会重新计算这些转换,如果这些计算过程比较复杂,这可能会消耗很多资源。

## 没有使用缓存多次使用数据:

多次使用数据而没有使用缓存:
## 过滤年龄大于30的数据
filtered_data = data.filter(data["age"] > 30)  
## 计算过滤后的数据条数
filtered_data.count()  
## 显示过滤后的数据
filtered_data.show()

每次动作都会再次执行过滤步骤。

对于频繁用于多个步骤中的数据,请使用 .cache().persist(),等等。

    # 如果你需要重复使用这些数据,请先将其缓存(即保存数据以提高后续访问速度)
    filtered_data = data.filter(data["age"] > 30).cache()  
    filtered_data.count()  # 计算过滤后的数据条数  
    filtered_data.show()  # 显示过滤后的数据
    # 再次显示时会更快

3.1 缓存和持久化使用不当
很多人没注意到缓存(例如 cache()persist()),这可以避免在迭代过程中或重复访问时重新计算。

然而,缓存所有内容可能会引发内存问题。仅在确实需要时才缓存,并确保在不再需要数据时执行 unpersist

3.2 过长的转换链
没有中间操作或缓存的很长的转换链会使流程变复杂,增加执行时长。有时最好把复杂的链拆分一下,加入一些操作或把数据缓存起来。

Error 4. 配置洗牌操作不当

不了解 shuffle 是如何工作的会导致变慢或崩溃。默认的 shuffle 在大型连接时可能会引起延迟或错误。

  • shuffle 操作(如 joingroupBy)对网络和内存消耗较大。如果配置不当,可能可能导致这些操作失败。

我们把large_data1和large_data2通过'id'这个字段连接在一起,然后展示出来的。

    joined_data = large_data1.join(large_data2, "id")  
    joined_data.show()
  • 根据数据大小调整 shuffle 相关的配置(例如 spark.sql.shuffle.partitions),并尽可能减少 shuffle。
    spark.conf.set("spark.sql.shuffle.partitions", "100") # 根据数据量调整分区数量  
    合并数据 = large_data1.join(large_data2, "id")  
    合并数据.show()
为驱动而收集大量数据集

请注意:在处理大型数据集时不要使用 .collect().take()

    all_data = data.collect() # 将整个数据集收集到驱动器中:

这可能会引发内存错误,当data太大时,

  • 使用collect操作时: collect会将整个数据集带到驱动程序中,可能导致驱动程序崩溃。通常最好使用如taketakeSample这样的操作来处理少量数据,并且除非数据集足够小,否则避免使用collect
  • 尽量减少收集的数据量,或者直接将数据写入存储中,而不是将其带到驱动程序中。
    sample_data = data.limit(1000).collect()  # 只收集1000条数据  
    # 或者直接将数据写入存储路径 "output_path"
误区6:忽略优化技术

不使用诸如广播连接或Catalyst优化器之类的优化技术。

Spark 有一些优化技术来提升性能的表现,但需要理解何时使用它们。

    large_data.join(small_data, "id").show() # 根据"id"字段在大型数据表和小型数据表之间连接会相当慢

使用广播连接来处理小数据量的表,并使用DataFrame接口,这些接口经过Spark Catalyst优化器的优化。

    # 我们对小数据集使用广播连接来优化连接操作  
     from pyspark.sql.functions import broadcast  
     large_data.join(broadcast(small_data), "id").show() # 广播连接可以优化这个连接操作

spark/apache-spark-优化技巧 (https://www.toptal.com/spark/apache-spark-optimization-techniques)

错误 7. 不太给力的聚合
  • 错误:在不了解其对性能影响的情况下跑聚合。

.groupBy().reduceByKey() 这样的操作会涉及大量的数据洗牌(shuffle),在处理大规模数据集时可能会变得很慢。尽可能使用 预聚合 的数据或在洗牌前进行局部聚合。这样在进行数据洗牌之前先执行局部聚合会更有效。

例子

    # 不要直接对大量数据进行分组操作  
    data.groupBy("category").sum("amount").show()  

    # 尽可能在 map 端进行聚合  
    data = data.map(lambda x: (x['category'], x['amount'])) \  
               .reduceByKey(lambda a, b: a + b)
  • 使用groupByKey而不是reduceByKey
    groupByKey将所有具有相同键的值发送到单个执行器,这可能导致内存不足的问题。而reduceByKey会在将数据发送到其他节点之前,在每个分区上先进行局部缩减,从而通常更高效,而且更节省内存。

其他步骤……

优化Spark配置: 这包括更改Spark属性。例如:调整Spark执行器,调整Spark内存……

优化存储: 使用正确的文件格式,例如ORC和Parquet,这些格式内存效率高,可以加速查询。

记录转换步骤

在调试或优化过程中,记录哪些转换步骤被应用是非常重要的。这样当某个转换或操作失败时,就能更容易识别问题和瓶颈。

点击查看更多内容
TA 点赞

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

评论

作者其他优质文章

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

100积分直接送

付费专栏免费学

大额优惠券免费领

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

举报

0/150
提交
取消