news 2026/10/27 19:40:27

CDH 6.2 与 Spark on YARN 集群部署调优实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CDH 6.2 与 Spark on YARN 集群部署调优实战

1. 先想清楚 CDH 6.2 这套版本要用在哪,再动手装

上个月帮人重装一套跑了三年的 CDH 6.2 集群,原因是前任运维把 YARN 的yarn.nodemanager.resource.memory-mb按物理内存写满了,Spark 作业一多就整台机器卡死。重装过程里最花时间的不是装,而是对着版本矩阵一项项确认“这个组合到底行不行”。所以这篇不写成流水账式的安装步骤,而是把它当成一次完整的环境交付过程——从版本选型、系统底座、元数据库,一直走到 Spark 服务上线、提交第一个 YARN 作业、看 History Server 的日志。适合手上还有 CDH 6.2 存量集群要维护、或者需要从零搭一套离线数仓基础环境的人参考。

先说清楚一个前提:CDH 6.2 是一个有明确年份边界的产品,它的组件版本组合是硬约束,不是你想换就能换。它的定位是一套打包完整、开箱即用的 Hadoop 生态发行版,用 Cloudera Manager(下文简称 CM)做统一的安装、监控、配置下发和告警。你拿它跑 Spark,本质上是拿它跑“Spark on YARN”,Spark 本身不承担资源调度,只做计算。

1.1 版本矩阵决定了你的 JDK 和操作系统不能乱来

CDH 6.2 对应的组件版本大致是这样一组搭配,我在装之前会先抄在一张纸上,后面所有配置项都围绕它来定:

组件CDH 6.2 对应版本说明
Hadoop(HDFS/YARN)3.0.0HDFS 默认 NameNode RPC 端口是 8020,Web UI 是 9870,和 Hadoop 2 的 50070 不一样
Spark2.4.0只有 on YARN 一种部署方式,没有独立集群模式
Hive2.1.1元数据必须外置到 MySQL/PostgreSQL
ZooKeeper3.4.5建议 3 节点奇数部署
HBase2.1.0依赖 HDFS 和 ZK
Oozie5.1.0需要独立数据库
Impala3.2.0可选,纯离线场景可以不上
CM Server/Agent6.2.xCM 与 CDH 版本要匹配

操作系统这块,CDH 6.2 支持的是 RHEL/CentOS 7.x 这一代,7.2 到 7.9 都可以。别拿 CentOS 8 或者更新的版本去试,包依赖和 systemd 的行为差异会让你在第一步 yum 安装就卡住。JDK 统一用 8,不要用 11 也不要混装,CM 6.2 不支持 JDK 11,混装之后最常见的现象是 Agent 起来了但心跳一直红,或者 CM Server 启动日志里报UnsupportedClassVersionError。

还有一个容易被忽略的点:所有节点必须是同一个 JDK 小版本。我之前遇到过一台机器上残留了老版本 JDK,/usr/bin/java指向的和JAVA_HOME指向的不是同一个,结果 Hive 的 schematool 在一个节点上能跑、在另一个节点上直接报错。装 JDK 这件事,宁可在每台机器上手工解压统一目录,也不要用不同来源的包。

1.2 Spark 在这个体系里不是独立集群,而是 YARN 上的一个计算框架

这一点必须先讲透,否则后面角色分配会做错。很多从社区版 Spark 转过来的人,习惯性去找 Standalone 或者 Mesos,CDH 的 Spark 服务里压根没有这个选项。CDH 里的 Spark 服务只做三件事:分发客户端配置、管理 Spark 的一些全局默认参数、拉起 History Server。真正的资源申请、容器分配、队列调度,全部走 YARN。

所以你在规划机器的时候,Spark 不需要单独占一台机器当 Master。你真正需要的是一个健康的 YARN 集群(ResourceManager + NodeManager)和 HDFS。Spark 的 Executor 是 YARN 的 Container,Executor 的内存上限受yarn.scheduler.maximum-allocation-mb限制,Executor 的核数受yarn.scheduler.maximum-allocation-vcores限制。这两个参数如果没放开,你在 spark-submit 里写再大的--executor-memory也会被 YARN 直接拒绝,报InvalidResourceRequestException。

理解了这层关系,后面所有配置你都会有个判断标准:凡是要影响 Spark 跑多大的,先去 YARN 那边改;凡是要影响 Spark 跑出来结果好不好查的,才在 Spark 服务里改。

1.3 机器规划:三台起步的角色分布

最小可用的三节点规划我一般这样切,测试环境够用,生产按这个比例横向扩:

主机角色节点1节点2节点3
CM Server + Agent是AgentAgent
NameNode(Active/Standby)是是否
JournalNode是是是
DataNode是是是
ResourceManager是是否
NodeManager是是是
ZooKeeper是是是
Hive Metastore / HiveServer2是否否
MySQL是否否
Spark History Server是否否
Spark Gateway是是是

