Spark性能优化:核心技巧与实战指南

目录

一、核心优化思想

二、代码层面优化(最有效、成本最低)

[1. 选择高性能算子](#1. 选择高性能算子)

[2. 广播大变量(Broadcast)](#2. 广播大变量(Broadcast))

[3. 使用Kryo序列化](#3. 使用Kryo序列化)

[4. 持久化(Cache/Persist)的正确使用](#4. 持久化(Cache/Persist)的正确使用)

三、数据倾斜专项处理

四、Shuffle调优

五、资源与并行度调优

[1. 资源配置参数(提交作业时指定)](#1. 资源配置参数(提交作业时指定))

[2. 动态资源分配(Dynamic Allocation)](#2. 动态资源分配(Dynamic Allocation))

[3. 并行度](#3. 并行度)

[六、Spark SQL 与 DataFrame 特定优化](#六、Spark SQL 与 DataFrame 特定优化)

七、存储与格式优化

优化总结与步骤

一、核心优化思想

  1. 避免数据倾斜(Data Skew):这是Spark作业最大的"性能杀手"。指数据在Partition间分布极不均匀,导致个别Task处理数据量巨大,成为整个Stage的瓶颈。

  2. 减少Shuffle :Shuffle是跨节点的数据混洗,涉及磁盘I/O、网络I/O和数据序列化,代价极高。优化的核心就是尽量避免或减少Shuffle

  3. 充分利用内存和CPU:让数据尽可能留在内存中,并让所有CPU核心保持忙碌,避免空闲。

  4. 使用合适的并行度(Parallelism):并行度决定了Task的数量,太少导致资源闲置,太多则带来调度开销。


二、代码层面优化(最有效、成本最低)

1. 选择高性能算子
  • reduceByKey/aggregateByKey替代groupByKeyreduceByKey会在Map端先进行本地聚合,极大减少Shuffle数据量。groupByKey则直接全量Shuffle,性能差。

  • mapPartitions替代map :如果需要对每个元素进行数据库连接等昂贵操作,mapPartitions可以每个分区建立一次连接,而不是每个元素一次。但要注意内存,它一次性处理整个分区的数据。

  • foreachPartitions替代foreach:同理,用于数据写入等操作。

  • filter后尽早coalesce :大量过滤后数据变少,可以用coalesce减少分区数,减少小文件和不必要的Task开销。

2. 广播大变量(Broadcast)
  • 当Driver需要将一个较大的只读数据(如 lookup table)分发到每个Executor时,使用broadcast。Spark会将其以高效的方式分发到每个节点一次,而不是随着每个Task序列化传输,极大减少网络开销。

    复制代码
    val broadcastVar = sparkSession.sparkContext.broadcast(largeLookupMap)
    rdd.map(x => broadcastVar.value.get(x))
3. 使用Kryo序列化
  • Java默认序列化慢且体积大。Kryo序列化更快,结果更小。

    复制代码
    spark.conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
    spark.conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]))
4. 持久化(Cache/Persist)的正确使用
  • 当一个RDD/DataFrame被多次使用时,必须将其持久化到内存或磁盘,避免重复计算。

  • 根据数据大小和访问模式选择合适的存储级别:

    • MEMORY_ONLY(默认):性能最好,完全在内存中。

    • MEMORY_AND_DISK:内存放不下时,溢写到磁盘。

    • MEMORY_ONLY_SER:将数据序列化后存储,更省内存,但增加CPU开销。

  • 注意:不要无脑缓存,缓存不需要的数据会浪费宝贵的内存资源。


三、数据倾斜专项处理

  1. 识别倾斜:通过Spark UI查看Stage的Task执行时间,找到执行时间特别长的Task。

  2. 解决方法

    • 加盐(Salting):将倾斜的Key加上随机前缀,打散到不同分区处理,最后再去掉前缀合并结果。这是最常用的方法。

    • 将倾斜Key分离:将倾斜的Key单独拿出来处理(如使用广播Join),非倾斜部分正常处理,最后合并。

    • 提高Shuffle并行度 :通过spark.sql.shuffle.partitions(默认200)增加分区数,让倾斜的Key分散到更多Task中(治标不治本,对于极端倾斜效果有限)。

    • 使用Map端Join:如果倾斜是由大表Join小表引起的,确保将小表广播出去。


四、Shuffle调优

  1. spark.sql.shuffle.partitions :控制Shuffle后的分区数量,默认200。应根据数据量和集群规模调整(如设置为 executor-cores * executor-num * 2~3)。

  2. spark.shuffle.file.buffer:Shuffle写缓冲区大小,默认32k。适当调大(如64k)可以减少磁盘I/O次数。

  3. spark.reducer.maxSizeInFlight:Shuffle读缓冲区大小,默认48m。适当调大(如96m)可以减少网络请求次数。

  4. spark.shuffle.io.maxRetriesspark.shuffle.io.retryWait:Shuffle过程网络超时重试相关,在网络不稳定时可调大。


五、资源与并行度调优

1. 资源配置参数(提交作业时指定)
  • --num-executors:设置合适的Executor数量。

  • --executor-memory:每个Executor的内存。要预留一部分给系统开销(约10%)。

  • --executor-cores:每个Executor的核心数。通常推荐每个核心分配4-8GB内存。

  • --driver-memory:Driver内存,如果需要collect大量数据回Driver,需要调大。

2. 动态资源分配(Dynamic Allocation)
  • 启用后,Spark可以根据工作负载动态地申请和释放Executor,提高集群资源利用率。

    复制代码
    spark.dynamicAllocation.enabled=true
    spark.dynamicAllocation.minExecutors
    spark.dynamicAllocation.maxExecutors
    spark.dynamicAllocation.initialExecutors
3. 并行度
  • 输入阶段:由数据源的分片数决定(如HDFS块数,Kafka分区数)。

  • 处理阶段 :由spark.default.parallelism(针对RDD)和spark.sql.shuffle.partitions(针对DataFrame/Dataset)控制。

  • 原则:确保每个Task处理的数据量在 128MB ~ 256MB 之间比较合适,且总Task数量是总核心数的 2~3倍 以上,以充分利用资源。


六、Spark SQL 与 DataFrame 特定优化

  1. 启用Catalyst优化器:自动进行,无需干预。它会进行谓词下推、列剪裁、常量折叠等优化。

  2. 使用钨丝计划(Tungsten):自动进行,它使用堆外内存和自定义序列化器,提升CPU和内存效率。

  3. 选择正确的Join策略:Spark SQL的Join策略有:

    • Broadcast Hash Joinspark.sql.autoBroadcastJoinThreshold(默认10MB)。确保小表能被广播。

    • Sort Merge Join:处理两个大表的Join。

    • Shuffle Hash Join:在Spark 3.x中已不推荐。

    • 可以通过/*+ BROADCAST(t1) */等Hint提示优化器。

  4. 合理设置分区和分桶:对经常过滤和Join的字段进行分区或分桶存储(如Parquet + Hive Metastore),可以极大加速查询。

  5. AQE(自适应查询执行)Spark 3.x 最重要的优化特性! 强烈建议开启。

    • spark.sql.adaptive.enabled=true

    • 动态合并Shuffle分区:解决分区过多导致小文件的问题。

    • 动态调整Join策略:运行时发现过滤后表变小,可将Sort Merge Join转为Broadcast Hash Join。

    • 动态优化数据倾斜Join:自动检测并处理倾斜数据。


七、存储与格式优化

  • 使用列式存储格式 :如 Parquet、ORC。它们支持列剪裁和谓词下推,能大幅减少I/O。

  • 选择合适的压缩编解码器:如Snappy(平衡速度/压缩比)、Zstandard(高压缩比)、LZO(快速)。

    复制代码
    df.write.option("compression", "snappy").parquet("path")

优化总结与步骤

  1. 第一步:写好代码。使用高性能算子、广播、Kryo、合理缓存。

  2. 第二步:定位瓶颈善用Spark UI,它是性能调优的"眼睛"。重点关注:

    • 各Stage和Task的执行时间。

    • Shuffle读写数据量。

    • GC时间。

    • 是否存在数据倾斜(Skew)或任务长尾(Straggler)。

  3. 第三步:针对性调优。根据UI分析结果,判断是数据倾斜、Shuffle量大、资源不足还是GC问题,然后应用上述对应方法。

  4. 第四步:开启AQE等现代化特性(如果使用Spark 3+)。

  5. 第五步:调整全局配置和资源。进行系统性的参数微调。

相关推荐
clarance20152 小时前
ChatBI王者之争:ThoughtSpot、Databricks、Power BI等五大产品深度对决与选型指南
大数据·人工智能·信息可视化·数据挖掘·数据分析
TDengine (老段)2 小时前
TDengine 数据订阅架构设计与最佳实践
大数据·数据库·时序数据库·tdengine·涛思数据
程序猿追2 小时前
昇腾NPU实战:Z-Image-Turbo-Fun-Controlnet-Union模型部署与测试全记录
大数据·服务器·人工智能·机器学习
InfiSight智睿视界3 小时前
智能巡店系统:连锁餐饮数字化运营的核心引擎
大数据·人工智能·ai
数据库学啊3 小时前
好用的车联网时序数据库机构有哪些
大数据·数据库·时序数据库
武子康3 小时前
大数据-181 Elasticsearch 段合并与磁盘目录拆解:Merge Policy、Force Merge、Shard 文件结构一文搞清
大数据·后端·elasticsearch
Elastic 中国社区官方博客3 小时前
如何通过个性化、分群感知排序来提升电商搜索相关性
大数据·数据库·elasticsearch·搜索引擎·全文检索
沃达德软件3 小时前
智慧警务实战模型与算法
大数据·人工智能·算法·数据挖掘·数据分析
Hello.Reader3 小时前
Flink SQL 中的 ORDER BY 子句批处理 vs 流处理的排序语义
大数据·sql·flink