☰
CDH6.3.2部署Spark3.3.1:定制tgz包配置与YARN集群集成实战
2026/10/10 13:08:04 网站建设 项目流程

简介:这是一份针对CDH 6.3.2环境编译的Spark 3.3.1发行包,主要面向需要在CDH集群中部署Spark SQL的数据工程师与运维人员。它解决了社区版Spark与CDH Hadoop版本不兼容的问题,可直接替换或配合现有CDH组件使用,并参考配套文档完成Spark SQL的配置与调优。压缩包共包含1313个文件,体积约254.24MB,涵盖PySpark/Spark SQL相关的Python脚本、Scala/Java源码、可执行jar包及若干配置文件,便于用户按需查阅或二次开发。目前已有1177人关注学习。相比自行编译源码,该资源已整合好CDH适配层,能显著缩短环境搭建时间;配合描述中的CSDN博文,可帮助读者快速掌握在CDH 6.3.2上启用Spark SQL的完整流程与常见问题排查思路,适合具备一定Spark基础、希望在生产环境中快速落地的开发者。

1. 这张 tgz 是 CDH 用户上 Spark 3 的唯一的捷径

如果你正在维护一套 CDH 6.3.2 集群,又想在不动 Hadoop 内核的前提下把 Spark 从 2.4 升到 3.3.1,那么你大概率会下载到spark-3.3.1-bin-3.0.0-cdh6.3.2.tgz这个包。它不是 Apache 官方原版 Spark,而是针对 CDH 6.3.2 的 Hadoop 3.0.0 客户端重新编译的 Spark 发行版,解决了官方预编译包在 CDH 上提交 YARN 任务时认证失败、依赖冲突、RPC 协议不匹配的一堆麻烦。适合想在 CDH 集群里跑 Structured Streaming、Dynamic Partition 或 Spark 3 语法特性的数据工程师,也适合被 CDH 自带 Spark 2.4 的 bug 折磨到想换新版本的人。

2. 装前先认清这个包的三个底层事实

2.1 包名里的版本号到底代表什么

spark-3.3.1-bin-3.0.0-cdh6.3.2.tgz这段名字不是随便拼的。spark-3.3.1是 Apache Spark 的版本号,bin表示这是预编译的二进制分发包,而后面的3.0.0-cdh6.3.2则直接告诉你它针对的 Hadoop 客户端版本是 CDH 6.3.2 内置的 Hadoop 3.0.0。换句话说,这个包在编译时链接的 Hadoop 依赖、YARN 协议版本、HDFS 客户端 API,全部对齐到 CDH 6.3.2 那套运行时环境。

这里有一个最常见的误解:有人看到3.0.0以为是 Spark 3.0,其实它是 Hadoop 的版本标识。CDH 6.3.x 的底层 HDFS 和 YARN 都是 Hadoop 3.0.0 的 CDH 定制分支,所以官方 Apache 版的 Spark 预编译包(默认针对 Hadoop 2.7 或 3.2)拿到 CDH 上直接跑,经常在提交任务时报Invalid protocol或TokenCache相关错误。原因就是 YARN Application Client 协议版本和 CDH 的实现不兼容。用这个 CDH 定制包,等于把协议层对齐了,省掉你自己重新编译的工作。

2.2 为什么不直接用 CDH 自带的 Spark

CDH 6.3.2 管理界面里默认带的是 Spark 2.4.0,而 Spark 3.3.1 在 ANSI SQL、动态分区裁剪、Adaptive Query Execution、Kafka 数据源等能力上都有明显提升。如果你只是想跑简单的 ETL,2.4 够用;但一旦涉及MERGE INTO、UPDATE语法或者想让 AQE 自动优化 Join 的 Shuffle 分区数,2.4 就力不从心了。我见过某公司在 CDH 上用 2.4 跑数据湖的 Upsert 任务,因为不支持MERGE INTO,只能用 Hive 重写整个分区,任务耗时从 40 分钟涨到 3 小时。换到 Spark 3.3.1 后,同样的逻辑用 Delta 格式的 Merge 语法,20 分钟就结束了。

所以这个 tgz 的实际价值是:它让你绕过 CDH 组件版本锁定的限制,在一个受控的目录里独立运行 Spark 3.3.1,提交任务时通过 YARN 的yarn-cluster模式复用 CDH 的调度和存储层,从 HDFS 读写数据,跟 Hive Metastore 通信,同时保留 CDH 自身的运维监控体系不动。换句话说,它就是给 CDH 集群开了一扇侧门。

2.3 这个包和 Apache 原版的差别在哪些关键类