Gateway 角色特别说一下:它不是常驻进程,作用是把 Spark 的客户端配置文件(spark-defaults.conf、hive-site.xml、core-site.xml、yarn-site.xml 等)下发到这台机器上,让这台机器能直接跑 spark-shell 或者 spark-submit。少了这个角色,你在客户端上跑作业会一直提示找不到配置。每台可能提交作业的机器都要挂 Gateway,这是我在项目里踩过的最冤枉的一次坑——当时作业在一个节点能跑,换台机器就报 HDFS 地址解析不到。

三台机器的内存建议不低于 32G,磁盘用 SSD 更好,因为 Spark 的 shuffle 和 HDFS 的本地盘读写量都很大。如果是虚拟机,注意别把 CPU 超分得太狠,Executor 的核数是按逻辑核算的,超分过度会导致 GC 时间飙升,但 YARN 不会告警,只会表现为作业莫名其妙变慢。

2. 系统底座:装机后第一小时要做的十几件事

装 CM 之前的准备工作,是整件事里性价比最高的部分。这些工作做得扎实,后面 CM 里那一排健康检查基本都是绿的;做得马虎,后面会以各种你意想不到的方式报错,而且报错信息通常不指向真正的原因。我个人的经验是,这十几件事里任何一件漏做,平均会多花两到三个小时去排查。

2.1 主机名、hosts、时钟,这三件小事决定后面顺不顺

主机名必须是永久主机名并且能被所有节点互相解析。用hostnamectl set-hostname node01设置,然后检查hostname -f返回的是完整域名而不是localhost。如果返回localhost.localdomain,说明/etc/hosts里没写对应的条目,后面 CM 建集群时会直接失败,而且失败信息很含糊。

/etc/hosts里每一行都要写成“IP + FQDN + 短名”的形式,比如:

10.10.10.11 node01.example.internal node01 10.10.10.12 node02.example.internal node02 10.10.10.13 node03.example.internal node03

为什么强调这个?因为 CM 的 Agent 在向 Server 注册时,上报的是自己的主机名,Server 会拿这个主机名去反查 IP。如果 DNS 反解不出来,Server 会认为这台机器不可达,界面上表现为“主机不可用”,但ping又是通的,很容易误判成网络问题。

时钟同步用 chrony,三个节点都指向同一个内网时间源。CM 的健康检查里有一项“时钟偏移”,默认阈值我记得是 3 秒,超过就报红。这个检查看起来只是黄灯,但时间不同步会真实影响 Kerberos(如果开了安全认证)、YARN 的容器时间戳、以及作业日志的时间线对齐,排查问题时会让你非常痛苦。

yum install -y chrony systemctl enable chronyd && systemctl start chronyd chronyc sources -v # 确认有一个带 * 的同步源

2.2 内核与资源限制:swappiness、THP、文件句柄

这一组参数直接决定 Spark 作业在高负载下的稳定性,不是可选项。

第一是vm.swappiness。Linux 默认 60,意味着内存有压力时倾向于把匿名页换到 swap。Spark 的 Executor 堆内存很大,一旦被换出,GC 时的缺页中断会让整个容器停顿几百毫秒甚至更久,表现出来就是“作业卡住了,但 CPU 不高”。设成 1,尽量只用 swap 应对紧急情况。

第二是透明大页(THP)。THP 的khugepaged内核线程会在后台合并大页,合并期间引发内存分配延迟抖动。对 JVM 来说这种抖动是致命的,因为 JVM 有自己的堆管理策略,和内核的大页合并互相不知道对方在干什么。Hadoop 和 Spark 的官方建议都是关掉:

echo never > /sys/kernel/mm/transparent_hugepage/enabled echo never > /sys/kernel/mm/transparent_hugepage/defrag

注意这两条命令是临时的,重启就失效。要持久化,写在/etc/rc.local里并给执行权限,或者做成 systemd 服务。用 tuned 的话,也可以自定义 profile,但 rc.local 更直白,我更倾向后者。

第三是文件句柄和进程数。HDFS 的 DataNode、YARN 的 NodeManager 都是多线程模型,一个节点上跑几百个容器很常见,每个容器都要占文件描述符。默认的 1024 完全不够:

# /etc/security/limits.conf 追加 * soft nofile 65536 * hard nofile 65536 * soft nproc 65536 * hard nproc 65536

改完必须重新登录或者重开 session 才生效,ulimit -n验证一下。这里有个细节:如果你用 systemd 启动服务,limits.conf对 systemd 服务不一定生效,还需要在服务的 unit 里配LimitNOFILE。不过 CM 安装的服务默认已经带了足够的限制,一般不用额外改。

内核网络参数这些,我在/etc/sysctl.conf里放一组常用值:

vm.swappiness = 1 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 15 net.ipv4.tcp_keepalive_time = 1200 net.ipv4.ip_local_port_range = 1024 65000 net.core.somaxconn = 4096 net.ipv4.tcp_max_syn_backlog = 8192

sysctl -p生效。这几个参数主要是为了应对大量短连接场景,比如 Spark 的 shuffle 阶段会建立大量 TCP 连接,端口范围窄了会出现Cannot assign requested address。

2.3 安全策略与防火墙的内网取舍

SELinux 建议直接关掉,或者至少设成 permissive 模式。CDH 的某些组件在 enforcing 模式下会出现目录权限被拒绝的问题,尤其是 Parcel 解压到/opt/cloudera/parcels以及 HDFS 的本地数据目录,表现是服务启动失败但日志里只写 permission denied,很难第一时间联想到 SELinux。

setenforce 0 sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config

防火墙这块要说清楚取舍。如果集群完全在隔离内网,直接把 firewalld 停掉是最省事的:

systemctl stop firewalld && systemctl disable firewalld

如果必须保留防火墙,那你得把 CM 的端口清单开全,包括 7180(CM Web UI)、7182(Agent 与 Server 通信)、8020、9870、8088、8032、2181、9083、10000、18088、21050、16010 等等。这个清单很长,漏一个就会导致某个健康检查红。我个人的做法是:内网环境一律关防火墙,用安全组或者网络层的访问控制来代替,比在每台机器上维护 iptables 规则靠谱得多。

3. JDK 与元数据库:CM 和 Hive 的地基不能省

这一节是整个安装过程里最像“传统运维”的部分,也是最不能偷懒的部分。元数据库配错了,CM 装到一半会失败;JDK 装错了,装完了也起不来。而且这两样东西一旦装好,后面基本不会再动,属于一次投入长期受益。

3.1 JDK 8 的安装位置与 JAVA_HOME 的统一

我习惯把 JDK 放在/usr/java/jdk1.8.0_xxx这个路径下,因为 CM 的默认探测路径就包含/usr/java,它会自动扫描并列出可用的 JDK。如果用其他路径,安装向导里需要手动填,多一步操作就多一次出错机会。

所有节点装同一个版本,装完之后做一个软链接/usr/java/default指向实际目录,然后统一设置环境变量:

export JAVA_HOME=/usr/java/default export PATH=$JAVA_HOME/bin:$PATH

把这两行写进/etc/profile.d/java.sh,这样所有用户登录都会加载。注意不要只写在~/.bash_profile里,因为 CM 的 Agent 是用 root 或者某个 sudo 用户启动服务的,非登录 shell 不会读你的用户级配置,最终会导致服务启动时找不到 JAVA_HOME。这个坑我见过太多次,症状是 CM 界面上所有服务的“Java 主目录”都显示为红色感叹号。

装完验证一下:java -version输出 1.8,echo $JAVA_HOME路径正确,而且在 sudo 环境下也要验证一遍:sudo -i java -version。

3.2 MySQL 的参数调优与建库脚本

CM 自己的配置数据、Hive 的元数据、Oozie 的调度数据、Hue 的数据,都需要一个关系型数据库。生产环境一律用 MySQL 或者 PostgreSQL,绝对不要用内置的 Derby——Derby 是单连接的,HiveServer2 和 spark-sql 同时连就会锁死,而且数据丢了没法恢复。

MySQL 建议 5.7,8.0 也能用但要额外注意用户创建和授权语法的差异。装完之后改/etc/my.cnf,几个关键参数:

[mysqld] transaction-isolation = READ-COMMITTED max_connections = 550 max_allowed_packet = 32M thread_cache_size = 64 innodb_flush_method = O_DIRECT innodb_log_file_size = 256M binlog_format = mixed character-set-server = utf8

transaction-isolation必须是 READ-COMMITTED,这是 CM 的硬性要求,用默认的 REPEATABLE-READ 会导致某些并发场景下元数据操作出现间隙锁,严重的会死锁。max_connections给到 550 是因为一个中等规模的集群里,HiveServer2、Metastore、Oozie、CM 加起来的连接池会占掉不少。

注意:MySQL 5.7 的query_cache系列参数虽然文档里还在提,但实际收益极小,而且 8.0 已经彻底移除,如果你照着老文档抄了query_cache_size之类的配置,在 8.0 上会直接启动失败。我建议这套直接不写。

建库我一般用一段脚本一次建完,避免后面装服务时反复回来加:

CREATE DATABASE scm DEFAULT CHARACTER SET utf8; CREATE DATABASE metastore DEFAULT CHARACTER SET utf8; CREATE DATABASE oozie DEFAULT CHARACTER SET utf8; CREATE DATABASE hue DEFAULT CHARACTER SET utf8; CREATE DATABASE sentry DEFAULT CHARACTER SET utf8; CREATE USER 'scm'@'%' IDENTIFIED BY 'Scm_Pass_2024'; CREATE USER 'hive'@'%' IDENTIFIED BY 'Hive_Pass_2024'; CREATE USER 'oozie'@'%' IDENTIFIED BY 'Oozie_Pass_2024'; CREATE USER 'hue'@'%' IDENTIFIED BY 'Hue_Pass_2024'; GRANT ALL PRIVILEGES ON scm.* TO 'scm'@'%'; GRANT ALL PRIVILEGES ON metastore.* TO 'hive'@'%'; GRANT ALL PRIVILEGES ON oozie.* TO 'oozie'@'%'; GRANT ALL PRIVILEGES ON hue.* TO 'hue'@'%'; FLUSH PRIVILEGES;

MySQL 8 的用户要先 CREATE 再 GRANT,不能用 GRANT 语句里带 IDENTIFIED BY 的老写法,这是 8.0 的一个明确的语法变更。如果你的环境是 8.0,上面这段要拆成两步写。

另外注意字符集。Hive 的元数据里存表名、列名、注释,如果字符集是 latin1,中文注释会乱码,虽然后来能修,但修起来很麻烦。建库时显式指定 utf8,一劳永逸。

3.3 scm_prepare_database.sh 到底做了什么事

CM 提供了scm_prepare_database.sh这个脚本,装完cloudera-manager-server包之后在/opt/cloudera/cm/schema/目录下。很多人照着文档敲一遍就过去了,其实值得搞清楚它干了什么。

它做两件事:一是把连接信息写进/etc/cloudera-scm-server/db.properties,二是执行 CM 的建表 SQL。执行完之后,CM Server 启动时读这个 properties 文件连库,如果连不上就反复重试然后退出。

用法:

/opt/cloudera/cm/schema/scm_prepare_database.sh mysql \ -h node01 -P 3306 \ --scm-host node01 \ scm scm 'Scm_Pass_2024'

前面几项是数据库类型、地址端口,--scm-host指定 CM Server 所在的主机,后面三个依次是库名、用户名、密码。执行成功会打印一段All done, your SCM database is configured correctly!。

如果报Thu Jun 06 10:00:00 CST 2024 MainThread ERROR driver not found,说明 MySQL 的 JDBC 驱动没放进 CM 的 lib 目录。驱动文件要放在/usr/share/java/mysql-connector-java.jar,这是 CM 约定的路径。放好之后重新执行即可。这个错误信息很不直观,我第一次遇到的时候查了半天以为是权限问题。

4. CM Server 与 Agent:从 repo 到集群上线

到这一步,机器层面的东西都齐了,可以开始装 CM。CM 是典型的 Server-Agent 架构:Server 是管理者,只有一个,负责 Web UI、配置下发、状态汇总;Agent 装在每个节点上,负责执行 Server 下发的命令、上报心跳、拉起和停止服务进程。这个架构决定了,只要 Agent 和 Server 的通道断了,Server 上看到的就是一片红,但实际上服务可能还在正常跑。

4.1 yum 源与三个包的安装顺序

CM 相关的包有三个:cloudera-manager-daemons、cloudera-manager-server、cloudera-manager-agent。顺序上,先在所有节点装 daemons 和 agent,最后只在 Server 节点装 server。

cloudera-manager-daemons是基础包,安装时会创建cloudera-scm用户、/opt/cloudera 目录结构、以及很多系统级的配置。它必须最先装,因为另外两个包都依赖它。cloudera-manager-agent装完会自动注册为 systemd 服务,但先别启动,等 Server 起来之后再统一启动 agent。

yum 源用内网镜像或者本地仓库。如果你的环境无法访问外网,需要在能访问的机器上下载全量 rpm 和 parcel,然后搭一个本地 http 服务:

# 简单粗暴的做法 cd /var/www/html python3 -m http.server 8080

然后每台机器的/etc/yum.repos.d/cm.repo指向这个地址。这种搭法在测试环境很好用,生产环境建议还是用正式的制品仓库。

装完之后确认一下服务状态:

systemctl status cloudera-scm-agent # 应该显示 inactive (dead) 或者刚装完还没配置 ls /opt/cloudera/ # 应该有 parcels、parcel-repo、cm 等目录

如果/opt/cloudera/parcel-repo不存在,手工创建并确认权限是cloudera-scm:cloudera-scm。

4.2 Parcel 本地仓库的目录结构与 sha 命名坑

Parcel 是 CDH 分发组件的方式,本质上是一个压缩包加上一个校验文件,CM 从仓库下载、校验、解压、激活。用本地仓库的原因是:集群规模一上去,几十个节点同时从外网下载几个 G 的包,速度和稳定性都是问题。

你需要准备三个文件放到/opt/cloudera/parcel-repo/:

CDH-6.2.0-1.cdh6.2.0.p0.967373-el7.parcel CDH-6.2.0-1.cdh6.2.0.p0.967373-el7.parcel.sha manifest.json

