☰
CDH 6.2 Spark集群搭建:版本矩阵、基础环境与调优验证
2026/10/27 11:24:56 网站建设 项目流程

1. 环境整体设计与版本矩阵梳理

CDH6.2 装 Spark 集群基础环境这件事,说难不难,说简单也真不简单。我见过太多人卡在"明明机器都能 ping 通、端口都开着,就是 spark-submit 报类找不到"这种问题上,折腾一整天最后发现是某个节点的 JDK 小版本不一致。所以这篇内容我不打算从"点下一步"讲起,而是先把版本矩阵、节点规划、资源估算这些看起来不性感但决定成败的东西捋清楚,再往下落到实操命令。适合刚接手大数据平台搭建的同学,也适合那些已经装过一版、但集群跑着跑着总出怪问题的老手回头对照。

CDH 6.2 这个发行版对 Spark 的绑定关系是死的:它自带的是Spark 2.4.0,并且是基于 Hadoop 3.0.0 编译的。这一点必须记住,因为后面所有关于 Hadoop 版本、Scala 版本、Guava 依赖的判断都从这里出发。你从外部拿一个 Spark 2.4.5 的包塞进去,理论上能跑,但和 CM(Cloudera Manager)托管的配置分发机制是两套逻辑,出问题排查起来会让你怀疑人生。我的建议是:服务端用发行版自带的 parcel,客户端工具版本与之严格对齐,不要自作聪明地"顺手升个级"。

1.1 为什么必须先锁版本再动手

很多人装集群的习惯是"先把系统装好,再去官网下最新包",这个顺序在 CDH 场景下是反的。原因有三层。第一层,CDH 6.2 依赖JDK 8,而且对 8u 的具体小版本有要求,官方推荐 8u141 以上,实测 8u181、8u202 都比较稳,8u302 之后的某些版本在 Kerberos 相关类上有行为变化,非安全集群感知不到,一旦你后面开 Kerberos 就会踩。第二层,Hadoop 3.0.0 的内部依赖里 Guava 是 11.0.2,而 Spark 2.4.0 编译时用的是 Guava 14,CM 会在 classpath 顺序上做处理,但如果你自己往/opt/cloudera/parcels/CDH/lib/spark/jars里手动丢 jar 包,很容易把顺序搞乱,症状就是NoSuchMethodError或者IllegalAccessError。第三层,Python 版本,Spark 2.4 同时支持 Python 2.7 和 3.4+,但 PySpark 在 Python 3.7 上和 2.4.0 搭配有已知的 pickle 兼容问题,稳妥做法是节点上装 Python 3.6,或者干脆只跑 Scala/Java 作业、PySpark 单独指定PYSPARK_PYTHON。

把这三个版本钉死之后,写一份文档记录下来,所有节点严格照抄。我个人的习惯是在每台机器的/etc/version-lock.md里写清楚:OS 版本、JDK 完整版本号、Python 版本、CDH parcel 版本、MySQL 驱动版本。后面任何人扩容机器,照着这份文件装,能省掉 80% 的"新节点行为诡异"问题。

1.2 节点规划与内存资源估算

节点规划这件事,网上很多文章直接甩一句"三台起步",但不告诉你为什么、怎么分角色。我按一套典型的3 节点测试/小生产集群来讲,规格是 16 核 / 64G 内存 / 2TB 数据盘 + 500G 系统盘。角色分配上:

节点主机名承担的守护进程
node01cdh-master-01NameNode(Active)、ResourceManager、CM Server、CM DB、ZooKeeper、Spark History Server
node02cdh-worker-02DataNode、NodeManager、ZooKeeper、CM Agent、JournalNode
node03cdh-worker-03DataNode、NodeManager、ZooKeeper、CM Agent、JournalNode

注意这里我把CM Server 和元数据库放在 master,这是测试环境的省事做法,生产环境一定要拆开,CM 的监控数据写库很凶,和 NameNode 抢 IO 会让整个集群的写入延迟上去。同理,Spark History Server 也不应该和 NameNode 同机,因为它要读大量的 event log 文件,属于典型的"重读轻写"负载,跟 NameNode 的元数据操作抢页缓存。

