☰
Linux下搭建Hadoop 3.1.3与Spark 3.4.4 PySpark开发环境实战
2026/10/9 3:11:53 网站建设 项目流程

如果你手头正好有一台 Linux 服务器或者一台配置还行的虚拟机,想搭一套能跑 PySpark 的开发环境,我强烈建议你就照着 Hadoop 3.1.3 + Spark 3.4.4 这个组合来。这套组合可以说是当前学习大数据处理和做实验最稳的搭配之一:Hadoop 提供底层存储和资源调度,Spark 提供内存计算能力,PySpark 则让 Python 开发者也能顺畅地写分布式计算任务。对于刚入门大数据的学生、转行做数据工程的朋友,或者需要在本地复现线上任务的开发者来说,把这一套环境在 Linux 上装通,后面学 Spark SQL、调优、写实时任务都会轻松很多。

网上关于 Hadoop 和 Spark 的教程并不少,但很多都停留在“照着敲能跑”的程度,版本稍微一换就一堆坑。我自己在从零搭这套环境的时候,踩过不少雷:Java 版本不匹配导致 NameNode 起不来、格式化 HDFS 又把集群搞挂、PySpark 连接 YARN 时日志各种报错等等。这篇文章就把我实际操作的完整流程写下来,每个配置文件我都会解释是干什么的、为什么要这么配,最后再附上我在实训过程中遇到的经典问题排查清单,按这个思路走,你大概一个下午就能把环境跑通。

1. 方案设计与版本选型:为什么要选这套组合

1.1 版本搭配的核心逻辑

选版本这件事,看起来简单,实际上最影响成败。很多新手喜欢追新,上来就装 Hadoop 3.4、Spark 4.0,结果发现官方文档里的配置项已经变了,第三方依赖还没跟上,社区里能搜到的解决方案都是老版本的,时间全耗在排错上。这套环境我选择 Hadoop 3.1.3 + Spark 3.4.4,主要基于三个考量:

第一,兼容性。Spark 3.4.4 官方构建的二进制包spark-3.4.4-bin-hadoop3就是面向 Hadoop 3.x 的,和 Hadoop 3.1.3 搭配没有问题。而 Hadoop 3.1.3 是 CDH 发行版最后一代稳定版本,很多企业生产环境至今还在用,学这套配置不会学完就淘汰。

第二,Java 版本匹配。Hadoop 3.1.3 官方要求 JDK 8 或者 JDK 11,Spark 3.4.4 支持 Java 8/11/17,但我在实际测试中发现用 JDK 8 最稳。JDK 17 在编译 Spark 作业时偶尔会出现模块化访问限制的报错,虽然有参数可以绕过,但对新手不友好,平白增加排错难度。

第三,Python 支持。PySpark 3.4.4 要求 Python 3.8 以上,目前主流 Linux 发行版默认的 Python 3.8、3.10、3.11 都满足。你要是装太老的 Spark 2.x,还得去匹配 Python 2.7,那就真的过时了。

1.2 部署模式:单机、伪分布式还是集群

这套环境可以部署成三种模式,先想清楚自己属于哪种场景再动手:

模式特点适用场景资源要求
本地模式Spark 不连集群,本地起多线程快速验证 PySpark 语法极低,2G 内存即可
伪分布式在单机上模拟一个 Hadoop 集群,NameNode、DataNode、YARN 都在同一台机器学习 Hadoop 原理、跑 MapReduce 作业建议 4G 内存以上
集群模式多台机器分别承担不同角色生产、课程大作业、小团队开发至少 3 台机器,每台 4G 以上

我下文写的配置是伪分布式 + Spark Standalone/YARN 混用的方案。伪分布式能让你完整经历 HDFS 格式化、进程启动、Web UI 查看这些流程,和集群环境没有本质区别。等以后要扩展成多节点,只需要把配置里的localhost改成各自主机名,并把需要分发的主机加入 workers 列表就行,架构上不需要做任何改动。

1.3 硬件与操作系统建议

我最初在一台 2 核 4G 内存的虚拟机上跑,HDFS 刚启动内存就告急,YARN 再分配两个容器,机器直接卡死。后来加到 4 核 8G,跑起来就非常从容了。如果你想用这套环境跑稍微像样一点的 PySpark 作业,最少给 4G,推荐 8G。

