Spark 批处理调优这点事:资源怎么要、Shuffle 怎么省、序列化怎么选?我用这些年踩过的坑告诉你

举报
Echo_Wish 发表于 2025/12/05 21:16:05 2025/12/05
【摘要】 Spark 批处理调优这点事:资源怎么要、Shuffle 怎么省、序列化怎么选?我用这些年踩过的坑告诉你

Spark 批处理调优这点事:资源怎么要、Shuffle 怎么省、序列化怎么选?我用这些年踩过的坑告诉你

大家好,这里是大数据圈里“被 Yarn 吊着打过、和 Spark 吵过架”的 Echo_Wish。
今天咱们来聊聊 Spark 批处理作业的调优。别听到“调优”两个字就觉得高大上,它其实本质就是三句话:

资源别瞎要、Shuffle 别乱搞、序列化别拖后腿。

你能把这三件事整明白,70% 的性能问题都会给你让路。剩下 30% 基本靠缘分和运维同事心情。

这篇文章,我想和你聊点真东西:你能立刻用、立刻见效、能救你半夜线上跑不动作业的那种优化技巧。


一、资源调优:要得对比要得多更重要

Spark 作业的资源问题,大部分不是资源少,而是资源分配的方式完全不对。

1. Executor 数量和 CPU 核心到底怎么配?

这里给你一个我自己线上测试无数次得出的“黄金思路”:

  • Executor 内核数别超过 5~6 个
    因为再多 GC 压力大、上下文切换更大、反而越跑越慢。

  • Executor 数量比核心数量更重要
    小 Executor + 多 Executor 往往比 大 Executor + 少 Executor 更稳。

例如,你的集群有 50 核想全用上,不要这样配:

# 典型的错误配置:又大又笨
--executor-cores 10
--num-executors 5

应该这样:

# 典型的性能更优配置:小而精,数量多
--executor-cores 5
--num-executors 10

为什么?
因为大 Executor 会导致:

  • GC 时间飙升
  • Shuffle 文件争抢
  • Task 并发不均衡

而小 Executor 则让资源调度更灵活、稳定性更高。


2. Executor 内存不是越大越好

很多朋友喜欢直接开 20G、30G 内存的 Executor,我只能说:
你这是把自己的作业推进了 GC 炼狱

合理内存一般在 6~12G 之间,特别是 ETL 型作业。

万一你非要调大?那至少要打开:

--conf spark.memory.fraction=0.6
--conf spark.memory.storageFraction=0.3

否则别怪 Spark 动不动 OOM。


二、Shuffle 调优:能绕开就绕开,绕不开就优化

Shuffle 是 Spark 性能最大的杀手,没有之一。
凡是经过 Shuffle 的地方,都会:

  • 打断 pipeline
  • 写磁盘、读磁盘
  • 网络传输
  • 排序 & 聚合

你要做的不是“优化 Shuffle”,而是“减少 Shuffle”!

1. 尽量避免两个最致命的操作:groupByKeyjoin

groupByKey 是性能灾难,因为它会把 key 相同的所有 value 拉到同一个 Executor。

举个例子:

// 千万别这么写!这是 shuffle 地狱!
rdd.groupByKey()

正确写法应该使用 reduceByKey / aggregateByKey

// reduceByKey 会在 Map 端先进行预聚合,极大减少 shuffle 数据量
rdd.reduceByKey(_ + _)

2. join 一定要提前 broadcast 小表,减少 shuffle

传统 join 如下,一定会 shuffle:

df1.join(df2, "id")

但如果 df2 是小表(小于 300MB),你应该这样写:

import org.apache.spark.sql.functions.broadcast

df1.join(broadcast(df2), "id")

本质就是:凡是小表 join,大胆 broadcast。

3. Shuffle 分区(spark.sql.shuffle.partitions)是性能关键

Spark 默认是 200 分区,这个值对大多数作业来说过大或过小。

我的经验:

数据量 推荐分区数
< 10GB 50~100
10~200GB 200~500
> 200GB 500~2000

设置方式:

--conf spark.sql.shuffle.partitions=300

千万不要盲目调大分区,否则:

  • 分区太多 → 调度时间爆炸
  • 分区太少 → 单分区数据倾斜

三、序列化:Spark 性能的隐形加速器

Spark 默认使用 Java 序列化,效率低、体积大,非常影响 shuffle 和 cache 效率。

一句话:强烈建议使用 Kryo。

开启 Kryo:

--conf spark.serializer=org.apache.spark.serializer.KryoSerializer
--conf spark.kryo.registrationRequired=false

如果你有自定义 class,可以手动注册,加速更多:

val conf = new SparkConf()
conf.registerKryoClasses(Array(classOf[MyClass]))

为什么 Kryo 这么重要?

  • 序列化速度比 Java 快 5~10 倍
  • 体积小 30%~60%
  • 直接提升 shuffle 和 cache 性能

我自己线上 ETL 作业切到 Kryo 后,整体性能提升了 25% 左右,非常划算。


四、一个真实案例:从 1 小时优化到 14 分钟

某业务的明细拉链作业每日增量约 80GB,最初 60 分钟跑不完。
我调优后压到了 14 分钟。优化手段如下:

1. 资源重配

从:

--executor-cores=8
--executor-memory=20G
--num-executors=10

改成:

--executor-cores=5
--executor-memory=10G
--num-executors=18

GC 秒降。

2. Broadcast 小表

原来:

df1.join(df2, Seq("uid", "date"))

改为:

df1.join(broadcast(df2), Seq("uid", "date"))

Shuffle 直接少一半。

3. Spark Shuffle 分区调小

从默认 200 → 调整为 300
结合 80GB 数据量效果刚刚好。

4. 序列化改为 Kryo

直接减少 shuffle 文件大小,带来 10~15% 性能收益。


五、最后说一句:调优没有银弹,但有套路

Spark 调优看起来复杂,其实就三个方向:

  1. 让资源配置更合理
  2. 减少 Shuffle,或者让 Shuffle 更轻量
  3. 让数据体积更小,序列化更高效

你只要掌握这三点,任何批处理作业你都能找到优化点。

而调优最核心的本质就是一句话:

你要理解 Spark 的执行方式,而不是调一堆参数希望它变快。

代码写得再优雅、框架再先进,只要你无脑 groupByKey、无脑大 Executor、无脑默认序列化,性能肯定救不回来。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。