目录
[1. 选择高性能算子](#1. 选择高性能算子)
[2. 广播大变量(Broadcast)](#2. 广播大变量(Broadcast))
[3. 使用Kryo序列化](#3. 使用Kryo序列化)
[4. 持久化(Cache/Persist)的正确使用](#4. 持久化(Cache/Persist)的正确使用)
[1. 资源配置参数(提交作业时指定)](#1. 资源配置参数(提交作业时指定))
[2. 动态资源分配(Dynamic Allocation)](#2. 动态资源分配(Dynamic Allocation))
[3. 并行度](#3. 并行度)
[六、Spark SQL 与 DataFrame 特定优化](#六、Spark SQL 与 DataFrame 特定优化)
一、核心优化思想
-
避免数据倾斜(Data Skew):这是Spark作业最大的"性能杀手"。指数据在Partition间分布极不均匀,导致个别Task处理数据量巨大,成为整个Stage的瓶颈。
-
减少Shuffle :Shuffle是跨节点的数据混洗,涉及磁盘I/O、网络I/O和数据序列化,代价极高。优化的核心就是尽量避免或减少Shuffle。
-
充分利用内存和CPU:让数据尽可能留在内存中,并让所有CPU核心保持忙碌,避免空闲。
-
使用合适的并行度(Parallelism):并行度决定了Task的数量,太少导致资源闲置,太多则带来调度开销。
二、代码层面优化(最有效、成本最低)
1. 选择高性能算子
-
用
reduceByKey/aggregateByKey替代groupByKey:reduceByKey会在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开销。
-
-
注意:不要无脑缓存,缓存不需要的数据会浪费宝贵的内存资源。
三、数据倾斜专项处理
-
识别倾斜:通过Spark UI查看Stage的Task执行时间,找到执行时间特别长的Task。
-
解决方法:
-
加盐(Salting):将倾斜的Key加上随机前缀,打散到不同分区处理,最后再去掉前缀合并结果。这是最常用的方法。
-
将倾斜Key分离:将倾斜的Key单独拿出来处理(如使用广播Join),非倾斜部分正常处理,最后合并。
-
提高Shuffle并行度 :通过
spark.sql.shuffle.partitions(默认200)增加分区数,让倾斜的Key分散到更多Task中(治标不治本,对于极端倾斜效果有限)。 -
使用Map端Join:如果倾斜是由大表Join小表引起的,确保将小表广播出去。
-
四、Shuffle调优
-
spark.sql.shuffle.partitions:控制Shuffle后的分区数量,默认200。应根据数据量和集群规模调整(如设置为executor-cores * executor-num * 2~3)。 -
spark.shuffle.file.buffer:Shuffle写缓冲区大小,默认32k。适当调大(如64k)可以减少磁盘I/O次数。 -
spark.reducer.maxSizeInFlight:Shuffle读缓冲区大小,默认48m。适当调大(如96m)可以减少网络请求次数。 -
spark.shuffle.io.maxRetries和spark.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 特定优化
-
启用Catalyst优化器:自动进行,无需干预。它会进行谓词下推、列剪裁、常量折叠等优化。
-
使用钨丝计划(Tungsten):自动进行,它使用堆外内存和自定义序列化器,提升CPU和内存效率。
-
选择正确的Join策略:Spark SQL的Join策略有:
-
Broadcast Hash Join :
spark.sql.autoBroadcastJoinThreshold(默认10MB)。确保小表能被广播。 -
Sort Merge Join:处理两个大表的Join。
-
Shuffle Hash Join:在Spark 3.x中已不推荐。
-
可以通过
/*+ BROADCAST(t1) */等Hint提示优化器。
-
-
合理设置分区和分桶:对经常过滤和Join的字段进行分区或分桶存储(如Parquet + Hive Metastore),可以极大加速查询。
-
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")
优化总结与步骤
-
第一步:写好代码。使用高性能算子、广播、Kryo、合理缓存。
-
第二步:定位瓶颈 。善用Spark UI,它是性能调优的"眼睛"。重点关注:
-
各Stage和Task的执行时间。
-
Shuffle读写数据量。
-
GC时间。
-
是否存在数据倾斜(Skew)或任务长尾(Straggler)。
-
-
第三步:针对性调优。根据UI分析结果,判断是数据倾斜、Shuffle量大、资源不足还是GC问题,然后应用上述对应方法。
-
第四步:开启AQE等现代化特性(如果使用Spark 3+)。
-
第五步:调整全局配置和资源。进行系统性的参数微调。