操作系统方面,Ubuntu 22.04、CentOS 7、Rocky Linux 我都试过。Ubuntu 的包管理最方便,openjdk-8-jdk 一条命令就能装;CentOS 7 的默认 Python 是 2.7,额外装 Python 3.8 会麻烦一点。新手建议直接用 Ubuntu 22.04 LTS。

2. 环境准备:装软件前先把地基打牢

2.1 JDK 8 安装与环境变量配置

整个 Hadoop 生态都是跑在 JVM 上的,JDK 装不好,后面全白搭。我建议直接装 OpenJDK 8,这是 Hadoop 3.1.3 用得最广泛的版本。

Ubuntu 系统执行:

sudo apt update sudo apt install openjdk-8-jdk -y

CentOS/Rocky 系统执行:

sudo yum install java-1.8.0-openjdk java-1.8.0-openjdk-devel -y

装完之后关键一步是确认/usr/lib/jvm下实际存在的目录名,然后写入环境变量:

ls /usr/lib/jvm/

一般在 Ubuntu 上会看到类似java-8-openjdk-amd64的目录。然后编辑/etc/profile:

sudo vim /etc/profile

在文件末尾追加:

export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 export PATH=$PATH:$JAVA_HOME/bin

执行source /etc/profile后验证:

java -version

看到openjdk version "1.8.0_xxx"就说明 JDK 就位了。这里我特别想吐槽一点:有些教程让你把 JAVA_HOME 直接写成/usr/lib/jvm/default-java,这个路径在部分系统上是一个软链接,指向哪个版本完全取决于系统默认设置,一旦系统切换了默认 JDK,你的 Hadoop 环境变量就悄悄失效了,排查起来特别费劲。所以一定要写具体版本目录。

2.2 SSH 免密登录配置

Hadoop 的启动脚本依赖 SSH 在主机间通信,即使伪分布式,也要配置本机 localhost 的 SSH 免密。不配的话,每次启动时都会反复提示输入密码,而且脚本在多进程拉起时会因为这个卡住。

sudo apt install openssh-server -y ssh-keygen -t rsa -P "" -f ~/.ssh/id_rsa cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys

第一行ssh-keygen -P ""是生成一个空密码的密钥对,避免每次连接还要输密码。然后测试一下:

ssh localhost

能直接进入 shell 不要求输密码,就说明免密成功了。如果你用的是 root 用户,SSH 默认可能禁止 root 登录,需要修改/etc/ssh/sshd_config把PermitRootLogin设置为yes,然后重启 sshd 服务。这个坑我踩过,当时以为是免密配置失败,排查了半天发现是 root 登录被拦了。

2.3 用户与目录规划

我见过不少人直接拿 root 跑 Hadoop,勉强能跑,坏处是后面想用普通用户开发时权限混乱,而且 Hadoop 本身官方不推荐用 root 跑 DataNode。我的建议是创建一个专门的大数据用户:

sudo useradd -m -s /bin/bash bigdata sudo passwd bigdata

然后规划一个统一安装目录,我习惯放在/opt下:

  • /opt/hadoop:Hadoop 主目录
  • /opt/spark:Spark 主目录
  • /opt/data:HDFS 元数据和数据块存储目录

目录规划这个细节很少有人在教程里强调,但对排障非常关键。默认情况下 Hadoop 会把临时文件写到/tmp,系统一重启就全没了,你的 HDFS 状态和未落盘的元数据全丢。把数据目录独立出来,一方面避免系统临时目录清理导致数据丢失,另一方面以后备份、迁移环境也方便,直接把/opt/data打包拷走就行。

3. Hadoop 3.1.3 安装与伪分布式配置

3.1 下载解压与目录布局

Hadoop 的下载地址建议用 Apache 官方的归档仓库,不要从乱七八糟的第三方站点下,防止被塞了修改过的包:

cd /opt sudo wget https://archive.apache.org/dist/hadoop/common/hadoop-3.1.3/hadoop-3.1.3.tar.gz sudo tar -zxvf hadoop-3.1.3.tar.gz sudo mv hadoop-3.1.3 hadoop sudo chown -R bigdata:bigdata /opt/hadoop