内存怎么算?以 64G 的机器为例,OS 和基础进程留8G,CM Agent 和监控留2G,剩下的给 YARN。NodeManager 可用内存我一般配yarn.nodemanager.resource.memory-mb = 49152(48G),留 6G 缓冲。核数上yarn.nodemanager.resource.cpu-vcores = 14,留 2 核给系统和其他守护进程。然后反推 Spark executor:

单个 executor 内存 12G,overhead 按 10% 算约 1.2G,加上 container 额外开销,实际占 YARN 约 13.5G。48G / 13.5G ≈ 3.5,取 3 个 executor/节点,每个 4 核,正好 12 核,留 2 核余量。

这套数字不是拍脑袋,是"先用默认值跑起来,再看监控调"的结果。新手常犯的错是把executor.memory设成 40G 想"一次吃完",结果 GC 停顿时间直接飙到秒级,因为堆太大、老年代回收成本指数上升。经验值:单 executor 堆内存不建议超过 24G,超过就该拆成多个 executor。

1.3 系统层的前置条件清单

装之前有几件事必须做完,缺一件后面都会返工。清单如下:

  • 主机名规范化,且hostname -f能正确返回 FQDN,不能只返回短名。CM 的 Agent 心跳是靠 FQDN 注册的,短名会导致 "Agent 已存在但主机名不匹配"。
  • 所有节点的/etc/hosts内容完全一致,包含全部节点的 IP 与 FQDN 映射。
  • 关闭 SELinux(/etc/selinux/config改为disabled),并重启生效。这一条不是"可选项",SELinux 的 enforcing 模式会让 DataNode 写盘直接 Permission denied,日志里还看不出原因。
  • 时间同步。三节点集群如果不做 NTP,Kerberos 开了之后 ticket 会直接失效;即使不开,ZooKeeper 的 session 也会莫名超时。用 chronyd 就行。
  • 关闭透明大页(THP)。Hadoop 全家桶对这个都很敏感,echo never > /sys/kernel/mm/transparent_hugepage/enabled,并写进 rc.local 或 systemd unit。
  • vm.swappiness 调低到 1 甚至 0,避免内存压力下把 JVM 堆换出到磁盘。

这些操作单独看每条都很小,但它们是"集群能不能长期稳定跑"的地基。我在一个项目上见过 THP 没关,导致 NodeManager 在压力测试时出现几十秒的假死,最后靠sar和vmstat一路追才定位到。

2. 基础环境落地实操

这一节是纯手工活,命令都是可以直接抄的,但我建议你抄之前理解每条在干什么,因为不同发行版(CentOS 7 和 Ubuntu 20.04)的细节不一样,我按 CentOS 7 系写,其他系统自行转换。整个流程按"单节点配置 → 批量分发 → 统一验证"的顺序走,比一台台手敲可靠得多。

2.1 主机名、hosts 与免密登录

先在三台机器上分别执行:

hostnamectl set-hostname cdh-master-01 # 另两台分别改

然后编辑/etc/hosts,三台保持完全一致:

10.10.20.11 cdh-master-01 10.10.20.12 cdh-worker-02 10.10.20.13 cdh-worker-03

改完ping cdh-worker-02和hostname -f都要能通、能返回完整域名。这一步有个坑:某些云主机的cloud-init会在重启后重写 hosts,把 127.0.1.1 那行加回去,建议把manage_etc_hosts关掉。

免密登录方面,CM 的安装向导要求提供 root 或具备 sudo 权限的账号。测试环境我直接用 root,简化流程;生产环境建议建一个cdhadmin用户,配置NOPASSWD的 sudoers 条目,并在 CM 向导里指定这个账号。免密做法就是 master 上生成密钥对,ssh-copy-id分发到三台(包括 master 自己),然后ssh cdh-worker-02 date验证不需要输入密码。

提示:CM 向导会用你提供的账号去各节点执行命令并分发 parcel,账号必须具备无交互 sudo 权限,否则会在"检查主机"阶段报 SSH 相关错误,而且错误信息很含糊,容易误判成网络问题。

2.2 JDK 与 Python 的安装细节

JDK 我推荐用系统包管理器装 OpenJDK 8,或者手工解压 Oracle JDK 8 到/usr/java/jdk1.8.0_202。两种方式都可以,关键是三台路径一致。CM 的 Java 路径配置在"主机 → 所有主机 → Java 主目录"里,一旦某台路径不同,那台上的所有角色都启动不了。