如果你把包解压之后做一次diff,会发现yarn/、hive/下的依赖 jar 里,有若干类的包名前缀带org.apache.hadoop,但实现是从 CDH 的hadoop源码编译出来的。举个例子,hadoop-common-3.0.0-cdh6.3.2.jar里的UserGroupInformation类,对 Kerberos ticket 的缓存策略就和 Apache Hadoop 3.2 不同。这就是为什么你在 CDH 集群上用官方 Spark 跑spark-submit --keytab时,经常出现LoginException或ticket expired的隐性问题。

使用这个包时,你还需要注意一个细节:它的 Spark 内部配置项spark.sql.hive.metastore.version默认值可能还是 2.3.0,而 CDH 6.3.2 的 Hive Metastore 是 2.1.1 的 CDH 分支。如果不去改配置直接连 Hive,非常容易在读取分区表时报MetaException(message:Got exception: org.apache.thrift.TApplicationException。我一般会在spark-defaults.conf里显式设置:

spark.sql.hive.metastore.version=2.1.1 spark.sql.hive.metastore.jars=/opt/cloudera/parcels/CDH/lib/hive/lib/*

这样 Spark 就直接用 CDH 自带的 Hive Metastore 客户端 jar,而不是它内置的旧版本,能少踩很多坑。

注意:如果你的集群开启了 Kerberos,上述配置还需要加spark.yarn.principal和spark.yarn.keytab,否则提交任务会卡在 Hive Metastore 连接阶段,日志里反复出现访问被拒。

3. 把 Spark 3.3.1 挂到 CDH 集群:解压配置集成三步走

3.1 下载解压并让所有节点共享一份二进制

动手第一步不是解压,而是先确认 CDH 的 YARN 是yarn.nodemanager.aux-services打开的状态,因为 Spark on YARN 依赖 NodeManager 的辅助服务来启动 Executor。在集群任一节点上执行:

# 在 CM 所在节点用 curl 拉取包,注意替换为你的实际下载地址 curl -L -o /opt/spark-3.3.1-bin-3.0.0-cdh6.3.2.tgz \ http://your-repo/spark-3.3.1-bin-3.0.0-cdh6.3.2.tgz # 解压到统一目录 sudo mkdir -p /opt/spark sudo tar -xzf /opt/spark-3.3.1-bin-3.0.0-cdh6.3.2.tgz -C /opt/spark sudo ln -s /opt/spark/spark-3.3.1-bin-3.0.0-cdh6.3.2 /opt/spark/current # 如果有多个 Gateway 节点,用 rsync 同步 rsync -av /opt/spark/current/ hadoop@worker01:/opt/spark/current/

这个步骤的核心目的是保证 Spark 二进制在所有需要提交任务的节点上完全一致,避免不同节点有不同 jar 版本导致的偶发序列化错误。如果你只有一个 YARN 集群,不一定每个节点都要部署,Gateway 节点(提交任务的机器)有即可,因为 Executor 的 jar 会通过 YARN DistributedCache 分发。但要注意,如果开启了spark.yarn.archive或者手动指定了spark.yarn.dist.jars,这个包里的jars/目录最好原样打包成一个 zip 让 YARN 分发,不然每个 Executor 启动时都会去 HDFS 拉大量小文件,任务启动慢到怀疑人生。

解压之后建议先检查一下目录权限。因为 Spark 的临时目录默认在/tmp,如果多用户共用服务器,/tmp/spark目录的权限位会导致随机 Shuffle 文件写入失败。我一般会在spark-env.sh里设SPARK_LOCAL_DIRS=/data/spark/tmp,并对它执行:

sudo chmod -R 1777 /data/spark/tmp

3.2 配置 spark-env.sh 和 spark-defaults.conf 的关键参数

spark-env.sh是 Spark 进程启动前加载环境变量的地方。在 CDH 环境下,最需要设置的就是HADOOP_CONF_DIR和SPARK_DIST_CLASSPATH,前者让 Spark 能找到 YARN 和 HDFS 的配置文件,后者让 Spark 的 driver/executor 进程能正确拿到 CDH 的 Hadoop 类库。不要自己去拼 classpath,CDH 提供了一个脚本可以输出完整的路径:

# 让 Spark 使用 CDH 的 Hadoop 客户端配置 export HADOOP_CONF_DIR=/etc/hadoop/conf # 关键:SPARK_DIST_CLASSPATH 必须指向 CDH 的 hadoop classpath export SPARK_DIST_CLASSPATH=$(/opt/cloudera/parcels/CDH/lib/hadoop/bin/hadoop classpath) # 指定本地临时目录,避免 /tmp 空间不足 export SPARK_LOCAL_DIRS=/data/spark/tmp # JVM 参数统一,防止不同节点 Java 版本不一致 export SPARK_DAEMON_JAVA_OPTS="-Xms1g -Xmx1g"

SPARK_DIST_CLASSPATH是这里最关键的变量。如果漏了,你会在提交任务时看到ClassNotFoundException: org.apache.hadoop.yarn.api.records.ApplicationId,因为 Spark 默认带的 classpath 里没有 CDH 的 YARN 类。而如果你在每台机器上都装了 CDH parcel,用上面这条命令可以动态获取路径,比手写/opt/cloudera/parcels/CDH/lib/hadoop/lib/*更可靠,因为 parcel 升级后路径会变。

spark-defaults.conf里需要设置的参数,我按重要性排序列在下面:

# Spark 假装自己是 YARN 的一个客户端 spark.master=yarn spark.submit.deployMode=client # 让 Spark 通过 Hive Metastore 读取表元数据 spark.sql.hive.metastore.version=2.1.1 spark.sql.hive.metastore.jars=/opt/cloudera/parcels/CDH/lib/hive/lib/* # 开启动态资源分配,避免常驻浪费 YARN 队列资源 spark.dynamicAllocation.enabled=true spark.dynamicAllocation.shuffleTracking.enabled=true spark.shuffle.service.enabled=true # 内存参数:driver 和 executor 都按 CDH 节点的实际内存来 spark.driver.memory=4g spark.executor.memory=4g spark.executor.cores=2 # 关键:指定 Spark 3 的 Shuffle 服务,配合上面的 shuffleTracking spark.yarn.shuffle.stopOnFailure=false

动态资源分配要配合 YARN 的 Shuffle Service 使用,但 CDH 6.3.2 默认不会帮你部署 Spark 3 对应的 Shuffle Service。你需要把 Spark 包里的yarn/shuffle文件夹放到 NodeManager 的 classpath 里,或者在每台 NodeManager 节点上额外启动一个独立的 Shuffle Service 进程。前者操作复杂且升级 NodeManager 时容易丢失;我建议直接用后者,写一个 systemd 服务管理它,具体做法在第 4 章里会说。

3.3 验证 YARN 和 HDFS 的连通性,用 Pi 程序跑第一轮冒烟

配置完成后别急着跑业务 SQL,先跑一个不依赖 Hive 的 Spark 自带的 Pi 程序,验证 YARN 调度和 HDFS 读写链路是否通。这一步能把「配置错误」和「业务代码错误」隔离开来。

/opt/spark/current/bin/spark-submit \ --class org.apache.spark.examples.SparkPi \ --master yarn \ --deploy-mode client \ --driver-memory 2g \ --executor-memory 2g \ --executor-cores 1 \ /opt/spark/current/examples/jars/spark-examples_2.12-3.3.1.jar \ 10

如果这个程序能在 2 分钟内输出Pi is roughly 3.1418...,说明 Spark 和 CDH 的基本链路是通的。如果卡在Submitting application to YARN这一步,用yarn application -list看应用是否创建成功;如果创建成功但 Executor 一直起不来,去看 NodeManager 日志里的NodeManager.log,最常见的原因是SPARK_DIST_CLASSPATH里包含的 jar 和 NodeManager 自身的 classpath 冲突,导致NoClassDefFoundError。解决办法是在spark-env.sh里把 CDH 的类库追加到SPARK_DIST_CLASSPATH的最前面,让 Spark 优先加载 CDH 版本。

提示:跑 Pi 程序时,--deploy-mode client表示 driver 运行在你当前的提交节点上。如果你在个人电脑上提交到远程集群,必须改成cluster模式,否则 driver 进程起不来。这个细节我见过很多人翻车。

3.4 集成 Hive 表:从 HDFS 路径自动映射表结构

Spark 3 读 Hive 表有两种方式:走 Hive Metastore 拿元数据,或者直接按 HDFS 路径 + schema 推断。在 CDH 集群上,几乎所有生产表都注册在 Metastore,所以正确做法是配好前面的spark.sql.hive.metastore.jars后,直接用 Spark SQL 建临时视图来验证:

/opt/spark/current/bin/spark-sql \ --master yarn \ --deploy-mode client \ --executor-memory 2g \ --executor-cores 1 \ -e "SHOW DATABASES;"

如果你能看到 CDH 里已有的数据库列表,说明 Hive Metastore 集成成功了。接着查一张业务表的数据量和分区数,确认 Spark 3.3.1 的FileSourceScanExec能正确解析 CDH 的 HDFS 路径写法。这里有个高频坑:CDH 的表路径很多挂载在/user/hive/warehouse下的分区目录,目录名可能包含=号,比如dt=2024-01-01。Spark 3.3.1 默认支持这种分区路径,但如果表的存储格式是 CDH 定制的 Hive RCFile 或自定义 SerDe,Spark 原生读不了,必须在会话里加:

SET spark.sql.hive.convertMetastoreOrc=true; SET spark.hadoop.hive.metastore.schema.verification=false;

spark.sql.hive.convertMetastoreOrc这个参数决定 Spark 是否把 ORC 表转换为原生的 Spark ORC 读法,而不是走 Hive 的HiveSerDe。如果你发现查 ORC 表慢得离谱,先检查这个参数是不是没开。

4. 部署后必看:Spark 3.3.1 on CDH 的 5 个高频故障与排查

4.1 提交任务时报NoClassDefFoundError: org/apache/hadoop/mapreduce/TaskAttemptContext

现象:spark-submit提交到 YARN 后,Driver 启动不到 5 秒就失败,日志里出现NoClassDefFoundError,但如果你用--master local[2]跑同样的代码却能正常执行。原因:SPARK_DIST_CLASSPATH只包含了 Hadoop 的核心类,没有包含 MapReduce 相关的 jar。CDH 里 MapReduce 库位于/opt/cloudera/parcels/CDH/lib/hadoop-mapreduce/*,而hadoop classpath命令默认输出包含这一项的概率取决于你的HADOOP_CLASSPATH环境变量有没有被覆盖。解决:在spark-env.sh里手动追加:

export SPARK_DIST_CLASSPATH="$SPARK_DIST_CLASSPATH:/opt/cloudera/parcels/CDH/lib/hadoop-mapreduce/*"

4.2 动态资源分配开启后 Executor 一直持锁不释放

现象:任务跑完了,yarn application -list里已经看不到应用,但 web UI 上 Executor 的「Remove」操作一直停在Removing状态,队列资源被占着不归还。原因:CDH 的 YARN NodeManager 上没有部署 Spark 3 对应的 Shuffle Service,而动态资源分配的执行器删减依赖 shuffle 数据的外部存储服务来确认安全回收。没有 Shuffle Service,Spark 不敢释放 Executor。解决:在每个 NodeManager 节点上部署独立 Spark Shuffle Service,用 systemd 托管:

# 在每台 NodeManager 节点上创建 systemd 服务 sudo tee /etc/systemd/system/spark-shuffle.service <<'EOF' [Unit] Description=Spark Shuffle Service After=network.target [Service] Type=simple User=yarn Environment="SPARK_HOME=/opt/spark/current" Environment="HADOOP_CONF_DIR=/etc/hadoop/conf" ExecStart=/opt/spark/current/sbin/start-mesos-shuffle-service.sh Restart=always [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reload && sudo systemctl enable spark-shuffle --now

注意,这个脚本是 Mesos 的 Shuffle Service,但你把它跑在 YARN 环境下也没问题,因为它的本质是启动一个独立的 Netty 服务监听某个端口,Executor 通过这个端口把 Shuffle 数据推出去。启动后在spark-defaults.conf里加两行让 Executor 知道去哪里找这个服务:

spark.shuffle.service.port=7337 spark.shuffle.service.enabled=true

4.3 读取 Hive 分区表报MetaException: Got exception: org.apache.thrift.transport.TTransportException

现象:在spark-sql里执行SELECT COUNT(*) FROM 大表时抛TTransportException,而 CDH 自带的 Hive CLI 查同样表正常。原因:Spark 3.3.1 默认的 Hive Metastore 版本配置是 2.3.0,它会尝试用 Thrift 协议加 Token 标识去连接 CDH 的 Metastore 2.1.1,两边 Thrift 协议握手失败。解决:确认spark.sql.hive.metastore.jars指向 CDH 目录后,删掉 Spark 内置 jar 的影响:

# 在 spark-defaults.conf 里强制走本地 jar spark.sql.hive.metastore.jars=/opt/cloudera/parcels/CDH/lib/hive/lib/*:/opt/cloudera/parcels/CDH/lib/hive-hcatalog/share/hcatalog/*

如果该配置没生效,检查你是不是同时设置了spark.sql.hive.metastore.jars.path,这个参数会把上一条覆盖掉。

4.4 磁盘临时目录写满导致 Shuffle 取数失败

现象:大 Shuffle 任务跑到一半,Executor 报FetchFailedException,点开堆栈发现是No space left on device。原因:Spark Executor 的 Shuffle 数据默认写到/tmp,而 CDH 机房节点的/tmp通常只有 5GB 甚至更小。解决:在spark-env.sh里设置SPARK_LOCAL_DIRS指向数据盘,并保证 YARN 的yarn.nodemanager.local-dirs与它不在同一块盘上,避免相互挤占。同时给这些目录设置独立的健康监测脚本,如果某块盘快满了,自动把任务换到其他 Executor 上,降低「一个盘拖垮一个 Job」的概率。

4.5 提交任务的 Gateway 节点 OOM

现象:多个用户同时spark-submit到 YARN,Gateway 机器 JVM 频繁 Full GC,最后直接OutOfMemoryError: Java heap space。原因:所有 Driver 进程都运行在 Gateway 节点上,而 Spark 3.3.1 的 Driver 默认堆外内存开销又比 2.4 大。解决:要么限制单用户提交的 Driver 内存上限,在用户端强制执行spark.driver.memory=2g等配额;要么在 Gateway 上做提交代理,把--deploy-mode统一改成cluster,让 Driver 跑在集群内的某个容器里。我合作的某运维团队做了后者,Gateway 的压力直接下降了一个量级,因为所有 Driver 的 JVM 开销散到了集群的几十个节点上。

5. 从冒烟到跑业务:三招让 Spark 3.3.1 在 CDH 上用得长久

5.1 用历史服务定位慢作业的瓶颈

spark-submit跑完应用就消失了,想复盘哪个 Stage 慢只能去翻日志。我强烈建议把 Spark 3.3.1 自带的 HistoryServer 也在 CDH 环境里跑起来。配置方法不复杂,在spark-defaults.conf里设置:

spark.eventLog.enabled=true spark.eventLog.dir=hdfs://nameservice1/user/spark/spark-events spark.eventLog.compress=true spark.history.fs.logDirectory=hdfs://nameservice1/user/spark/spark-events

然后用sbin/start-history-server.sh启动,Web 端口默认 18080。这样每个任务结束后,你能看到每个 Stage 的 Shuffle Read/Write 量、GC 时间、执行器调度延迟,定位「某个任务为什么吃满 40 个 Executor 却只跑 20% 利用率」这类问题,比在日志里大海捞针效率高得多。

5.2 用spark-submit --py-files跑 Python 数据分析任务

CDH 自带 Spark 2.4 对 Python 3 的支持不友好,而这套 Spark 3.3.1 是原生支持 Python 3 的。平时做数据分析可以直接把代码打成 zip 包,用--py-files提交,不用在 Gateway 上装一堆 Python 包:

/opt/spark/current/bin/spark-submit \ --master yarn \ --deploy-mode client \ --py-files hdfs://nameservice1/user/etl/analysis_deps.zip \ --conf spark.sql.shuffle.partitions=200 \ /data/scripts/analyze_clickstream.py --date 2024-05-20

spark.sql.shuffle.partitions这个参数在 3.3.1 里默认值 200 有点保守。如果你的数据量在 TB 级,我一般会调整到 500 到 1000,同时搭配 AQE 的spark.sql.adaptive.coalescePartitions.enabled=true让它在小任务时自动缩减分区,避免小文件过多。

5.3 给 Spark 3 定制 YARN 队列并限制资源上限

CDH 里多个业务组共用 YARN 队列时,Spark 3 动态分配默认会尽量多占资源。最好的做法是在 Capacity Scheduler 里单独划一个spark3队列,并给这个队列设置yarn.scheduler.capacity.maximum-capacity,限制它最多用集群的 40% 资源;然后在 Spark 提交时用--queue spark3固定队列。这样就能避免 Spark 3 任务把 Hive on MR 的队列资源全部挤走,产生「Spark 一跑,别人全部排队」的事故。

注意:如果你用了 Fair Scheduler,队列配置方式和 Capacity Scheduler 不同,但--queue参数是通用的。别把队列名拼错,否则 YARN 会直接拒绝提交,报错信息是Queue named xxx does not exist。

我做 CDH 维护这几年,最大的教训就是:给集群引入新组件时,先在隔离队列里跑满 48 小时稳定性测试,再放业务流量。看似多花了时间,实际上比出故障后回滚配置要省几倍力气——特别是处理 Kerberos 票据续期、Shuffle Service 故障转移这类隐蔽问题,应急排查时真是又急又气。希望这篇笔记能让你在部署spark-3.3.1-bin-3.0.0-cdh6.3.2.tgz时少走几个坑,把精力留在真正的数据分析任务上。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询