解压完后,/opt/hadoop/etc/hadoop下就是所有配置文件。Hadoop 的配置文件都是 XML 格式,用vim直接编辑即可。

我要先强调一个基本概念:Hadoop 伪分布式模式下,NameNode(管理文件系统的元数据)、DataNode(实际存数据)、ResourceManager(管理资源)、NodeManager(执行任务)这 4 个进程全部在同一台机器上。这正好对应了 HDFS 和 YARN 两层:HDFS 负责文件存储,YARN 负责任务调度和资源分配。

3.2 核心配置文件逐个拆解

配置 Hadoop 的核心是 4 个 XML 文件加上一个环境变量文件。我一个个过,每个参数都会说明为什么要这么配。

第一个文件:core-site.xml

这个文件配置 HDFS 对外提供的入口地址和临时目录:

<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/opt/data/hadoop-tmp</value> </property> </configuration>

fs.defaultFS是 NameNode 的 RPC 通信地址,hdfs://localhost:9000是标准写法。这里不要写成file:///,否则 HDFS 会被当成本地文件系统。hadoop.tmp.dir就是刚才说的数据目录,我把它从默认的/tmp/hadoop-${user.name}改到了/opt/data下,这个目录会存 HDFS 的元数据和日志。

第二个文件:hdfs-site.xml

这个文件配置数据块的副本数和存储目录:

<configuration> <property> <name>dfs.replication</name> <value>1</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>file:///opt/data/hdfs/namenode</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>file:///opt/data/hdfs/datanode</value> </property> </configuration>

dfs.replication是数据块副本数量,默认是 3。在伪分布式模式下只有 1 个 DataNode,如果设成 3,DataNode 每次写数据都会尝试复制到另外两个节点,结果就是一直报NotReplicatedYet之类的警告,虽然不影响使用,但满屏报错会干扰排查。所以伪分布式直接设 1。

dfs.namenode.name.dir和dfs.datanode.data.dir是 NameNode 和 DataNode 各自存储状态的路径。注意要提前建目录:

sudo mkdir -p /opt/data/hdfs/namenode sudo mkdir -p /opt/data/hdfs/datanode sudo chown -R bigdata:bigdata /opt/data

第三个文件:yarn-site.xml

YARN 是 Spark 和 MapReduce 的资源调度框架,这里最关键的是配置 Shuffle 服务:

<configuration> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> <property> <name>yarn.nodemanager.aux-services.mapreduce_shuffle.class</name> <value>org.apache.hadoop.mapred.ShuffleHandler</value> </property> </configuration>

mapreduce_shuffle是 MapReduce 任务的数据洗牌机制,Hadoop 的 Map 和 Reduce 阶段之间要通过这个服务传输中间数据。Spark 如果以 YARN 模式运行,也会依赖这个机制。如果不配,作业提交到 YARN 后会在 shuffle 阶段一直卡着不动,非常隐蔽。

第四个文件:mapred-site.xml

这个文件用来告诉 MapReduce 框架跑在 YARN 上。注意 Hadoop 3.x 安装包默认只有mapred-site.xml.template这个模板文件,需要先复制一份:

cp /opt/hadoop/etc/hadoop/mapred-site.xml.template /opt/hadoop/etc/hadoop/mapred-site.xml

内容如下:

<configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> </configuration>

在 Hadoop 1.x 时代,MapReduce 有自己独立的调度系统,从 2.0 开始统一走 YARN。这个配置就是把计算框架和资源调度框架绑定起来,千万不能漏。

第五个文件:hadoop-env.sh

这个 shell 脚本负责设置 Hadoop 进程要用的 JVM 参数,最关键的是 JAVA_HOME。我把它高亮出来,因为很多教程在这里埋了坑,只让你写export JAVA_HOME=$(readlink -f /usr/bin/java | sed "s:/bin/java::"),这种动态获取在个别精简版系统上会解析失败。我更建议直接写死:

export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64

3.3 NameNode 格式化与启停验证

