简介:面向大数据开发、数据分析与机器学习实践者的 Spark 3.2.0 预编译发行包,适配 Hadoop 3.2 环境,可直接用于本地或集群部署、课程实验与项目原型验证,省去版本匹配和源码编译的繁琐。压缩包共 1476 个文件,约 287.02MB,内含 238 个 jar、462 个 py、203 个 scala、135 个 java 等运行库与源码示例,并配有 sh/cmd 启动脚本、conf 配置模板、parquet/orc/csv 样例数据及说明文档,覆盖 Spark Core、Spark SQL、Streaming、MLlib 与 GraphX 常用组件,也便于研究 Catalyst 优化、PySpark API、Kubernetes 部署和内存管理。已有 1126 人学习下载。完整目录保留了发行版的依赖、示例与脚本结构,适合按模块查阅和对照验证,帮助读者快速搭建可运行的实验环境,理解各组件在大数据处理流程中的协作方式;借助内置示例与样例数据,可练习 SQL 查询、流式处理和模型训练等任务,也可作为版本升级、兼容性对比和教学演示的基线包。
1. 内网机器没外网,这个包决定你今天能不能按时下班
内网环境、无外网、只有一台能连到集群的跳板机,这种场景下要在一小时内把一份几十 GB 的日志跑出统计结果,spark-3.2.0-bin-hadoop3.2.tgz往往就是唯一能用的东西。它不是什么新框架,就是 Spark 3.2.0 的官方预编译二进制分发包:bin-hadoop3.2这个后缀说明包里的 jar 是针对 Hadoop 3.2 客户端依赖编译好的,解压就能跑,不需要 Maven,不需要 Scala 编译链,也不需要联网下载任何东西。适合三类人:想快速搭一套实验环境的数据开发、要交付离线部署包的运维、想在读源码前先摸清运行时行为的初学者。不适合想改 Spark 内核的人,那种情况应当去拿源码包自己 build。
2. 拆开这个 tgz:目录骨架里每一层对应哪一步部署
拿到一个几百兆的压缩包,很多人第一反应是直接tar -xzf然后ls,看到一堆目录就懵了。其实 Spark 的分发包结构是有明确分工的,哪一步该动哪个目录是固定的,认清楚以后,后面配环境、换 jar、打包分发都不会乱来。
2.1 解压、校验与目录盘点
先别急着一把梭解压,包在网络传输过程中损坏是常见事,解压到一半报unexpected EOF才是真的浪费时间。我一般会先算一次校验值,再解压到约定的软件目录,路径里不要有空格和中文,这是所有大数据组件的老规矩。
# 算校验值,和官方同目录下的 .sha512 文件对一下,不一致就别往下走了 sha512sum spark-3.2.0-bin-hadoop3.2.tgz # 解压到统一目录,-C 指定落点,避免在当前目录散开 mkdir -p /opt/module tar -xzf spark-3.2.0-bin-hadoop3.2.tgz -C /opt/module # 改成短名字,后面写环境变量能少打一截 cd /opt/module && mv spark-3.2.0-bin-hadoop3.2 spark # 看每个子目录占多少空间,对体积有直觉 du -sh /opt/module/spark/*/ | sort -hsha512sum只做完整性判断,不做安全判断,包本身的来源要自己确认。-C参数比cd + tar更稳,因为你不会忘记自己在哪个目录。改名这一步能省事,SPARK_HOME=/opt/module/spark比带版本号的长路径好记,代价是同一台机器上跑两个 Spark 版本时需要改成软链接切换。
解压完的目录大致是这个分工:
| 目录 | 作用 | 什么时候会动它 |
|---|---|---|
bin/ | spark-submit、spark-shell、pyspark、spark-sql等入口脚本 | 只在 PATH 里引用,不改内容 |
sbin/ | start-master.sh、start-worker.sh、start-all.sh、历史服务脚本 | Standalone 模式启停集群时用 |
conf/ | 全是.template模板,必须自己复制改名才生效 | 每次部署都要动 |
jars/ | 全部运行时依赖,两百多个 jar | 版本冲突时来这里临时替换 |
python/ | pyspark包和py4j的 zip | 配 PySpark 的 PYTHONPATH 时关心 |
examples/ | 示例 jar 与示例代码 | 冒烟测试直接拿来跑 |
data/ | mllib 用的小样本数据 | 学习阶段拿来练手 |
yarn/ | YARN 侧 shuffle service 需要的 jar | 上 YARN 且开启外部 shuffle 时用 |
kubernetes/ | 容器化部署的 Dockerfile 和 RBAC 模板 | 容器环境部署才看 |
R/ | SparkR 包 | 只有 R 用户关心 |
一个提醒:conf/下面所有文件都是xxx.template,直接改模板是不生效的,必须cp成去掉.template的名字。这个细节每年都要坑一批第一次部署的人。
2.2 bin-hadoop3.2 这个后缀绑死了哪些东西
Spark 官方在 3.2.0 这个版本线上提供了三种包:源码包、bin-hadoop2.7、bin-hadoop3.2,还有一个without-hadoop。之所以要分,是因为 Spark 自身不实现 HDFS 和 YARN 协议,它依赖 Hadoop 的客户端库,而 Hadoop 2.x 和 3.x 的客户端 API 在二进制上并不兼容。bin-hadoop3.2意味着包里已经打好了 Hadoop 3.2.x 的客户端 jar,它的直接后果是:你的目标集群如果是 HDFS 3.x + YARN 3.x,这个包是对口的;如果是更老的 Hadoop 2.6 及以下,这个包在提交时会在 RPC 层直接报协议不匹配,别浪费时间调参数。
cd /opt/module/spark # 看发行说明,确认构建信息 cat RELEASE # 确认内置的 Scala 版本,后面自己写 Scala 程序编译版本必须对齐 ls jars/ | grep -E '^scala-library|^spark-core|^hadoop-client|^py4j'第二条命令的输出里,scala-library-2.12.x.jar说明这个包是 Scala 2.12 版本。这条信息非常关键:你自己用 Maven 打的业务 jar,如果是按 Scala 2.11 编译的,扔进来运行时就会撞上NoSuchMethodError: scala.Predef$.refArrayOps这类看着完全不知所云的错。hadoop-client-api和hadoop-client-runtime是 Hadoop 3.x 起的 shaded 包结构,把依赖重定位过,这也是为什么 Spark 3.x 比 2.x 在依赖冲突上干净了不少。
再补一句关于without-hadoop的用法:如果集群的 Hadoop 版本正好不是 3.2,比如是 3.3 或 2.7,标准做法不是拿bin-hadoop3.2硬凑,而是拿without-hadoop版本,然后把集群自己的 Hadoop jar 通过spark.yarn.jars或 classpath 引进去。这样做的好处是客户端版本和集群版本严格一致,坏处是要多配一步。日常实验环境图省事,直接用bin-hadoop3.2就行。
3. 单机跑通:JAVA_HOME、SPARK_HOME 与第一个 SparkSession
环境变量这一步看着最没技术含量,实际上是新手翻车最集中的地方。Spark 的启动脚本依赖脚本自己去找 Java,找不到就退回到java命令,系统里如果同时装了多个 JDK,很可能启动脚本用的是 A,业务代码编译用的是 B,然后你就得到一个Unsupported class file major version的报错,看着像版本问题又找不到是哪里的版本。
3.1 环境变量与 spark-env.sh 的分工
/etc/profile.d/下放一个脚本是最省事的做法,对所有用户生效,且不用每次登录手动 source。
# /etc/profile.d/spark.sh export JAVA_HOME=/usr/local/jdk1.8.0_351 export SPARK_HOME=/opt/module/spark export PATH=$PATH:$SPARK_HOME/bin:$SPARK_HOME/sbin export PYSPARK_PYTHON=/usr/bin/python3 export PYSPARK_DRIVER_PYTHON=/usr/bin/python3逐条说:JAVA_HOME必须指向 JDK 根目录而不是bin子目录,也不要用 JRE,javac缺失会让某些编译路径直接挂。SPARK_HOME是所有启动脚本的锚点,脚本靠它定位jars/和conf/。PATH里把bin放在前面,是为了让spark-submit这类命令能直接敲。两个PYSPARK_*变量是最容易被忘的,如果不设,executor 上的 Python 会用系统默认的,driver 和 executor 用的 Python 版本不一致时,报错会非常难查。
然后处理conf/。spark-env.sh管的是 JVM 层面和守护进程层面的东西,spark-defaults.conf管的是应用配置默认值,两者的优先级和生效范围完全不同,混着写会出玄学问题。
cd $SPARK_HOME/conf # 模板必须复制改名才生效 cp spark-env.sh.template spark-env.sh cp spark-defaults.conf.template spark-defaults.conf cp log4j.properties.template log4j.propertiesspark-env.sh里我一般写这几行:
# 显式指定 JDK,避免启动脚本自己去猜 export JAVA_HOME=/usr/local/jdk1.8.0_351 # Standalone 模式下 Master 的对外地址,单机实验写本机内网 IP 或主机名,别写 localhost export SPARK_MASTER_HOST=192.168.10.11 export SPARK_MASTER_PORT=7077 export SPARK_MASTER_WEBUI_PORT=8080 # Worker 资源声明,SUBMIT 时的资源上限不能超过这里 export SPARK_WORKER_CORES=4 export SPARK_WORKER_MEMORY=8g # 守护进程自身的堆,历史服务、Master 这些进程用 export SPARK_DAEMON_MEMORY=1g # 日志和 pid 落到非临时目录,重启后还能翻 export SPARK_LOG_DIR=/opt/module/spark/logs export SPARK_PID_DIR=/opt/module/spark/runSPARK_WORKER_MEMORY是给这个 Worker 能提供的总内存,不是每个 executor 的内存,很多人把它当成 executor 内存填,结果提交时一算资源超了直接拒绝。SPARK_DAEMON_MEMORY默认只有 1g,历史服务加载大量 event log 时容易 OOM,这个值在生产上要往上调。
3.2 local 模式的两条验证路径
配置完先别急着起集群,用 local 模式跑通是最快的验证方式,因为它完全不涉及网络、不涉及守护进程,纯粹验证「这个包 + 这个 JDK」这一对组合是好的。
# local[2] 表示 driver 用一个线程跑,本地并发度 2 spark-shell --master local[2]进到交互界面后,粘这几行:
// 读自带 README 做词频,文件协议写 file:// 是为了避免解析到 HDFS val text = sc.textFile("file:///opt/module/spark/README.md") // 切词、去空、计数、按次数倒排,取前十 val top = text .flatMap(_.split("\\s+")) .filter(_.nonEmpty) .map(w => (w, 1)) .reduceByKey(_ + _) .sortBy(_._2, ascending = false) .take(10) top.foreach(println)flatMap后必须filter去空串,否则空格会被当成一个词统计;sortBy的第二个参数是布尔值,Scala 里写成ascending = false比写false可读性好;take是 action,前面全是 transformation,这样写能保证整个过程只触发一次计算。跑通以后sc.master会打印local[2],这是确认 master 生效最直接的方式。
PySpark 这条路单独验证一遍,因为它多了一层 Python 进程通信:
pyspark --master local[4]from pyspark.sql import SparkSession spark = (SparkSession.builder .appName("smoke-test") .master("local[4]") .config("spark.sql.shuffle.partitions", "8") .config("spark.driver.memory", "2g") .config("spark.ui.port", "4040") .getOrCreate()) df = spark.read.text("file:///opt/module/spark/README.md") print("行数:", df.count()) # 看一眼真正生效的配置,比对着配置文件猜靠谱 print(spark.sparkContext.getConf().getAll()) spark.stop()几个参数值得讲透。local[4]里的 4 是本地并发线程数,不是 4 个 executor,用local[*]会吃掉机器所有核,在共享开发机上这是个坏习惯。spark.sql.shuffle.partitions默认 200,对这份只有几十行的小文件来说是巨大浪费,会起 200 个 task 各处理几行,跑得比 8 个还慢,这就是典型的「参数没调对导致 Spark 看起来慢」的案例。spark.driver.memory在 SparkSession 里设置,只在local或client部署模式下对当前 JVM 有效,在cluster模式下写在代码里是无效的,必须走--driver-memory。最后那句getConf().getAll()是我自己的习惯,配置到底有没有生效,别信大脑,信输出。
4. 从 local 到 Standalone 与 on YARN:master/worker 配置与提交参数
local 模式验证完,下一步是让多个进程协同。Spark 自带的 Standalone 集群模式是最轻的分布式形态,不依赖外部资源调度器,两台机器就能搭起来;而生产上绝大多数是把 Spark 交给 YARN 调度。这两条路径的配置文件和提交参数差异不小,混淆了会出现「参数写了几十个,一个都没生效」。
4.1 Standalone 集群的三个配置文件
Standalone 需要动conf/下的三个文件:spark-env.sh(上面已配)、workers(工作节点列表)、spark-defaults.conf(默认应用配置)。
cd $SPARK_HOME/conf # 模板要改名,注意 3.x 里叫 workers,不是 2.x 时代的 slaves cp workers.template workers # 每行一个工作节点主机名,主节点自己也要干活就写上自己 cat > workers <<'EOF' 192.168.10.11 192.168.10.12 EOF主机名解析必须提前打通,所有节点的/etc/hosts都要能互相解析到对方的真实内网地址,且不能解析成127.0.0.1。这一条是后面避坑章要展开的重点。
# 三个启动脚本的分工:先起 master,再起 worker,或者一条 all 搞定 $SPARK_HOME/sbin/start-master.sh $SPARK_HOME/sbin/start-workers.sh # 验证进程,Master 和 Worker 是两个独立 JVM jps -l | grep -E 'Master|Worker' # 看端口监听,7077 是 RPC,8080 是 Master WebUI ss -lntp | grep -E '7077|8080'start-all.sh能一把起完,但它要求当前机器能免密登录到所有 worker 节点,且workers文件里的主机名可达。我一般会拆开执行,因为start-all.sh失败时日志分散在多个节点上,排查成本反而更高。jps只显示当前机器进程,worker 节点上要看自己的。
spark-defaults.conf是应用级默认值,写得规范能省掉每次提交都敲一长串参数:
spark.master spark://192.168.10.11:7077 spark.eventLog.enabled true spark.eventLog.dir file:///opt/module/spark/eventLog spark.serializer org.apache.spark.serializer.KryoSerializer spark.driver.memory 2g spark.sql.shuffle.partitions 200spark.eventLog是历史服务的数据源,不开这个,应用跑完 UI 立刻消失,事后想查执行计划只能重跑。实验环境用本地路径可以,生产要写 HDFS 路径,且所有节点都能访问。KryoSerializer的收益在 shuffle 数据量大时很明显,但要注意自定义类需要注册,不注册会退化到字段名序列化,收益打折。
提交一个自带的示例验证集群:
spark-submit \ --master spark://192.168.10.11:7077 \ --deploy-mode client \ --class org.apache.spark.examples.SparkPi \ --executor-memory 2g \ --total-executor-cores 4 \ $SPARK_HOME/examples/jars/spark-examples_2.12-3.2.0.jar 100逐项说明:--deploy-mode client表示 driver 跑在提交命令所在的机器,日志直接打到终端,调试阶段首选;cluster模式会把 driver 交给集群,日志要去 worker 节点目录或者历史服务里翻。--executor-memory 2g是单个 executor 的堆大小。--total-executor-cores是 Standalone 模式专用的总核数参数,注意这里没有--num-executors,因为 Standalone 的 Worker 按需分配,executor 数量是算出来的,不是你指定的。最后那个100是传给SparkPi的分片数,不是迭代次数,这个参数在不同示例里含义不一样,要对着源码看。
4.2 提交参数怎么算:从核数内存倒推
参数拍脑袋填是资源浪费的根源。我的算法是反过来:先看 Worker 有多少资源,再看业务需要多少并发,最后倒推参数。
假设 3 个 Worker,每个 8 核 16G(SPARK_WORKER_CORES=8、SPARK_WORKER_MEMORY=16g),总资源是 24 核 48G。要跑一个数据量中等、单分区处理耗时约 2 分钟的作业,我一般按「每个 executor 4 核 8G」去切,那么:
- 单个 Worker 上能起 2 个 executor(8 核 / 4 核),内存 2×8G=16G 刚好占满,但要留出
memoryOverhead,所以实际给 7g 左右更安全; - 全集群 executor 数约 6 个,总并发 task 数 6×4=24;
spark.sql.shuffle.partitions设成略大于 24 的值,比如 48 或 64,让每个 task 有活干但不至于排队。
# 按上面算出来的参数提交一份业务 jar spark-submit \ --master spark://192.168.10.11:7077 \ --deploy-mode client \ --class com.example.JobMain \ --executor-memory 7g \ --total-executor-cores 24 \ --conf spark.executor.memoryOverhead=1024 \ --conf spark.sql.shuffle.partitions=64 \ --conf spark.serializer=org.apache.spark.serializer.KryoSerializer \ /opt/jobs/biz-job-1.0.jar --input /data/input --output /data/output--conf里的值优先级高于spark-defaults.conf,这是排查「配置为什么没生效」时第一个要确认的点。--executor-memory后面的 g 是小写,写成7G在部分版本上会解析失败,虽然现在多数情况能容忍,但统一小写更保险。
4.3 on YARN 的差异点
切到 YARN,配置文件换成 Hadoop 的三件套,通过HADOOP_CONF_DIR引进来:
export HADOOP_CONF_DIR=/opt/module/hadoop/etc/hadoop export YARN_CONF_DIR=$HADOOP_CONF_DIR这两个变量都指同一个目录,HADOOP_CONF_DIR给 Spark 读 HDFS 配置,YARN_CONF_DIR给 Spark 读 YARN 配置。只设一个有时也能跑,但在cluster模式下会遇到 driver 找不到资源管理器地址的问题。
spark-submit \ --master yarn \ --deploy-mode cluster \ --queue root.default \ --num-executors 10 \ --executor-cores 2 \ --executor-memory 4g \ --conf spark.executor.memoryOverhead=1024 \ --conf spark.dynamicAllocation.enabled=false \ --class com.example.JobMain \ /opt/jobs/biz-job-1.0.jar和 Standalone 的差别集中在三点。第一,--num-executors在这里生效,Standalone 下写它是无效的,这个错配非常常见。第二,YARN 模式必须配memoryOverhead,因为 executor 的 JVM 除了堆还有堆外内存(Netty 直连缓冲、JVM 元空间、Python worker),YARN 只看容器总内存,超了直接杀进程,不给 GC 的机会。第三,动态资源分配在 YARN 上可以开,但开了之后--num-executors就变成初始值而不是上限,最小/最大值要通过spark.dynamicAllocation.minExecutors和maxExecutors控制,两者混用容易得出「参数没生效」的错觉。
5. 避坑排查:五类上线前必须过一遍的报错
这个包本身是官方构建的,出问题的地方几乎都在环境和参数上。下面五类是这几年被问得最多、也是新手最容易卡住的,按「现象 → 原因 → 解决」整理,遇到时对着排查能省掉大量试错。
5.1 JDK 版本:最先撞上的那堵墙
现象:执行spark-shell或spark-submit直接抛java.lang.UnsupportedClassVersionError: ... class file version 61.0,或者报java.lang.IllegalAccessError: class org.apache.spark.storage.StorageUtils$ ... cannot access class sun.nio.ch.DirectBuffer。
原因:Spark 3.2 官方支持的是 Java 8 和 Java 11 两个大版本,用 JDK 17 及以上去跑会撞到模块系统封装,前面那个IllegalAccessError就是 JDK 16 起强封装导致的,需要加一堆--add-opens才有机会跑起来。另一个方向是驱动了发现不了 Java,JAVA_HOME指向 JRE 也会出现类似问题。
解决:先统一版本,java -version和$JAVA_HOME/bin/java -version两条都要看,因为 PATH 里的 java 和 JAVA_HOME 指向的完全可以不是同一个。确认统一到 8 或 11 之后,在spark-env.sh里显式export JAVA_HOME,别依赖启动脚本去猜。同一台机器上如果必须跑两个版本,用软链接加环境变量切换,别想着一个 JAVA_HOME 伺候所有组件。
5.2 依赖冲突:guava 与 Scala 的两个老对手
现象:作业提交成功后,执行到某个算子是抛java.lang.NoSuchMethodError: com.google.common.base.Preconditions.checkArgument(ZLjava/lang/String;Ljava/lang/Object;)V,或者NoSuchMethodError: scala.Predef$.refArrayOps。
原因:第一种是 guava 冲突。业务 jar 自己打了一份 guava,版本比 Spark 内置的高或低,两个版本的类同名不同实现,classpath 顺序决定谁先被加载,这就是典型的血泪经验——本地 IDE 跑得好好的,上集群就崩。第二种是 Scala 版本不匹配,Spark 3.2 的二进制包是 Scala 2.12,业务 jar 如果用 2.11 编译,二进制接口对不上。
解决:guava 这类冲突的正解是重写打包,把业务依赖里的 guava 用 shade 插件重定位到自己的包名下,从根源上消灭同名类;临时方案可以试--conf spark.executor.userClassPathFirst=true,但这只是调整加载顺序,治标不治本,而且可能把别的依赖顺序也打乱。Scala 版本问题没有捷径,改pom.xml里的scala.binary.version,用providedscope 引用 Spark 相关依赖,别自己打进去。
5.3 主机名与端口:Failed to bind 和 loopback 警告
现象:启动日志里出现WARN Utils: Your hostname, node01 resolves to a loopback address: 127.0.0.1,随后 Worker 注册不上,或者提交作业报Failed to bind to /x.x.x.x:0、Connection refused。
原因:/etc/hosts里主机名被映射到了 127.0.0.1,Spark 拿这个地址去注册,其他节点根本连不到;或者防火墙拦了 7077、8080 端口;或者 4040 端口被上一次没退干净的 UI 占用。
解决:先把 hosts 改对,每台节点上ping $(hostname)必须返回真实内网 IP 而不是127.0.0.1,这一步是硬性门槛。如果机器上有多张网卡,显式指定export SPARK_LOCAL_IP=192.168.10.11,别让 Spark 自己挑。端口占用用ss -lntp | grep 4040查,多应用并行时给每个应用单独指定spark.ui.port。我一般还会在部署完成后跑一遍nc -zv逐个探测关键端口,这动作花不了半分钟。
5.4 内存与容器:被 YARN 杀掉的那 0.5G 去哪了
现象:作业跑到一半 executor 全部消失,YARN 日志里写着Container killed by YARN for exceeding memory limits. 4.5 GB of 4 GB physical memory used. Consider boosting spark.executor.memoryOverhead。
原因:YARN 限制的是容器总物理内存,等于堆内存加 overhead。很多人只算--executor-memory 4g,忽略了 JVM 元空间、线程栈、Netty 直连缓冲,PySpark 还要额外算 Python worker 进程的内存。堆没满,容器先超了,YARN 不通知直接杀。
解决:按max(384MB, 0.1 × executor内存)给 overhead 起步,PySpark 作业和 shuffle 密集的作业要往上加,--executor-memory 4g配spark.executor.memoryOverhead=1024是经验值。另外注意这个配置名在 Spark 3.x 已经统一成spark.executor.memoryOverhead,老配置spark.yarn.executor.memoryOverhead被废弃,混着写会出现「设了但没生效」的情况,因为废弃的键会被忽略。
5.5 PySpark 侧的两个隐性不一致
现象:报Python worker failed to connect back to the driver,或者 executor 上ModuleNotFoundError: No module named 'pandas',而 driver 机器上明明装好了。
原因:第一种通常是 driver 和 executor 的 Python 版本不一致,或者 executor 上PYSPARK_PYTHON没设;第二种是第三方包只装在 driver 本地,executor 节点根本没有这个包,因为 PySpark 不会自动分发 site-packages。
解决:第一,用绝对路径同时指定两个变量,spark.pyspark.python和spark.pyspark.driver.python都指向 executor 节点上实际存在的解释器路径,别依赖python3这个别名。第二,依赖包用两种方式分发:简单场景用--py-files分发 zip 包,且 worker 端要sys.path.append;生产场景直接用 venv 打包成 tar 上传,通过--archives分发并配环境变量指向解压目录,这样能保证每个 executor 拿到的包版本完全一致。
5.6 提交参数里的三个「写了但没生效」
顺带提醒三个高频陷阱,它们不报错,只是静默失效,比报错更难查。其一,--num-executors只在 YARN 和 K8s 模式下生效,Standalone 下写它没意义,必须用--total-executor-cores。其二,spark.driver.memory写在SparkSession.builder.config()里,在cluster部署模式下会被忽略,driver 内存要在提交命令里用--driver-memory指定。其三,spark.sql.shuffle.partitions是 SQL 和 DataFrame 的专用参数,纯 RDD 作业用的是spark.default.parallelism,两者互不覆盖,改了前者发现 RDD 作业还是几千个 task,就是这个原因。
排查动作我一般按这个顺序走,从下往上,能快速缩小范围:
| 顺序 | 动作 | 看什么 |
|---|---|---|
| 1 | spark-submit --version | 版本、Scala 版本、Java 版本是否对口 |
| 2 | jps -l | Master/Worker/Driver 进程是否都在 |
| 3 | ss -lntp | 端口监听、是否被别的进程占用 |
| 4 | spark.ui的 Executors 页 | executor 数量、每个的堆使用与 GC 时间 |
| 5 | yarn logs -applicationId <id> | 容器被杀的真实原因在 YARN 侧 |
6. 调优清单与验证手法:让这份包在生产边界内跑稳
Spark 3.2.0 有一个容易被忽略的变化:自适应查询执行在 3.2 版本起默认开启,spark.sql.adaptive.enabled的默认值已经是 true,这意味着小分区自动合并这个能力你是白拿的,但倾斜处理仍然要手动开。
| 参数 | 默认值 | 建议 | 说明 |
|---|---|---|---|
spark.sql.adaptive.enabled | true | 保持 true | 3.2 起默认开启,别手动关掉 |
spark.sql.adaptive.coalescePartitions.enabled | true | 保持 true | 自动合并过小的 shuffle 分区 |
spark.sql.adaptive.skewJoin.enabled | false | 有倾斜才开 | 开启后倾斜分区会拆分重跑 |
spark.serializer | JavaSerializer | 改 Kryo | shuffle 数据量大时明显省内存 |
spark.sql.autoBroadcastJoinThreshold | 10MB | 视小表大小调 | 调太高会导致 driver OOM |
spark.sql.shuffle.partitions | 200 | 按并发度算 | 配合 AQE 后不必精调 |
spark.sql.autoBroadcastJoinThreshold这条特别想说:调大到几百 MB 确实能救大表关联小表的场景,但广播的数据要先收集到 driver,调过头就是 driver 端OutOfMemoryError,这个后悔药是没有的,只能把参数改回去重跑。
冒烟验证我固定走四步,跑完才算部署可用:
# 1. 版本自检,输出里三行都要对口 $SPARK_HOME/bin/spark-submit --version # 2. 用自带示例排除业务代码因素,跑得通说明环境是好的 $SPARK_HOME/bin/spark-submit \ --master local[2] \ --class org.apache.spark.examples.SparkPi \ $SPARK_HOME/examples/jars/spark-examples_2.12-3.2.0.jar 10 # 3. 提交一个真作业到目标 master,确认资源能申请到 $SPARK_HOME/bin/spark-submit --master yarn --deploy-mode cluster \ --class org.apache.spark.examples.SparkPi \ $SPARK_HOME/examples/jars/spark-examples_2.12-3.2.0.jar 100 # 4. 起历史服务,确认 event log 真的落盘了 $SPARK_HOME/sbin/start-history-server.sh curl -sI http://192.168.10.11:18080 | head -1第三步和第二步的区别在于,它真正走了资源调度,能验证队列、内存、overhead 这一整条链路。第四步用curl探一下返回码,比用浏览器打开更早发现问题,脚本化也方便。
一个小技巧:把这四步写成一个smoke.sh放进部署包,每次扩容、换 JDK、换节点都跑一遍,比凭记忆检查靠谱得多。从那以后每次交付离线包,我都强制先在自己机器上解压一遍、跑完这四步再发出去,因为踩过一次压缩包在拷贝过程中损坏、到现场解压失败的坑,那一次返工的成本远高于这几分钟。希望帮到你。
本文还有配套的精品资源,点击获取