欢迎光临
我们一直在努力

CDH 6.3.2上Spark 3.3.1集群安装配置与高频故障排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}

简介:面向CDH 6.3.2平台的大数据开发与运维人员,spark-3.3.1-bin-3.0.0-cdh6.3.2.tgz提供了一个与CDH Hadoop深度适配的Spark 3.3.1二进制发行包,解决版本兼容和编译适配问题,部署后可直接配合配套文档使用Spark SQL、Spark Shell、PySpark等常用组件。包体共1313个文件,约254.24MB,结构分明:jar包是运行所需依赖库,py/scala/java包含源码与扩展逻辑,sh/cmd为启停脚本,rst/txt/md涵盖使用说明,avro、parquet、orc等提供可直接导入的样例数据,便于测试表结构和Spark SQL查询,其中ipynb/jpg等辅助文件可作开发参考或架构示意。目前已有1177人学习浏览,适合希望在CDH环境快速落地Spark能力的工程师,也适合从传统Hive迁移至Spark SQL的团队参考。除了可用于生产的运行时环境,用户还能获得与CDH 6.3.2匹配的Spark SQL脚本和示例数据,直接对照文档即可完成从HDFS读取、建表到查询的完整链路,省去梳理兼容性细节的大量时间,降低生产部署风险。

1. 集群里躺着的 Spark 3.3.1:这份 CDH 6.3.2 包值不值得换

在 CDH 6.3.2 环境里做 spark 集群搭建,很多人第一反应是去官网拉一个 spark-3.3.1-bin-hadoop3 的通用包,结果提交任务时要么报 HDFS 客户端版本冲突,要么在 YARN 上起不来容器。干脆的做法是直接用配套编译好的 spark-3.3.1-bin-3.0.0-cdh6.3.2.tgz:它把 Spark 3.3.1、Hadoop 3.0.0 客户端 ABI 和 CDH 6.3.2 的依赖打在一个包里,解决的是“编译期和运行期版本必须对上”这一类玄学问题。适合已有 CDH 集群、想把 Spark 从 2.x 往 3.x 升,或者新环境想少踩坑的工程师,也适合做离线安装包的离线交付场景。

2. 包名拆解:为什么是 bin-3.0.0-cdh6.3.2 而不是官方二进制包

拿到文件名,先别急着解压,把三个数字段读明白,后面能省半天排错时间。

2.1 三个数字段分别代表什么

spark-3.3.1 是 Apache Spark 的版本号,这个版本使用 Scala 2.12 编译,对应 spark-core_2.12、spark-sql_2.12 等一系列产物。bin-3.0.0 指的是 Hadoop Client 的二进制版本,CDH 6.3.2 内部的 Hadoop 版本实际是 3.0.0-cdh6.3.2,文件名只保留了主版本段。最后一段 cdh6.3.2 表示这是针对 CDH 6.3.2 发行版做的配套编译包。

文件名段 值 含义
spark 3.3.1 Spark 程序版本,Scala 2.12 编译,运行时依赖 spark-*_2.12 系列 jar
bin 3.0.0 Hadoop 客户端二进制 API 版本,实际坐标是 3.0.0-cdh6.3.2
cdh 6.3.2 目标发行版,对应 HDFS/YARN/Hive 的 CDH 6.3.2 基线

这一步为什么关键?Spark 运行时不直接和 NameNode 打交道,而是通过 Hadoop Client 库访问 HDFS 和 YARN。CDH 的 Hadoop 在 3.0.0 基础上打了一批补丁,RPC 协议带版本标记。如果 Spark 包是用 Apache Hadoop 3.3.x 主线编译的,提交到 CDH 集群后经常在 HDFS 操作阶段报 Token 失效或 RPC 版本不匹配。反过来,把 CDH 编译的包提交到开源 Hadoop 集群,也可能在 container 启动阶段翻车。

2.2 与官方 Apache 二进制包的三处差异

官方发布的 spark-3.3.1-bin-hadoop3 走 Apache Hadoop 3.3.x 主线,官方发布版不做 CDH 适配。我对比过的差异主要有三点。

第一,依赖坐标。CDH 的 hadoop-client、hadoop-hdfs、hadoop-yarn 都带 cdh 后缀,Maven 中央仓库默认拉不到这些构件,构建时要在 pom 里额外配置 CDH 的 repository。这份 tgz 把 CDH 依赖已经编译进 jars,省掉了整个构建适配阶段。