配置文件都改好之后,第一件必做的事是格式化 NameNode。格式化的原理是在dfs.namenode.name.dir指定的目录下初始化文件系统元数据,生成 clusterId 和 VERSION 等关键文件。可以理解为给一张新硬盘做分区表。

su - bigdata cd /opt/hadoop bin/hdfs namenode -format

看到输出末尾有successfully formatted字样才代表成功。这里有个极其重要的细节:格式化操作生成的 clusterId 会记录在 NameNode 的 VERSION 文件中,而 DataNode 第一次启动后也会生成并记录自己的 clusterId。如果以后你改配置或者反复格式化,但 DataNode 的数据目录没有清理,两个节点 clusterId 不一致,DataNode 就永远起不来,日志里一直报Incompatible clusterIDs。解决的办法是格式化前把/opt/data/hdfs/datanode目录内容清空。

格式化完成后启动集群:

sbin/start-dfs.sh

这一步会自动拉起 NameNode(jps 里叫 NameNode)、DataNode,以及 SecondaryNameNode。然后启动 YARN:

sbin/start-yarn.sh

用jps命令查看进程是否完整:

jps

正常情况下应该看到 5 个进程:

5177 NameNode 5322 DataNode 5521 SecondaryNameNode 4276 ResourceManager 4403 NodeManager

然后再用浏览器访问 Hadoop 的 Web UI:http://你的IP:9870。Hadoop 3.x 的 NameNode 管理界面端口是 9870,旧教程里的 50070 属于 Hadoop 2.x,照抄就找不到页面了。看到 Live Nodes 数量为 1,说明 HDFS 已经跑起来了。YARN 的资源管理界面在http://你的IP:8088。

最后验证一下 HDFS 的基本操作:

bin/hdfs dfs -mkdir /test bin/hdfs dfs -ls /

能正常创建并列出目录,说明整个 Hadoop 环境从存储到调度都通了。

3.4 配置文件改动的坑:改完必须重启的这些隐患

这里单独说一个很多人忽略的问题:Hadoop 的很多配置项是进程启动时读取一次、之后就缓存起来的,你改了 XML 文件如果不重启对应进程,代码里读到的还是旧值。比如你调整了dfs.replication,以为新写入的数据会按新副本数复制,实际上 NameNode 在启动时就缓存了这个参数,你必须执行sbin/stop-dfs.sh && sbin/start-dfs.sh重启才生效。

另外,改配置之前最好备份原文件,出错时能回退:

cp /opt/hadoop/etc/hadoop/hdfs-site.xml /opt/hadoop/etc/hadoop/hdfs-site.xml.bak

Hadoop 的 XML 对格式要求很严格,标签一对必须一一闭合。我遇到过用户在<value>标签里多打了一个空格,结果整个配置解析失败的情况。

4. Spark 3.4.4 部署:让计算引擎就位

4.1 下载对应版本与目录配置

Spark 的官方下载包有两种:一种是源码包需要自己编译,另一种是预编译二进制包。除非你要定制内部实现,否则直接下载spark-3.4.4-bin-hadoop3.tgz就行,这个二进制包已经包含了对 Hadoop 3.x 的依赖支持。

cd /opt sudo wget https://archive.apache.org/dist/spark/spark-3.4.4/spark-3.4.4-bin-hadoop3.tgz sudo tar -zxvf spark-3.4.4-bin-hadoop3.tgz sudo mv spark-3.4.4-bin-hadoop3 spark sudo chown -R bigdata:bigdata /opt/spark

进入 Spark 目录看看结构,bin/下是可执行脚本,conf/下是配置文件模板,jars/下是所有依赖的 jar 包。

4.2 spark-env.sh 与环境变量配置

Spark 的配置入口是conf/spark-env.sh。原始安装包没有这个文件,只有一个spark-env.sh.template模板,需要复制一份:

cp /opt/spark/conf/spark-env.sh.template /opt/spark/conf/spark-env.sh

编辑内容,关键配置如下:

export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 export HADOOP_HOME=/opt/hadoop export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop export SPARK_MASTER_HOST=localhost export SPARK_WORKER_MEMORY=2g export SPARK_WORKER_CORES=2