mkdir -p /usr/java && tar -zxf jdk-8u202-linux-x64.tar.gz -C /usr/java/ echo 'export JAVA_HOME=/usr/java/jdk1.8.0_202' >> /etc/profile.d/java.sh echo 'export PATH=$JAVA_HOME/bin:$PATH' >> /etc/profile.d/java.sh source /etc/profile.d/java.sh java -version # 确认输出为 1.8.0_202

Python 这块,先确认python -V。CentOS 7 自带 2.7.5,Spark 2.4 能跑,但很多新库已经不兼容 Py2。我的做法是装 Python 3.6 并保留系统 Python 不动,然后在 CM 里给 Spark 服务配置PYSPARK_PYTHON=/usr/bin/python3.6。注意不要动系统的 python 软链,CentOS 的 yum 就是靠 python2 跑的,改坏了连 yum 都用不了。

顺手把 MySQL 客户端和 JDBC 驱动也准备好,后面配元数据库要用:

yum install -y mysql mysql-connector-java

驱动 jar 的位置很关键,CM 有个机制叫"JDBC 驱动目录",默认在/usr/share/java/,把mysql-connector-java-5.1.47.jar放进去,再在 CM 里配置"数据库驱动"路径,否则初始化元数据库时会报No suitable driver。

2.3 内核参数与文件句柄调整

Hadoop 家族对文件句柄的需求非常大,一个 DataNode 每个块一个文件句柄,几百万块就是几百万个 fd。默认 1024 完全不够。修改/etc/security/limits.conf:

* soft nofile 65536 * hard nofile 65536 * soft nproc 65536 * hard nproc 65536

同时在/etc/systemd/system.conf里也要调DefaultLimitNOFILE,因为 CentOS 7 之后服务是通过 systemd 拉起的,limits.conf 对 systemd 管理的服务不生效,这是个非常隐蔽的坑——你明明改了 limits.conf,用su -进去看ulimit -n是 65536,但 DataNode 进程的 fd 上限还是 1024,因为它是 systemd 启动的。

内核参数方面,编辑/etc/sysctl.conf:

vm.swappiness = 1 net.core.somaxconn = 1024 net.ipv4.tcp_max_syn_backlog = 1024 vm.overcommit_memory = 1 kernel.pid_max = 4194303

vm.overcommit_memory=1这条是因为 JVM 启动时会预留较大虚拟内存,默认策略在某些内存紧张的情况下会让 HBase 或 Spark 的 executor 启动失败。改完sysctl -p生效。

注意:kernel.pid_max调大是为了避免高并发时出现fork: Cannot allocate memory,这在执行大量 spark-submit 或 MapReduce 小作业时很常见,症状是进程数一到几千就起不来。

2.4 防火墙、SELinux 与时间同步

测试集群里我直接systemctl stop firewalld && systemctl disable firewalld,减少排查干扰。生产环境不建议关,而是精确放行 CM 用到的端口段,不过那是另一个话题。

SELinux 前面提过要关,这里再补一句:改完配置必须重启,setenforce 0只是临时生效,重启后又变回 enforcing,然后 DataNode 突然起不来,你会以为是配置改错了。验证方式getenforce输出Disabled才算真的关掉。

时间同步用 chrony:

yum install -y chrony systemctl enable chronyd && systemctl start chronyd chronyc tracking # 看 System time 偏差,正常应在毫秒级

三台机器的时间偏差要控制在几十毫秒内。这个要求不是洁癖,ZooKeeper 的 leader 选举对时间敏感,Kerberos 的票据默认允许 5 分钟偏差,超过就Clock skew too great。曾有一次某台虚拟机的时钟漂了 7 分钟,导致该节点上所有服务注册到 ZK 一直失败,日志里全是Session expired,排查了两小时才想到看时间。

2.5 关掉透明大页与批量分发

THP 的关闭方式:

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

写进/etc/rc.local并给执行权限。然后验证cat /sys/kernel/mm/transparent_hugepage/enabled输出里带[never]。

到这里,单节点的基础环境就干净了。我的做法是把上面所有操作写成一个init.sh,用 scp 分发到另外两台执行一遍,最后统一跑一遍校验脚本:检查hostname -f、java -version、python3.6 -V、getenforce、ulimit -n、chronyc tracking、THP 状态。全部通过再进下一步,别信"应该没问题",集群搭建里"应该"是最贵的词。