第二,native 库。HDFS 短读和 CRC 校验在 libhadoop.so 里实现。CDH 环境有自己的原生库路径。我一般不会用包内自带的 native 库去覆盖系统库,而是让 Spark 通过 HADOOP_HOME 自动加载 CDH 自带的那份。如果启动日志出现 Unable to load native-hadoop library,只要任务能正常读写数据,这个警告可以暂时忽略。

第三,Hive 版本绑定。Spark 3.3.1 默认把内置 Hive 当 2.3.x 处理,而 CDH 6.3.2 的 Hive 基线是 2.1.1。官方包直接跑在 CDH 上,读取 Hive 表时常出现 metastore 序列化错误或 MethodNotSupported。配套编译包在构建时调整了 hive-exec、hive-metastore 的版本绑定,这是它存在的核心价值之一。

2.3 解压后先确认包结构

解压后用 tree 或 ls 扫一遍目录,主要看四个地方:

tar -tzf spark-3.3.1-bin-3.0.0-cdh6.3.2.tgz | head -20
# 或者直接解压后查看
tar -xzf spark-3.3.1-bin-3.0.0-cdh6.3.2.tgz -C /opt
ls /opt/spark-3.3.1-bin-3.0.0-cdh6.3.2/

这段命令做了两件事:第一行用 tar -tzf 只列出内容不实际解压,确认包体完整;第二行把它解压到 /opt 目录。正常包内应该包含 bin、conf、jars、python、R、data、examples 这几个顶层目录。jars 目录里几百个 jar 是 Spark 运行时的依赖全家桶,后面 YARN 集群模式加载就要靠它们。examples 目录里是官方的样例 jar,我通常拿它做第一次提交测试,比自写代码更快。

3. 安装与使用:Spark 集群搭建完成后的第一次提交

安装本身不难,难在第一次提交跑通。这部分按“解压—配置—提交”三步走,每步都有容易忽略的细节。

3.1 解压与目录规划

部署用户我建议用 hdfs 或者单独建一个 spark 用户,不要用 root。用 root 跑 Spark,写 HDFS 文件时 owner 全是 root,后续权限控制会很难受。

tar -xzf spark-3.3.1-bin-3.0.0-cdh6.3.2.tgz -C /opt
ln -s /opt/spark-3.3.1-bin-3.0.0-cdh6.3.2 /opt/spark
chown -R spark:spark /opt/spark-3.3.1-bin-3.0.0-cdh6.3.2

这里把解压目录软链接成 /opt/spark,做版本升级时直接切换软链接,不用改一堆脚本里的绝对路径。chown 确保 spark 用户对目录有完整读写权限。随后在 spark 用户的 ~/.bashrc 里加环境变量:

export SPARK_HOME=/opt/spark
export PATH=$SPARK_HOME/bin:$PATH
export HADOOP_CONF_DIR=/etc/hadoop/conf

HADOOP_CONF_DIR 必须指向 CDH 集群的客户端配置文件目录,里面要有 core-site.xml、hdfs-site.xml、yarn-site.xml。这行配置经常被漏掉,漏掉的结果是 Spark 找不到默认文件系统,提交时报 NameNode 地址为空。

3.2 修改 spark-env.sh 和 spark-defaults.conf

安装包自带的 conf/spark-env.sh.template,复制成 spark-env.sh 后至少配置四个参数:

cp /opt/spark/conf/spark-env.sh.template /opt/spark/conf/spark-env.sh
cat >> /opt/spark/conf/spark-env.sh << 'EOF'
export JAVA_HOME=/usr/local/jdk1.8.0_202
export HADOOP_CONF_DIR=/etc/hadoop/conf
export SPARK_DRIVER_MEMORY=4g
export SPARK_HISTORY_OPTS="-Dspark.history.fs.logDirectory=hdfs://nameservice1/spark-history"
EOF

JAVA_HOME 建议固定用 JDK 8。虽然 Spark 3.3.1 支持 JDK 11,但 CDH 6.3.2 的底层组件大多在 JDK 8 上验证过,用 JDK 8 可以避开后面要讲的 JAXB 问题。SPARK_HISTORY_OPTS 让 History Server 提前指向 HDFS 日志目录,避免任务跑完看不到历史界面。

接着改 spark-defaults.conf,这是 YARN 模式能不能稳定跑起来的关键:

cat > /opt/spark/conf/spark-defaults.conf << 'EOF'
spark.master yarn
spark.eventLog.enabled true
spark.eventLog.dir hdfs://nameservice1/spark-history
spark.history.fs.logDirectory hdfs://nameservice1/spark-history
spark.sql.adaptive.enabled true
spark.sql.adaptive.coalescePartitions.enabled true
spark.yarn.jars hdfs://nameservice1/spark-jars/*.jar
EOF

spark.eventLog.enabled 打开后,每个 Application 的运行事件会写进 HDFS,History Server 才能展示完整的执行计划。spark.yarn.jars 这一项对 CDH 集群尤其重要:YARN 的 ApplicationMaster 启动后,要从 HDFS 拉取 Spark 依赖 jar,如果不提前把 jars 上传到 HDFS,每次提交都会花大量时间分发本地 jar,甚至直接失败。

3.3 第一轮提交:用 spark-submit 跑一个自带样例

配置完成后,先跑一个最基本的提交验证集群连通性:

/opt/spark/bin/spark-submit \\
–master yarn \\
–deploy-mode cluster \\
–queue default \\
–class org.apache.spark.examples.SparkPi \\
/opt/spark/examples/jars/spark-examples_2.12-3.3.1.jar \\
10

这个命令把 SparkPi 任务提交到 YARN 集群,计算圆周率。–deploy-mode cluster 表示 Driver 跑在 YARN 的 ApplicationMaster 里,任务结束后日志在 YARN 日志目录,不在执行客户端。第一次提交看到 INFO ApplicationMaster: Waiting for cluster to be ready 以及最终输出的 Pi 估算值,说明 YARN 集成已经通了。

如果这一步失败,九成问题都出在 3.1 和 3.2 的环境变量上。逐个节点检查 HADOOP_CONF_DIR 路径是否一致、队列名是否存在。CDH 的默认队列名如果不确定,用 yarn rmadmin -showQueues 确认,不要盲目填 root 或 default。

4. 让业务跑起来:spark 中读取 JSON 与内存参数怎么给

集群起来只是开始,真正的日常操作是拿着数据算指标。JSON 是 CDH 数据仓库场景里最常见的交换格式,这一章把读取、分析、写出和内存参数一起讲完。

4.1 用 DataSource API 读 JSON 文件

Spark 读 JSON 用 DataFrameReader,路径可以是本地文件、HDFS 路径或者 Hive 表目录。我在 PySpark 里的标准写法是:

from pyspark.sql import SparkSession

spark = SparkSession.builder \\
.appName("json_demo") \\
.enableHiveSupport() \\
.getOrCreate()

df = spark.read \\
.option("multiline", True) \\
.option("primitivesAsString", False) \\
.json("hdfs://nameservice1/data/order_log/order_2024*.json")

df.printSchema()
df.show(5)

这段代码先从 builder 创建 SparkSession,enableHiveSupport 表示让 Spark 可以访问 Hive 元数据,在 CDH 环境通常要开。json 方法读取路径下所有符合 order_2024 前缀的 JSON 文件。multiline 参数很关键:CDH 上常见的 JSON 是一行一个对象的紧凑格式,但有些却是格式化后多行一对象,multiline 设为 True 才能正确解析。primitivesAsString 保持默认 False,让数字字段被解析成 Long/Double,而不是全部变成字符串。

我第一次在 CDH 集群现学现卖时,就是没开 multiline,读一个格式化 JSON 文件,结果每条记录只解析出第一行字段。后面养成了习惯:读 JSON 之前先 head -5 看一眼文件长什么样。

4.2 一个 spark 数据分析案例:按城市统计订单取消量

读取之后顺手做一轮聚合,这个案例能覆盖 filter、groupBy、写回 HDFS 三个高频操作:

from pyspark.sql import functions as F

canceled = df.filter(F.col("status") == "CANCELLED")

result = canceled.groupBy("city_id") \\
.agg(F.count("*").alias("cancel_cnt"),
F.sum("amount").alias("cancel_amount")) \\
.orderBy(F.desc("cancel_cnt"))

result.write.mode("overwrite") \\
.option("compression", "gzip") \\
.json("hdfs://nameservice1/data/report/cancel_by_city")