这里我重点说明一下HADOOP_CONF_DIR和HADOOP_HOME的区别。HADOOP_HOME是让 Spark 找到 Hadoop 的安装位置,用于加载依赖和本地库。HADOOP_CONF_DIR指向 Hadoop 的配置文件目录,Spark 提交任务到 YARN 或者读写 HDFS 时,需要通过这个目录里的core-site.xml和hdfs-site.xml知道 NameNode 在哪、鉴权方式是什么。只设 HADOOP_HOME 不设 HADOOP_CONF_DIR,Spark 会报找不到 HDFS 文件系统的错,或者任务提交后卡在等待资源。

修改完别忘了把 Spark 的 bin 也加进 PATH:

vim /etc/profile # 追加 export SPARK_HOME=/opt/spark # 追加 export PATH=$PATH:$SPARK_HOME/bin source /etc/profile

4.3 启动 Standalone 集群并验证

Spark 拥有自己独立的资源调度能力,叫 Standalone 模式。即使没有 YARN,你也可以先用 Spark 自带的 Master 和 Worker 把任务跑起来。Mini 场景下这个模式更直观、报错更少,是学习阶段首选的启动方式。

su - bigdata /opt/spark/sbin/start-master.sh /opt/spark/sbin/start-workers.sh

启动后访问http://你的IP:8080,能看到 Spark 的 Web UI,里面标注了 Master 的地址,通常类似spark://localhost:7077。记住这个地址,后面提交 PySpark 任务会用到。

然后跑一个 Spark 自带的示例程序验证 Scala 和 Java 任务能否正常提交:

/opt/spark/bin/run-example SparkPi 2

看到输出中包含 Pi 的近似值,就说明 Spark 的核心调度链路是通的。

4.4 Standalone 和 YARN 两种模式的取舍

我建议你在日常实验中两种模式都要会切换。Standalone 模式适合快速验证代码逻辑,它不依赖 HDFS 和 YARN,错误定位直接,Spark UI 里能看到每个 Stage 的执行情况。YARN 模式更接近企业生产环境,资源统一由 YARN 管理,能和 MapReduce 任务共存,但调试时多了一层资源分配的逻辑,日志也分散在 NodeManager 容器里,对新手不太友好。

切换方式通过spark-submit或spark-sql等命令的--master参数控制:

# Standalone 模式 --master spark://localhost:7077 # YARN 模式 --master yarn

如果你的作业要读写 HDFS 上的文件,两种模式都需要 Hadoop 相关配置;如果只是纯计算不碰 HDFS,那 Standalone 模式下甚至可以不启动 Hadoop,这也是快速开发调试很方便的一点。

5. PySpark 环境搭建与第一个任务

5.1 Python 版本确认与 pyspark 安装

PySpark 本质上是 Spark 的 Python API 绑定,它把 Python 代码翻译成 JVM 的运算指令,所以 Python 解释器版本必须和 Spark 兼容。先看一下系统的 Python 版本:

python3 --version

只要输出是 3.8 及以上就没问题。如果你的系统默认 Python 是 2.7,就一定不要偷懒,装一个 Python 3.8+ 并把python3指向新版本,否则 PySpark 会直接报Python 2.7 is no longer supported之类的错误。

接下来需要安装 PySpark 的 Python 包。这里有个容易混淆的点:Spark 发行版里已经带了 Python 相关的 API 文件(在/opt/spark/python),你直接用bin/pyspark就能打开交互式环境,为什么还要pip install pyspark?

区别在于:bin/pyspark是 Spark 自带命令行工具,适合在终端边写边调试;而pip install pyspark安装的是一个独立的 Python 库,你可以在自己的 Python 脚本里import pyspark来写程序。两者版本必须一致,否则会出现 API 方法对不上的情况,我建议安装和 Spark 版本一致的包:

pip3 install pyspark==3.4.4

如果你用的是虚拟环境,要在虚拟环境里安装;如果系统全局装,用pip3 install --user pyspark==3.4.4避免污染系统环境。

5.2 PYSPARK_PYTHON 和 PYSPARK_DRIVER_PYTHON

这组环境变量极其重要,可以说是 PySpark 环境配置里翻车率最高的地方。PYSPARK_PYTHON指定 Spark 的 worker(执行任务的子进程)使用哪个 Python 解释器;PYSPARK_DRIVER_PYTHON指定 driver(入口进程)使用哪个 Python 解释器。