3. CDH 6.2 集群安装与 Spark 服务落地

基础环境干净之后,进入正式安装。这一步的核心思路是:元数据库先建好 → CM Server 和 Agent 装好 → 用向导纳管节点和分发 parcel → 添加 Spark 服务。顺序不能乱,尤其是元数据库,CM 启动时会去连,连不上就起不来,你在界面上什么也看不到,只能在日志里干瞪眼。

3.1 元数据库的准备与参数

CDH 6.2 需要三类数据库:CM 自身的库,以及 Hive、Oozie、Hue 等服务的库。测试环境只装 Spark 和 HDFS、YARN 的话,其实只需要 CM 库和 Hive 的 Metastore 库(Spark SQL 要读写 Hive 元数据)。建库语句如下:

CREATE DATABASE scm DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; CREATE DATABASE hive DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; CREATE USER 'cdhuser'@'%' IDENTIFIED BY 'YourStrongPass'; GRANT ALL PRIVILEGES ON scm.* TO 'cdhuser'@'%'; GRANT ALL PRIVILEGES ON hive.* TO 'cdhuser'@'%'; FLUSH PRIVILEGES;

字符集必须是utf8,不能用latin1,否则后面 Hive 里建中文注释的表会报错。另外 MySQL 5.7 之后默认的sql_mode包含ONLY_FULL_GROUP_BY,某些 CM 的查询语句会因此报错,建议在my.cnf里显式设置为:

[mysqld] transaction-isolation = READ-COMMITTED character-set-server = utf8 max_connections = 500

maximum-connections调高是因为 CM 的监控采集会开不少连接,默认 151 在小集群就够,但一旦你装了多个服务,很容易撞到Too many connections。

3.2 离线仓库与 parcel 分发的取舍

CM 的安装方式有两种:在线 repo 和离线 parcel。只要不是完全隔离的内网环境,我强烈建议离线 parcel。原因很实际:在线模式下yum会去访问外部仓库,内网防火墙策略一变、或者仓库地址调整,安装就会卡在"正在安装软件包"半小时然后失败,而且失败后残留的包状态很难清理,往往要重装系统。离线 parcel 就是把CDH-6.2.0-1.cdh6.2.0.p0.967373-el7.parcel和对应的.sha文件放到 master 的/opt/cloudera/parcel-repo/下,CM 会自动校验并分发,行为完全可控。

parcel 文件大约 2G,.sha文件是它的校验和。这里有个必踩的坑:.sha文件名必须是 parcel 名加.sha,例如CDH-6.2.0-1.cdh6.2.0.p0.967373-el7.parcel.sha,而且内容里要带正确的 SHA1 值,不能是空文件。CM 是靠这个文件判断 parcel 是否完整的,写错了它会一直显示"正在下载"或者"校验失败"。

同时把 CM 的 rpm 包准备好:

yum install -y cloudera-manager-daemons-6.2.0-968364.el7.x86_64.rpm \ cloudera-manager-agent-6.2.0-968364.el7.x86_64.rpm \ cloudera-manager-server-6.2.0-968364.el7.x86_64.rpm

Server 只在 master 装,Agent 三台都装。

3.3 CM Server 初始化与 Agent 注册

Server 装完后初始化元数据库:

/opt/cloudera/cm/schema/scm_prepare_database.sh mysql scm cdhuser YourStrongPass

这条命令会创建 CM 需要的一系列表,成功会输出 "All done, your SCM database is configured correctly!"。如果报驱动找不到,就回去检查 2.2 节的 JDBC 驱动目录。

然后启动 server 和 agent:

systemctl start cloudera-scm-server systemctl start cloudera-scm-agent

Server 首次启动比较慢,要等 1 到 2 分钟,日志在/var/log/cloudera-scm-server/cloudera-scm-server.log,看到Started Jetty server才算好。浏览器访问http://cdh-master-01:7180,默认账号密码都是admin。

接着用向导"为集群安装 Cloudera Manager Agent",填主机名列表,填账号密码,CM 会通过 SSH 去各节点装 Agent。这一步失败率最高,九成是 SSH 免密没配好、sudo 权限不够、或者某台机器上已经装了旧 Agent 导致端口冲突(7182)。排查方法是在 master 上手动ssh cdh-worker-02 'sudo -n true'看是否无交互成功。