这里有个经典坑:CM 认的校验文件后缀是.sha,但很多下载源给的是.parcel.sha1或者.sha1,直接放进去 CM 不认,会一直显示“正在下载”然后失败,界面上只有一句“Hash 验证失败”。你需要把文件名改成<parcel完整文件名>.sha,注意.sha前面的名字必须和 parcel 文件名完全一致,包括版本号和 build 号。

还有一个更隐蔽的坑:manifest.json必须和 parcel 是同一批次的。如果你从不同版本拼凑,CM 会认为这个 parcel 不合法而拒绝分发。我一般是从同一个下载源把三个文件一起拿下来,改完名之后再核对一遍文件名。

目录权限也要注意,必须让cloudera-scm用户可读,否则 CM Server 读 manifest 会失败:

chown -R cloudera-scm:cloudera-scm /opt/cloudera/parcel-repo chmod 755 /opt/cloudera/parcel-repo

4.3 向导里的 SSH 凭据与 agent 心跳

CM Server 起来之后,浏览器访问http://node01:7180,默认账号密码都是 admin。进去之后是安装向导,第一步会让你填一组 SSH 凭据,用来向其他节点分发 agent 包和启动 agent。

这一步用 root 或者有 sudo 权限的用户都行,但要注意:Server 节点必须能免密 SSH 到所有其他节点,包括它自己。如果你从来没配过免密,先配好:

ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa ssh-copy-id node02 ssh-copy-id node03 ssh node02 'hostname' # 验证一下

向导里填的凭据会被用来登录每台机器执行安装命令。如果这里报“SSH 连接失败”,先手工在 Server 上ssh node02试一下,能通就是密码填错了,不能通就是免密没配对。这个环节报错率很高,我一般会在点下一步之前先在三个节点之间互相 ssh 一遍。

Agent 启动之后,会主动连 Server 的 7182 端口。如果主机列表里一台机器一直是“未收到心跳”,排查顺序是:先看这台机器上systemctl status cloudera-scm-agent,再看/var/log/cloudera-scm-agent/cloudera-scm-agent.log,最后用telnet node01 7182确认端口通不通。多数情况是 Server 上没起来,或者 firewall 挡住了。

5. 角色分配:Spark 要跑起来依赖哪些服务

向导走到角色分配这一步,界面会列出一大堆服务让你勾。很多人的做法是全勾,结果装上十来个服务,内存被吃掉一半,Spark 反而没资源跑了。我的原则是:先装最小可用集合,跑通之后再按需加。

5.1 最小可用服务集与 Gateway 角色的意义

要让 Spark 能跑,必须有的服务是:ZooKeeper、HDFS、YARN、Hive。这四个是铁三角加一个。

ZooKeeper 提供分布式协调,HDFS 的 HA 和 YARN 的 HA 都依赖它。HDFS 是存储底座,Spark 读写的文件、事件日志、以及后面要上传的 Spark jar 都在上面。YARN 是资源调度,Spark 的 Executor 全靠它分配。Hive 主要是为了让 Spark SQL 能读写元数据,用spark-shell直接写spark.sql("select * from xx")的时候,元数据是从 Hive Metastore 拿的。

可选的服务是 HBase、Impala、Oozie、Hue、Sqoop,这些按业务需要勾。如果只是想验证 Spark 环境,先把这些去掉,等集群稳定了再装,能省下好几个 G 的内存和一堆健康检查的红灯。

Gateway 角色的分配在向导里容易漏。它在角色分配界面里是一个独立的角色类型,需要显式地指定到每台机器上。指定之后,CM 会把这个节点需要的客户端配置写到/etc/spark/conf、/etc/hadoop/conf这些目录下。没有 Gateway 的节点,直接跑 spark-submit 会连不上 HDFS 和 YARN,因为 config 文件根本不在这台机器上。

5.2 YARN 侧的 shuffle service 与资源上限

Spark 跑在 YARN 上,Executor 之间的数据交换(shuffle)走的是外部 shuffle service,不是 Executor 之间直接传。这是因为动态资源分配要求 Executor 可以被回收,而回收之后它写出去的数据还得能被人读到,所以必须有一个独立于 Executor 生命周期的服务来接管。

配置在 YARN 服务里,yarn-site.xml的安全阀(Safety Valve)加上:

<property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle,spark_shuffle</value> </property> <property> <name>yarn.nodemanager.aux-services.spark_shuffle.class</name> <value>org.apache.spark.network.yarn.YarnShuffleService</value> </property>

CDH 的默认配置里通常已经包含这两项了,但值得确认一遍,尤其是从老集群迁移过来的场景。改完要重启所有 NodeManager。

然后是资源上限,这两个参数直接卡住 Spark 作业的规格:

参数建议值说明
yarn.nodemanager.resource.memory-mb物理内存的 70%~80%给操作系统、DataNode、NodeManager 自身留余量
yarn.nodemanager.resource.cpu-vcores逻辑核数或略少不要按超线程数写满
yarn.scheduler.maximum-allocation-mb单个大 Executor 的上限建议设为单节点可分配内存的 1/2 到 1/4
yarn.scheduler.maximum-allocation-vcores一般 8 到 16超过这个数提交会被拒

举个例子,一台 64G 内存、16 核的机器,我一般设resource-memory-mb = 49152(48G),resource-vcores = 14,maximum-allocation-mb = 16384(16G)。这样单个 Executor 最大 16G,能容纳一个比较大的 container,同时留出足够的余量给系统和其他守护进程。

maximum-allocation-mb设得太小是最常见的配置错误。有人按默认值 8192 不动,然后在 spark-submit 里写--executor-memory 12g,作业直接被拒,报错信息里只有一句InvalidResourceRequestException,看不到具体是哪个参数不够。

5.3 Spark 服务的几项关键配置:eventLog、history、动态分配

进入 Spark 服务的配置页面,有几项需要改:

第一组是事件日志,没有它就没法在 History Server 里回看作业:

spark.eventLog.enabled=true spark.eventLog.dir=hdfs://node01:8020/user/spark/applicationHistory spark.eventLog.compress=true

spark.eventLog.dir指向的目录必须提前建好并给对权限,Spark 服务启动时不会自动创建:

sudo -u hdfs hdfs dfs -mkdir -p /user/spark/applicationHistory sudo -u hdfs hdfs dfs -chown -R spark:hadoop /user/spark sudo -u hdfs hdfs dfs -chmod -R 1777 /user/spark/applicationHistory

第二组是 History Server 的读取路径,必须和上面的写入路径一致:

spark.history.fs.logDirectory=hdfs://node01:8020/user/spark/applicationHistory spark.history.ui.port=18088

第三组是动态资源分配,这个在共享集群里几乎是必开的:

spark.dynamicAllocation.enabled=true spark.shuffle.service.enabled=true spark.dynamicAllocation.minExecutors=1 spark.dynamicAllocation.maxExecutors=50 spark.dynamicAllocation.initialExecutors=2 spark.dynamicAllocation.executorIdleTimeout=60s

spark.shuffle.service.enabled必须配合前面 YARN 那边的spark_shuffle才有意义。开了动态分配的好处是作业空闲时会把 Executor 还回去,集群整体利用率高很多;坏处是如果 shuffle service 没配好,会出现“Executor 被回收后,下一个 stage 读不到数据”的诡异报错。

第四组是把 Spark 的 jar 提前传到 HDFS,避免每次提交作业都从本地往上拷:

sudo -u hdfs hdfs dfs -mkdir -p /user/spark/share/lib/spark2/jars PARCEL=/opt/cloudera/parcels/CDH/lib/spark/jars sudo -u hdfs hdfs dfs -put $PARCEL/* /user/spark/share/lib/spark2/jars/

然后在 Spark 配置里加上:

spark.yarn.jars=hdfs://node01:8020/user/spark/share/lib/spark2/jars/*

这一步在作业提交频繁的场景里收益非常明显,尤其是客户端和集群不在同一个机房的时候,每次上传几十兆的 jar 会让作业启动时间多出十几秒。

6. 提交第一个作业:从 spark-shell 到 spark-submit 的参数账

配置改完、服务重启完,别急着写业务代码,先跑通一个最简的作业。这一步的目的是验证“客户端配置 → YARN 资源申请 → Executor 启动 → 结果回传”这条完整链路,链路上任何一个环节有问题,跑个 SparkPi 就能暴露出来。

6.1 Executor 内存与 container 数量的手算方法

先说清楚 Spark on YARN 里两个容易混淆的概念:--executor-memory是 Executor 的 JVM 堆内存,但 YARN 分配 container 时用的不是这个值,而是executor-memory + memoryOverhead。memoryOverhead 默认是max(384MB, 0.1 × executor-memory)。

所以一个--executor-memory 4g的 Executor,实际占用的 container 是 4G + 400M ≈ 4.4G,YARN 会按 4.4G 向上取整到调度单位的整数倍。

单节点能起几个 Executor,算法是:

单节点可分配内存 ÷ 单个 container 内存 = Executor 数量

假设yarn.nodemanager.resource.memory-mb = 49152(48G),--executor-memory 8g,overhead 是 800M,那么单个 container ≈ 8.8G,48 ÷ 8.8 ≈ 5 个。

但还要看核数。--executor-cores 4的话,5 个 Executor 要 20 个核,超过节点的 14 个 vcore,实际只能起 3 个(12 核)。所以真正的瓶颈往往是核数而不是内存,这两个维度必须同时算。

我一般会按下面的方式过一遍:

项目数值计算依据
节点内存64G物理
YARN 可分配48G留 25% 给系统和服务
executor-memory8G业务数据量决定,一般 4~12G
memoryOverhead800M8G × 10%
单 container8.8G向上取整
内存维度可容纳5 个48 ÷ 8.8
executor-cores4一般 4~5
vcore 维度可容纳3 个14 ÷ 4
实际每节点3 个取小值

三个节点就是 9 个 Executor,共 72G 内存、36 个核。这个数字就是你提交作业时可以写--num-executors的上限参考。

6.2 客户端模式与集群模式的区别与选择

spark-submit有两个部署模式,client和cluster。在 CDH 里这个选择有实际意义,不是随便选。

client模式下,Driver 跑在你执行 spark-submit 的那台机器上,Executor 在 YARN 的容器里。好处是日志直接打在终端上,调试特别方便;坏处是如果这台机器和集群之间的网络不稳定,Driver 和 Executor 通信断了作业就挂了,而且这台机器本身的位置决定了它能不能访问 HDFS 的某些路径。

cluster模式下,Driver 也在 YARN 的容器里,客户端提交完就可以关掉。适合生产环境的定时任务,但如果作业失败,日志要通过yarn logs -applicationId去看,稍微麻烦一点。

我的习惯是:调试用 client,上线用 cluster。第一次跑通验证链路时用 client,因为能看到完整的启动日志,哪一步卡住一目了然。

跑一个 SparkPi:

spark-submit \ --master yarn \ --deploy-mode client \ --class org.apache.spark.examples.SparkPi \ --executor-memory 2g \ --executor-cores 2 \ --num-executors 2 \ /opt/cloudera/parcels/CDH/lib/spark/examples/jars/spark-examples_2.11-2.4.0-cdh6.2.0.jar \ 100

跑成功会输出Pi is roughly 3.14159...。如果卡在Application report for application_xxx (state: ACCEPTED)很久,说明资源不够,去 YARN 的 8088 界面看队列是不是满了,或者maximum-allocation-mb卡住了。

6.3 History Server 里到底该看什么

作业跑完之后,打开http://node01:18088,能找到刚才那次运行。很多人只是点进去看一眼状态就关了,其实这里的信息量很大,排错的时候是主要证据来源。

第一个要看的是Stages 页面,每个 stage 的 Shuffle Read/Write 大小、Spill 大小、GC 时间。如果某个 stage 的 Spill 特别大,说明内存不够,要么加 executor-memory,要么减少单分区数据量。GC 时间占比超过 10% 就要警惕,说明堆内存压力大。

第二个是Executors 页面,能看到每个 Executor 的任务分布。如果任务严重倾斜,某个 Executor 跑了几百个任务其他的只有几个,那是数据倾斜,需要改 key 或者加随机前缀。

第三个是Environment 页面,这里会列出作业实际生效的所有配置。有时候你在 spark-defaults.conf 里改了一个参数没生效,来这里一看就知道,因为这里展示的是最终合并后的值,优先级从高到低是:命令行参数 > 代码里 set > spark-submit 的 --conf > spark-defaults.conf > CDH 下发的默认值。

第四个是SQL 页面(如果是 DataFrame 作业),能看到物理执行计划,判断有没有走 BroadcastJoin、有没有发生全表扫描。

7. 上线前的验证清单与几次真实的排错记录

环境装完、Demo 跑通,不等于这套环境可以直接交付。我一般会走一遍固定的验证清单,把每个环节独立验证一遍,避免“能跑但跑得不对”的状态被带到生产。

7.1 一份可以照着执行的验证命令表

验证项命令期望结果
HDFS 可用hdfs dfs -ls /正常列出目录
HDFS 写入hdfs dfs -put /etc/hosts /tmp/无报错
YARN 可用yarn node -list所有 NodeManager 状态 RUNNING
YARN 队列yarn queue -status default显示队列容量
Spark 连 HDFSspark-shell里spark.read.text("/tmp/hosts").count()返回行数
Spark SQL 连 Hivespark.sql("show databases").show()至少列出 default
History Server浏览器开 18088能看到刚才的作业
Metastore 连通beeline -u "jdbc:hive2://node01:10000/default"能进交互界面

最后一项如果连不上,先确认 HiveServer2 是不是启动了(10000 端口),再确认 Metastore 的 9083 端口通不通。这两个是不同的服务,经常有人混为一谈。

7.2 几个高频报错的定位顺序

第一种:提交作业报Application rejected by queue placement policy。这不是资源问题,是队列权限问题。去 YARN 的 Scheduler 页面看这个用户对 default 队列有没有提交权限,或者看yarn.scheduler.capacity.root.default.acl_submit_applications这个参数。

第二种:作业跑到一半Container killed by YARN for exceeding memory limits。这是 container 的物理内存(堆 + overhead + 堆外)超了,不是堆内存不够。多数情况是memoryOverhead设小了,尤其是用了 PySpark 或者有大量 off-heap 操作的场景。解决办法是把spark.yarn.executor.memoryOverhead从默认的 10% 提到 15%~20%,而不是无脑加大 executor-memory。

第三种:History Server 里看不到刚跑完的作业。排查顺序是:先确认spark.eventLog.enabled是不是真的为 true(去 Environment 页面看),再看 HDFS 上/user/spark/applicationHistory目录下有没有新的文件,再看 History Server 的日志有没有权限错误。最常见的根因是 spark 用户对这个目录没有写权限,因为手工创建目录时 owner 设错了。

第四种:spark-shell启动报ClassNotFoundException: org.apache.hadoop.hive.conf.HiveConf。这说明客户端的 classpath 里缺 Hive 的 jar,通常是因为这台机器上没装 Gateway 角色,或者装了但 Spark 的配置没重新下发。

7.3 我个人的配置备份与变更习惯

最后分享一个我觉得最有价值的习惯:每次在 CM 界面改配置之前,先把当前生效的配置文件导出一份。CM 有个“导出客户端配置”的功能,也有/etc/spark/conf、/etc/hadoop/conf这些目录可以直接打包。改坏了能立刻对比回归。

另外,CM 的配置变更历史是可以回滚的,但回滚之后需要重启服务才生效,而且不是所有参数都能热加载。我一般会在变更记录里写清楚“改了什么、为什么改、改完观察到什么现象”,比如“把 max-allocation-mb 从 8192 调到 16384,因为 12G 的 Executor 提交被拒”。三个月后回头查问题,这几行笔记比日志有用得多。

还有一个细节:CDH 6.2 这套东西已经不算新了,新项目在评估选型时要考虑清楚生命周期。但如果你的团队手上就有一套存量环境要维护,把它摸透、把上面这些底座参数和资源上限算明白,比盲目升级要实在得多。毕竟集群稳不稳定,往往不取决于版本号,而取决于那几十个参数有没有人认真对过一遍。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/27 19:38:16

Agent-Reach:AI Agent统一触达层,搞定消息、系统与数据连接

做AI Agent开发这段时间&#xff0c;我最大的感受是&#xff1a;让模型开口说话容易&#xff0c;让模型真正动手干活难。市面上各种Agent框架层出不穷&#xff0c;但真到了接企业IM、调内部系统、跑定时任务的时候&#xff0c;大家又都回到同一条起跑线——逐个对接、各自为战。…

作者头像 李华
网站建设 2026/10/27 19:38:05

DS初阶三大排序:插入、希尔、选择的核心原理与工程取舍

1. 项目概述&#xff1a;为什么这三个排序必须放在初阶第一课讲&#xff1f;“DS初阶&#xff1a;八大排序之直接插入排序、希尔排序和选择排序”——这个标题乍看平平无奇&#xff0c;但如果你带过数据结构课、辅导过算法入门者&#xff0c;或者自己当年被排序卡在第一章超过两…

作者头像 李华
网站建设 2026/10/27 19:37:32

Phong光照模型原理与实时渲染优化实践

## 1. Phong光照模型基础解析1975年由Bui Tuong Phong提出的Phong模型&#xff0c;至今仍是计算机图形学中最经典的光照计算方法。这个模型之所以能经久不衰&#xff0c;核心在于它用相对简单的数学公式&#xff0c;实现了对物体表面光照现象的合理近似。### 1.1 模型组成要素P…

作者头像 李华
网站建设 2026/10/27 19:36:47

元宇宙金融治理:多模态AI构建虚拟经济自愈系统

1. 项目背景与核心价值当虚拟与现实边界逐渐模糊的今天&#xff0c;元宇宙正在重塑金融服务的形态。作为从业十余年的技术架构师&#xff0c;我见证了AI如何从实验室走向产业核心。这次要分享的是我们在某跨国金融集团落地的元宇宙治理方案——通过多模态AI构建的智能中枢&…

作者头像 李华
网站建设 2026/10/27 19:32:27

二维数组与函数:编程基础与性能优化实战

1. 二维数组与函数的基础概念解析 在编程世界中&#xff0c;二维数组和函数是两个最基础也最重要的概念。二维数组就像是Excel表格&#xff0c;有行有列&#xff0c;可以整齐地存放数据&#xff1b;而函数则像是工厂里的机器&#xff0c;接收原料&#xff08;参数&#xff09;&…

作者头像 李华
网站建设 2026/10/27 19:32:12

RPC实战指南:序列化、传输层与跨语言一致性避坑

1. 项目概述&#xff1a;这不是教科书里的RPC&#xff0c;而是我在三个真实系统里亲手调通、压测、重构过的RPC实践“远程过程调用”这六个字&#xff0c;听上去像教科书里一个安静的章节标题&#xff0c;但只要你真正把它放进生产环境跑过一次——比如凌晨两点收到告警&#x…

作者头像 李华