集群环境中,worker 可能分布在不同的机器上,每台机器的 Python 路径不一致,所以必须显式指定一个大家通用的解释器路径。即使伪分布式只有一个节点,如果不设置,Spark 默认调用python命令,而很多系统里python指向 Python 2.7,直接失败。我在配置时统一设置:

vim /etc/profile # 追加 export PYSPARK_PYTHON=/usr/bin/python3 # 追加 export PYSPARK_DRIVER_PYTHON=/usr/bin/python3 source /etc/profile

如果你的 python3 安装位置特殊,用which python3查看实际路径再填。

还有一个细节:在.zshrc或.bashrc里配置只对当前终端生效,Spark 的 worker 进程是通过 SSH 拉起来的,可能不会继承你终端里的环境变量,所以这里我建议写到/etc/profile这种全局文件里,确保所有进程都能读到。

5.3 第一个 PySpark 任务:本地模式与 Standalone

先说最简单的本地验证。写完代码后用 SparkSession 创建一个应用:

from pyspark.sql import SparkSession spark = SparkSession.builder \ .appName("FirstPySparkJob") \ .master("local[2]") \ .getOrCreate() data = ["hello spark", "hello hadoop", "hello python"] rdd = spark.sparkContext.parallelize(data) counts = rdd.flatMap(lambda line: line.split(" ")) \ .map(lambda word: (word, 1)) \ .reduceByKey(lambda a, b: a + b) for word, num in counts.collect(): print(f"{word}: {num}") spark.stop()

保存为first_job.py,用spark-submit提交:

/opt/spark/bin/spark-submit \ --master local[2] \ first_job.py

--master local[2]表示在本地用 2 个线程模拟分布式执行,先不依赖 Hadoop,适合验证核心逻辑。

验证通过之后,切换到前面搭好的 Spark Standalone:

/opt/spark/bin/spark-submit \ --master spark://localhost:7077 \ first_job.py

任务提交后,到 Spark Web UIhttp://IP:8080能看到Running Applications列表里新出现一个应用。点击进去,能看到每个 Executor 的资源使用情况。能正常执行并输出结果,说明 PySpark 到 Spark Standalone 的链路是通的。

5.4 把任务提交到 YARN 的配置差异

YARN 模式是 Spark 最常见的生产运行方式,和 Standalone 相比,你需要额外注意三件事:

第一,必须保证 HADOOP_CONF_DIR 设置正确。YARN 模式下,Spark 的 ApplicationMaster 需要和 ResourceManager 通信来申请容器,它靠的是HADOOP_CONF_DIR里的yarn-site.xml来找到 ResourceManager 地址。没配好,任务会一直卡在ACCEPTED状态,既不算失败也不继续跑。

第二,确保 HDFS 可用。YARN 模式下 Spark 默认把应用依赖的 jar 包、临时文件都上传到 HDFS 的临时目录,HDFS 没起来,提交就会报错。所以执行 YARN 模式前,先检查jps里 NameNode 和 DataNode 都在。

第三,指定 Python 解释器。YARN 模式会在一台新机器(NodeManager 节点)上启动 Python worker 进程,这和在 master 上PYSPARK_PYTHON环境变量生效的机制不同。务必在代码里显式指定:

import os os.environ["PYSPARK_PYTHON"] = "/usr/bin/python3" os.environ["PYSPARK_DRIVER_PYTHON"] = "/usr/bin/python3"

然后提交:

/opt/spark/bin/spark-submit \ --master yarn \ --deploy-mode client \ --driver-memory 1g \ --executor-memory 1g \ --num-executors 1 \ first_job.py

这里--deploy-mode client是指 driver 进程运行在你当前提交任务的终端所在机器上,日志直接打印在终端,方便调试。生产环境里更多用cluster模式,driver 由 YARN 在集群内随机某台机器拉起,但这需要所有节点都有同版本的 Python 和 PySpark,伪分布式只用一台机器所以差别不大。

跑通之后,你会看到 YARN 的 Web UI(8088 端口)里出现了对应的 Application,点进去还能看到 driver 日志和执行状态。