3.4 向导配置:从 parcel 到服务分配

纳管成功后进入 parcel 分发。选择"仅使用本地 parcel"或者把 URL 指向 master 的 7180 仓库,CM 会把 parcel 分发到三台并解压。解压完之后在/opt/cloudera/parcels/下能看到CDH软链,指向版本目录,这一步完成意味着所有 Java 组件都就位了,包括 Spark。

服务分配时,Spark 本身不需要独立守护进程,它依赖三个东西:HDFS(存数据)、YARN(跑计算)、Hive Metastore(存表结构)。所以添加 Spark 服务之前,HDFS、YARN、ZooKeeper、Hive 必须已经正常。我在小集群上的角色安排是:

角色所在节点说明
NameNodecdh-master-01单机部署,非 HA
DataNodecdh-worker-02/03两个副本节点
ResourceManagercdh-master-01与 NN 同机,测试环境可接受
NodeManagercdh-worker-02/03计算节点
Hive Metastorecdh-master-01依赖 MySQL
Spark History Servercdh-worker-02避免和 NN 抢 IO
网关角色三台全部供 spark-submit 使用

HDFS 的副本数,两 DataNode 的情况下默认 3 会一直处于 under-replicated 状态,必须改成 2,否则界面上一片黄条看着糟心,还会影响写入性能。这个参数在hdfs-site.xml的dfs.replication,通过 CM 改并重启。

3.5 Spark 服务添加与网关配置落地

添加 Spark 服务时,CM 会让你指定History Server 存储目录,填hdfs://nameservice/spark-history或hdfs://cdh-master-01:8020/spark-history。非 HA 环境就用后者,注意端口是 8020,写错了 History Server 会启动失败,日志里是Connection refused。

然后是Spark 网关角色,这个概念很多新手搞不清。网关不是守护进程,它的作用是把客户端配置文件(hive-site.xml、hdfs-site.xml、yarn-site.xml、core-site.xml)软链到/etc/spark/conf/下,让本地spark-submit能读到集群配置。所以每台要跑 spark-submit 的机器都必须有网关角色,并且部署网关后要执行"部署客户端配置"。

部署完成后,在任意节点上:

ls -l /etc/spark/conf/

应该能看到一堆指向/opt/cloudera/parcels/CDH/lib/spark/conf或/etc/hadoop/conf的软链。如果只有一个spark-defaults.conf孤零零躺着,说明客户端配置没部署成功,spark-submit会报Could not resolve host: nameservice之类的错。

接着配置 Spark 的重要参数,通过 CM 的"Spark → 配置"搜索修改:

spark.executor.memory=12g spark.executor.cores=4 spark.dynamicAllocation.enabled=true spark.dynamicAllocation.minExecutors=1 spark.dynamicAllocation.maxExecutors=12 spark.shuffle.service.enabled=true spark.eventLog.enabled=true spark.eventLog.dir=hdfs://cdh-master-01:8020/spark-history

spark.shuffle.service.enabled=true必须配合 YARN 的yarn.nodemanager.aux-services里加上spark_shuffle,否则动态分配一缩容就把 executorshuffle 数据删了,任务直接失败。CM 会自动处理这个联动,但建议装完去yarn-site.xml里确认一下。

内存超配这块,CDH 默认开启了yarn.nodemanager.resource.memory-mb的严格校验,spark.executor.memory + overhead超过 container 申请的spark.yarn.executor.memoryOverhead就会被 kill,症状是 executor 启动后立刻Container killed by YARN for exceeding memory limits。超配默认关闭,我一般保持关闭,宁可多算一点。

4. 客户端验证与性能基线

装完不等于可用。很多集群"看起来服务都绿了",实际 spark-submit 一次就暴露问题。这一节讲怎么用最小代价把隐藏问题逼出来,顺便建个性能基线,后面出问题好对比。

4.1 三个递进式的验证用例

第一个用例,纯 local 模式,验证 Spark 本身:

spark-submit --master local[2] \ --class org.apache.spark.examples.SparkPi \ /opt/cloudera/parcels/CDH/lib/spark/examples/jars/spark-examples_2.11-2.4.0-cdh6.2.0.jar 10