filter 里用 F.col 引用列,避免字符串硬编码带来的拼写错误。groupBy 按城市维度聚合,count 记单量,sum 累计金额。写回时 mode("overwrite") 会先删旧目录再写新数据,如果目标目录存在且不能删,就要改用 append。compression 选 gzip 是常规操作,JSON 压缩成 gzip 后 HDFS 存储能省掉一半以上空间。Spark 跑完后可以在 YARN 日志里看到 Completed Result 或任务结束的应用。

4.3 spark 内存参数:executor 内存和 overhead 怎么算

内存参数是 Spark 作业跑得稳不稳的分水岭。YARN 模式下,每个 executor 实际向 YARN 申请的资源是 executor 内存加上 overhead 的之和。如果之和超过 YARN 队列给单个 container 的上限,任务会一直卡在 ACCEPTED 状态。

参数 含义 常用值
spark.executor.memory executor 堆内内存 8g / 16g
spark.executor.memoryOverhead 堆外内存,容纳 JVM 自身、网络缓冲区、Python 进程等 2g / 4g / 6g
spark.driver.memory Driver 进程堆内内存 2g / 4g
spark.memory.offHeap.enabled 是否启用堆外显式内存 false,除非有明确需要

我的分配经验:单台物理机 64G 内存,跑 3 个 executor,每个 executor 分配 8g 堆内加 2g 堆外,加上系统预留,基本不会把节点打爆。如果任务涉及大量 shuffle,把 overhead 调高到 6g 比堆内再加 4g 效果更明显,因为 shuffle 缓存和溢写很多时候在堆外。CDH 集群的 YARN 容器最大内存由 yarn-site.xml 里的 yarn.nodemanager.resource.memory-mb 控制,提交前先用命令行确认一下这个值,别让两个参数打架。

spark.sql.adaptive.enabled 在 spark-defaults.conf 里已经打开,这是 Spark 3 的 Adaptive Query Execution。它会根据运行时 shuffle 结果动态调整分区数,避免一个超大可观测任务挂在单个 reducer 上。

5. 避坑排查:CDH 6.3.2 上 Spark 3.3.1 的五个高频故障

这个组合不是官方标准发行版,多个版本号交错,实际踩坑概率比纯开源 Hadoop 高很多。下面五条是我觉得最值得记录的。

5.1 现象:提交老 Jar 直接 NoClassDefFoundError: scala/collection/…

现象:用以前 Spark 2.3 时代编译的业务 Jar 提交任务,Driver 还没开始处理数据就报 NoClassDefFoundError,指向 scala.collection.immutable.List 或 scala.collection.IterableOnce 之类的类。

原因:Spark 3.3.1 用 Scala 2.12 编译,而老 Jar 是 Scala 2.11 编译的。Scala 2.11 和 2.12 的集合类在二进制层面不兼容,运行期找不到对应类。

解决:把业务代码用 Scala 2.12 重新编译,pom 里配置 scala.version 为 2.12.17 左右,再打 Jar。如果不想动源码,可以保留一个旧版 Spark 2.x 的提交入口作为过渡,但这不是长久之计。从那以后我每次升级 Spark 都先检查 Scala 版本。

5.2 现象:YARN 集群模式 ApplicationMaster 一直重试

现象:spark-submit 提交后,YARN 界面显示 ApplicationMaster 启动失败,重启了几次还是 ACCEPTED,日志里看不到具体业务异常,只能看到容器退出。

原因:最常见的是 spark.yarn.jars 没配置,或者配置的 HDFS 路径不存在。ApplicationMaster 启动后需要从 HDFS 拉取 Spark 依赖 jar,拉不到就反复重启。

解决:先按 3.2 的配置把 jar 上传到 HDFS:

hdfs dfs -mkdir -p /spark-jars
hdfs dfs -put /opt/spark/jars/*.jar /spark-jars/

然后在 spark-defaults.conf 里确认 spark.yarn.jars 指向 hdfs://nameservice1/spark-jars/*.jar。上传之后重启 History Server,再提交同一任务,应用状态会从 ACCEPTED 变成 RUNNING。

5.3 现象:读取 Hive 表时 SerializationError 或 ClassNotFound

现象:Spark SQL 里执行 select * from ods_table 直接报错,错误里能看到 Hive metastore 相关的序列化异常,或者找不到 com.hadoop.compression 之类类。

原因:这个包虽然做了 CDH 适配,但运行时如果 classpath 里混入了 CDH 自带的 hive-exec jar,版本冲突还是会冒出来。Spark 3 默认内置 Hive 2.3 客户端,和 CDH 的 Hive 2.1.1 在序列化协议上有差异。

解决:把集群客户端目录里的 hive-site.xml 拷贝到 Spark 的 conf 目录,确保 Spark 连的是 CDH 的 Hive Metastore;同时检查提交命令里有没有手动加额外的 hive-exec jar。最省心方式是 spark-submit 时不要用 –jars 塞 Hive 相关依赖,让 Spark 用包内自带的版本。

5.4 现象:PySpark 作业报 Python 版本不对或 ModuleNotFoundError

现象:用 pyspark 提交任务,日志里显示当前解释器是 /usr/bin/python,系统提示找不到 pyspark 模块,或者直接看到 python2.7 字样。

原因:CDH 操作系统默认把 /usr/bin/python 指向 Python 2,而 PySpark 的 Driver 和 Executor 需要同版本的 Python 解释器。如果环境里装了 Python 3 但没指定,两边解释器不一致就会各种怪错。

解决:在提交环境里设置两个变量:

export PYSPARK_PYTHON=/usr/bin/python3
export PYSPARK_DRIVER_PYTHON=/usr/bin/python3

如果每个节点都装了 Python 3,这样设置后 PySpark 的 driver 和 executor 会统一用 python3。注意自带 Python 版本要和提交端的 Python 版本一致,否则还是会在序列化时翻车。

5.5 现象:本地模式跑得好好的,YARN 一提交就失败

现象:同一个 SQL,spark-shell 本地模式秒出结果,改成 –master yarn 提交后报内存超限或资源不足。

原因:本地模式默认不经过 YARN 的容器隔离,对内存和 CPU 不做硬限制;YARN 模式下 executor 申请的资源身全部受队列和 nodemanager 限制。往往是 spark.executor.memory 加 memoryOverhead 超过队列上限,或者队列 ACL 不允许当前用户提交。

解决:用 yarn application -list 查看队列资源占用,用 yarn queue -status <队列名> 确认最大容量和 ACL。把 executor 内存降一档,或者申请一个有提交权限的队列。另一个容易忽略的点是提交用户 HK 是否有 HDFS 写权限,很多权限报错不会直接写 permission denied,而是表现为任务失败重试,这个也要一并检查。

6. 收尾技巧:一行冒烟测试脚本验证集群没有翻车

每次部署完新包,或者改完内存参数,我不会先跑线上业务,而是固定跑一个冒烟测试。它能验证 HDFS 连通、YARN 资源分配、Python 解释器、以及 JSON 读写链路是不是都正常。

冒烟脚本就放在 /opt/spark/smoke_test.py,逻辑非常简单:

from pyspark.sql import SparkSession
from pyspark.sql import functions as F

spark = SparkSession.builder.appName("smoke_test").getOrCreate()
df = spark.read.option("multiline", True).json("/tmp/smoke_test_input.json")
count = df.groupBy("type").count().collect()
print("SMOKE_TEST_RESULT:", count)
spark.stop()

提交命令固定这样写:

export PYSPARK_PYTHON=/usr/bin/python3
export PYSPARK_DRIVER_PYTHON=/usr/bin/python3
/opt/spark/bin/spark-submit \\
–master yarn \\
–deploy-mode client \\
–queue default \\
–num-executors 2 \\
–executor-cores 2 \\
–executor-memory 4g \\
/opt/spark/smoke_test.py

client 模式下 Driver 跑在提交节点,stdout 会直接打回到终端,出错时看日志最快。看到 SMOKE_TEST_RESULT 输出,就说明从 SparkSession 创建到 YARN 申请容器、再到 HDFS 读写全部走通了。这时再确认一下 YARN 界面上这个 Application 的 executor GC 时间和 shuffle 量,如果 GC 时间占比低于 10%,说明内存参数基本合理;如果占比很高,先按第 4.3 节把 overhead 调大再跑一轮。

这套检查我每次都会强制走一遍。之前有一次只改了 spark-defaults.conf 里的一个内存参数,直接提交线上任务,结果死在一半。从那以后,我的规则是:任何集群变动,先冒烟,再业务。这个习惯帮我省下了大量半夜救火的时间,希望帮到你。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

赞(0)
未经允许不得转载:171主机测评 » CDH 6.3.2上Spark 3.3.1集群安装配置与高频故障排查
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址