5.5 用 findspark 简化代码与 IDE 集成

我发现很多人在 PyCharm 或 VSCode 里直接写 PySpark 代码时,会遇到一个尴尬的问题:import pyspark成功,没问题,但连接 Spark 集群时报错。根本原因是 IDE 里运行 Python 脚本的环境变量和你在终端里配置的/etc/profile不共享。这时可以用findspark这个库在代码里动态指定 Spark 路径:

pip3 install findspark

然后在 Python 脚本开头加上:

import findspark findspark.init("/opt/spark") from pyspark.sql import SparkSession

findspark.init会自动帮你在代码内部设置JAVA_HOME、SPARK_HOME以及必要的 classpath,相当于把终端里手动做的事情用代码接管了。这个技巧在别人电脑上跑你的代码时特别省事,对方不用知道你的环境变量是怎么配的。但我要提醒一句:它只能解决加载问题,如果你要连接远程的 YARN,HADOOP_CONF_DIR仍然需要代码里用os.environ手动设置。

6. 实战排查手册:我遇到的经典翻车现场

6.1 连接与启动类故障

现象:HDFS 启动时日志报Incompatible clusterIDs

这个错误我见过至少三次,几乎每个重复格式化的人都会碰到。原因前面说过,NameNode 和 DataNode 的 VERSION 文件里 clusterId 不一致。每次格式化 HDFS 都会生成新的 clusterId,而已启动过的 DataNode 的数据目录还留着旧的 clusterId。遇到过这个报错,不要犹豫,直接把两个数据目录都清掉再格式化:

rm -rf /opt/data/hdfs/namenode/* rm -rf /opt/data/hdfs/datanode/*

然后重新格式化并启动 DFS。这里有个原则:一旦 DataNode 开始使用,就不要轻易重新格式化 NameNode;真要格式化,务必连 DataNode 目录一起清干净。

现象:ssh localhost需要输入密码

Hadoop 启动脚本会通过 SSH 拉起远程进程(哪怕目标就是本机),如果你在start-dfs.sh执行过程中看到要求输入密码,说明免密配置没生效。检查一下~/.ssh目录权限:

chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys

还有一个可能:你把公钥加到了 root 的authorized_keys,但你当前用的是bigdata用户,SSH 免密登录是按登录用户区分的,每个用户都要有自己的authorized_keys。

6.2 内存与资源类故障

现象:Spark 任务提交后一直卡在ACCEPTED不执行

这个先分情况。如果是 YARN 模式,先看 YARN 的 8088 页面,如果 Application 状态一直是ACCEPTED,大概率是资源没分配够。默认情况下 YARN 每个容器会申请 1G 以上内存,而伪分布式只有一台机器,NodeManager 上可分配的内存总共就几 G。一旦你提交任务时指定的--executor-memory过大,或者同时跑了好几个任务,资源就被占满。

我的处理方式:在yarn-site.xml里显式限制单容器最小内存:

<property> <name>yarn.scheduler.minimum-allocation-mb</name> <value>256</value> </property> <property> <name>yarn.scheduler.maximum-allocation-mb</name> <value>2048</value> </property>

然后提交任务时,executor 内存控制在 512M 或 1G,避免把资源拉满。

现象:Python worker 报错lost connection或者failed to connect back

这个报错非常典型。Spark 的 driver 和 Python worker 之间通过 socket 通信,worker 启动时会回连 driver 的某个端口。如果网络配置有问题(比如有多块网卡、防火墙开着、或用 docker 跑了伪分布式),回连失败就会报这个错。排查思路:

# 1. 检查防火墙是否拦截 sudo ufw status # 2. 确认 driver 所在机器的 hostname 能正确解析 hostname -i

如果是多网卡机器,在spark-env.sh里强制指定 driver 使用的 IP 和端口:

export SPARK_DRIVER_HOST=127.0.0.1

6.3 版本与类路径类故障

现象:java.lang.NoClassDefFoundError

这个通常是因为 Spark 找不到某个依赖 jar 包。Spark 的jars/目录下通常会包含 Hadoop 客户端相关的依赖,但你用的 JDK 版本太新时会因为模块访问限制触发缺失类。比如我在 JDK 17 下跑 PySpark 就遇到过javax.annotation找不到的问题。最简单的处理方式就是换回 JDK 8,不要和版本问题硬刚。

现象:pyspark.sql.utils.IllegalArgumentException: Unsupported class file major version

PySpark 的 Scala 端编译目标版本和 JVM 版本对不上时会出现这个问题。比如 Spark 3.4.4 编译时目标版本是 JVM 8/11,但你运行环境是 JVM 17,就会报major version 61这种错误。解决方案仍然是把 JDK 降到 8。

现象:Python in worker has different version xx than that in driver

driver 用的 Python 是 3.10,worker 用的 Python 是 3.6 或者反过来。这个就是前面说的PYSPARK_PYTHON没设统一导致的。我建议在所有机器上把python3统一指向同一个解释器,同时在spark-env.sh中强指定:

export PYSPARK_PYTHON=/usr/local/python3/bin/python3

有条件的话,把PYTHONPATH里的 pyspark 包路径也设置成同一个版本。

6.4 排查速查表

症状可能原因推荐排查动作
jps 无 NameNodeJAVA_HOME 没生效或端口 9000 被占先java -version,再 `netstat -tlnp
jps 无 DataNodeclusterId 不一致清空 datanode 目录,重启用 DFS
Web UI 404端口可能不是 9870确认 Hadoop 3.x 的 NameNode UI 端口
pyspark 导入报错pip 版 pyspark 与 Spark 发行版版本不符pip show pyspark确认版本为 3.4.4
任务一直 ACCEPTEDYARN 资源不足调低 executor 内存或检查 maximum-allocation-mb
乱码中文日志没配编码在 spark-env.sh 加上export SPARK_SUBMIT_OPTS="-Dfile.encoding=UTF-8"
Spark 任务读写 HDFS 失败可能是 HDFS 没启动先跑一个hdfs dfs -ls /测试

排查这些问题的共同方法论是:先看日志,再看 Web UI,最后才改配置。Spark 和 Hadoop 都提供了大量的运行日志,默认在$HADOOP_HOME/logs和$SPARK_HOME/logs目录下。任务失败时不要急着重新提交,先翻日志文件,往往答案就在中间几十行 Java 堆栈或者 Python 回溯里。和写普通 Python 程序不同,分布式的 bug 不会把明确的错误路径甩到你脸上,耐心逐层剥才是正道。

7. 个人实操心得与后续扩展方向

这套环境从 HDFS 到 YARN,再到 Spark Standalone,最后到 PySpark 跑通第一个 WordCount,一共花了我一个下午加一个早上。踩坑最密集的环节不是 Spark,反而是 Hadoop 的初始化和环境变量。回头总结,我觉得对你最有用的经验是这几点:

第一,环境变量一定要全局统一。JAVA_HOME、HADOOP_HOME、SPARK_HOME、PYSPARK_PYTHON 这四个变量写成死路径,别用什么$(command -v java)动态解析,省掉的五分钟会在排障时用五十分钟找回来。第二,每次格式化 HDFS 前,先规划好要保留的数据。伪分布式实验环境里,数据本身没什么价值,但那个反复格式化把集群搞挂的痛感,会深深刻在你记忆里。第三,别怕看日志。分布在/opt/hadoop/logs和/opt/spark/logs下的日志才是你最好的排障老师,比网上任何现成答案都可靠。

这套环境跑通之后,你还可以顺着往下扩展。我的下一步建议是尝试用 PySpark 读写 HBase:在 Hadoop 生态里,HBase 作为列式存储数据库,经常和 Spark 搭配做实时读写。方向就是先把 HBase 装好,然后在 Spark 的jars/目录下添加 HBase 客户端依赖,再用NewHadoopAPI方式读写。还有另一个方向是装一个 Hive 元数据服务,让 Spark SQL 能直接查 Hive 表。这些都属于同一套 Hadoop 生态的延伸,底层架构不需要动,只要往 classpath 里加依赖就行,我觉得这正是一套跑通的环境能带来的最大价值——你以后所有的分布式实验,都站在今天这块地基上。

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

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

立即咨询