能打出Pi is roughly 3.14说明 jar 包、JDK、Scala 版本链路都是通的。如果这一步就报NoClassDefFoundError: org/apache/hadoop/fs/FSDataInputStream,说明HADOOP_CONF_DIR没配好,回 3.5 节检查网关。

第二个用例,YARN client 模式,验证和 YARN 的联动:

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

这一步能暴露 90% 的问题:资源队不上、AKKA 通信超时、本地临时目录权限不对。如果卡在Running Spark using the REST application submission protocol很久,基本是 ResourceManager 地址解析不了,检查yarn-site.xml里的yarn.resourcemanager.hostname。

第三个用例,读 HDFS 写 HDFS,验证数据通路和序列化:

spark-submit --master yarn --deploy-mode cluster \ --class org.apache.spark.examples.SparkPageRank \ /opt/cloudera/parcels/CDH/lib/spark/examples/jars/spark-examples_2.11-2.4.0-cdh6.2.0.jar \ hdfs://cdh-master-01:8020/tmp/pagerank/data /tmp/out 10

这个例子依赖输入数据,需要先放个小文件进去。跑通它,说明 HDFS 读写、shuffle、history server 写日志全链路都没问题。

4.2 参数计算与实际压测记录

用spark-sql或spark-shell跑个简单的聚合,观察资源占用:

spark.sql("SELECT count(*) FROM range(100000000)").show()

10 亿行不太现实,一亿行足够看趋势。我在 3 节点 × 16 核 64G 的配置上实测,一亿行 count 大约 18 秒,executor 数自动扩到 6 个(每节点 2 个),每个 12G/4 核。这个数字只作参考,重点是通过 History Server 看shuffle read/write 量、GC 时间占比、任务倾斜。我发现一个常见现象:任务总时长 20 秒,其中 executor 启动占了 12 秒——这就是"小作业开动态分配"的代价,这种情况下把spark.dynamicAllocation.minExecutors调到 3,或者干脆关掉动态分配更划算。

再补一个内存参数的关系表,方便你调的时候对照:

参数建议值计算依据
executor.memory8-16g不超过 NM 可用内存的 1/3,避免单点抢占
executor.cores3-5超过 5 后 HDFS 并发读写效率下降
memoryOverhead至少 384m 或堆的 10%PySpark 场景要 20%
dynamicAllocation.max集群总核数 / executor.cores防止占满队列导致其他任务饿死
shuffle.partitions200 起,按数据量 × 2 核数调默认 200 对小数据过大,大数据过小

4.3 一个容易被忽略的验证:History Server 日志落盘

很多人跑完任务就不管了,等到要复盘时发现 History Server 里空空如也。原因通常是spark.eventLog.dir写错了,或者目录权限不对。HDFS 上的/spark-history目录必须允许所有跑任务的用户写入,CM 建这个目录时属主是spark,而你跑任务的用户可能是别的,就会静默失败。验证方法:

hdfs dfs -ls /spark-history hdfs dfs -touchz /spark-history/test_write

第二条能成功才算权限 OK。这一步我强烈建议在交付前做,因为 event log 是后续调优唯一的证据来源,没有它,性能问题基本靠猜。

5. 常见问题排查速查表

这套环境我装过不下十次,问题形态高度集中。下面按"现象 → 根因 → 处理"整理成表,遇到问题先查表,能省掉大量翻日志的时间。表格后面补几条表格装不下、需要展开讲的。

现象大概率根因处理动作
向导检查主机失败,报 SSH 错误免密未配或无 sudo 权限手动ssh 节点 'sudo -n true'验证
parcel 一直"正在下载".sha文件缺失或内容错误核对文件名与 SHA1 值
DataNode 起不来,日志无明确报错SELinux 未彻底关闭或 THP 未关getenforce查状态,重启确认
Agent 心跳正常但角色启动失败Java 路径不一致检查每台机器的 Java 主目录配置
executor 被 kill,提示超内存overhead 不足或超配开启调大memoryOverhead,保持超配关闭
卡在 "REST application submission"RM 地址解析失败检查yarn-site.xml主机名配置
Spark SQL 报 Metastore 连接失败Hive 元数据库凭证或驱动问题检查 JDBC jar 与hive-site.xml
History Server 无记录eventLog 目录权限或路径错用 touchz 验证目录可写
NoSuchMethodError: com.google.common手动塞 jar 导致 Guava 版本冲突移除非官方 jar,恢复 parcel 原生状态
shuffle fetch 大量失败shuffle service 未启用YARN aux-services 加spark_shuffle

