
简介这是一份针对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 自带的 SparkCDH 6.3.2 管理界面里默认带的是 Spark 2.4.0而 Spark 3.3.1 在 ANSI SQL、动态分区裁剪、Adaptive Query Execution、Kafka 数据源等能力上都有明显提升。如果你只是想跑简单的 ETL2.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.version2.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/ hadoopworker01:/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/tmp3.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 类库。不要自己去拼 classpathCDH 提供了一个脚本可以输出完整的路径# 让 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 -Xmx1gSPARK_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.masteryarn spark.submit.deployModeclient # 让 Spark 通过 Hive Metastore 读取表元数据 spark.sql.hive.metastore.version2.1.1 spark.sql.hive.metastore.jars/opt/cloudera/parcels/CDH/lib/hive/lib/* # 开启动态资源分配避免常驻浪费 YARN 队列资源 spark.dynamicAllocation.enabledtrue spark.dynamicAllocation.shuffleTracking.enabledtrue spark.shuffle.service.enabledtrue # 内存参数driver 和 executor 都按 CDH 节点的实际内存来 spark.driver.memory4g spark.executor.memory4g spark.executor.cores2 # 关键指定 Spark 3 的 Shuffle 服务配合上面的 shuffleTracking spark.yarn.shuffle.stopOnFailurefalse动态资源分配要配合 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下的分区目录目录名可能包含号比如dt2024-01-01。Spark 3.3.1 默认支持这种分区路径但如果表的存储格式是 CDH 定制的 Hive RCFile 或自定义 SerDeSpark 原生读不了必须在会话里加SET spark.sql.hive.convertMetastoreOrctrue; SET spark.hadoop.hive.metastore.schema.verificationfalse;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 ServiceSpark 不敢释放 Executor。解决在每个 NodeManager 节点上部署独立 Spark Shuffle Service用 systemd 托管# 在每台 NodeManager 节点上创建 systemd 服务 sudo tee /etc/systemd/system/spark-shuffle.service EOF [Unit] DescriptionSpark Shuffle Service Afternetwork.target [Service] Typesimple Useryarn EnvironmentSPARK_HOME/opt/spark/current EnvironmentHADOOP_CONF_DIR/etc/hadoop/conf ExecStart/opt/spark/current/sbin/start-mesos-shuffle-service.sh Restartalways [Install] WantedBymulti-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.port7337 spark.shuffle.service.enabledtrue4.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到 YARNGateway 机器 JVM 频繁 Full GC最后直接OutOfMemoryError: Java heap space。原因所有 Driver 进程都运行在 Gateway 节点上而 Spark 3.3.1 的 Driver 默认堆外内存开销又比 2.4 大。解决要么限制单用户提交的 Driver 内存上限在用户端强制执行spark.driver.memory2g等配额要么在 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.enabledtrue spark.eventLog.dirhdfs://nameservice1/user/spark/spark-events spark.eventLog.compresstrue spark.history.fs.logDirectoryhdfs://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.partitions200 \ /data/scripts/analyze_clickstream.py --date 2024-05-20spark.sql.shuffle.partitions这个参数在 3.3.1 里默认值 200 有点保守。如果你的数据量在 TB 级我一般会调整到 500 到 1000同时搭配 AQE 的spark.sql.adaptive.coalescePartitions.enabledtrue让它在小任务时自动缩减分区避免小文件过多。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时少走几个坑把精力留在真正的数据分析任务上。本文还有配套的精品资源点击获取