5.1 三个值得展开的坑

第一个,主机名大小写问题。CM 在注册主机时用的是 Agent 上报的 FQDN,如果你在/etc/hosts里写的是大写(比如CDH-MASTER-01),而hostname -f返回小写,就会出现"同一台机器被识别成两个主机",然后向导里节点数不对、角色分配混乱。处理办法是统一全小写,改完重启 Agent。

第二个,ZooKeeper 的 dataDir 权限。ZooKeeper 启动失败最常见的原因是dataDir属主不对,尤其你在 root 下手工建过目录,之后服务用zookeeper用户跑,就写不进去。日志里是一句很含蓄的Unable to create data directory,其实就在等你改权限。chown -R zookeeper:zookeeper /var/lib/zookeeper即可。

第三个,YARN 的 vcores 超配判断。你设了yarn.nodemanager.resource.cpu-vcores=14,但yarn.nodemanager.resource.cpu-vcores和yarn.scheduler.maximum-allocation-vcores是两回事,后者是单 container 上限。如果后者小于你的spark.executor.cores,提交时直接报Invalid resource request,错误信息里只提最大值,不提你请求了多少,很容易看漏。

5.2 排查顺序的经验总结

出问题时我的一般顺序是:先看 CM 界面上的角色日志入口 → 再看原生日志文件 → 最后看 OS 层(dmesg、/var/log/messages)。CM 聚合了大部分 Java 异常,但 OOM Killer 干掉进程这种事它看不到,只在dmesg里有痕迹。曾经有个 NodeManager 每隔几小时就重启一次,CM 里只显示"角色已停止",最后在dmesg里看到Out of memory: Kill process,原来是机器上跑了个内存泄漏的小脚本。

另外,不要迷信"重启能解决"。重启确实能让角色起来,但如果根因是配置错误,它会在几小时后再次挂掉,而你已经忘了之前改过什么。我的习惯是每次改动都在changes.md里记一行:时间、改了什么、为什么、影响哪些节点。这个习惯在三个月后回看时,价值远超你当时的想象。

6. 我在这套环境上踩过的真实教训

装完这套东西之后,真正让我印象深刻的不是某个具体命令,而是几个"当时觉得无所谓、后来付出代价"的决定。

一个是副本数。我一开始图省事保持默认 3,两 DataNode 的集群里一直是 under-replicated,HDFS 的写入路径会不断尝试复制到第三个节点然后失败,写入延迟比正常情况高出将近一倍。改成 2 之后,同样的导入任务从 40 分钟降到 22 分钟。这件事让我养成了一个习惯:装完集群第一件事是核对所有和节点数相关的参数,包括副本数、dfs.datanode.failed.volumes.tolerated、YARN 的 nodemanager 数量感知。

另一个是测试环境别开 Kerberos。我在一个小集群上为了"模拟生产",把 Kerberos 也开了,结果 Spark 的每个提交都要处理 ticket 刷新、delegation token 续期,一个没配好就报GSS initiate failed。后来我把安全认证拆成独立的第二阶段,先保证非安全模式下全链路通畅,再叠安全层,出问题能立刻判断是哪一层。这个顺序很重要,一次只引入一个变量。

还有个小技巧值得分享:把常用验证命令打包成一个smoke-test.sh,放在 master 上,内容就是前面 4.1 的三个用例加 HDFS 读写检查。每次改完配置就执行一次,三分钟出结果。这套东西在你要给别的同事交接环境时尤其有用,它能替你回答"这集群到底好没好"这个问题。

最后提一句扩容场景。新增节点时,除了走一遍基础环境,最容易漏的是Python 版本和 JDK 小版本要和现有节点完全一致,以及新节点的/etc/hosts要同步。我吃过一次亏,新节点装的是更新版本的 Python,PySpark 任务被调度过去就报 pickle 不兼容,而另外两个节点跑得好好的,导致问题是"间歇性"的,排查方向一度跑偏到数据倾斜上。从那以后,扩容前先跑一次版本比对脚本,成了我的固定动作。

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

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

立即咨询