最近在Linux上把Hadoop 3.1.3和Spark 3.4.4整套环境从零配了一遍配合Python 3跑PySpark前前后后折腾了几天。不少朋友也问过我这个组合怎么搭尤其是PySpark3和Hadoop集群的对接问题今天就把整个过程整理出来包括版本选择、配置细节、启动验证和生产中容易踩的坑给需要的同学一份可以直接照着做的检查单。这套环境说难不难说简单也不简单。难在版本匹配和细节配置上比如Java版本、Python解释器路径、Hadoop和Spark的CLASSPATH互相依赖都是容易卡住的地方。简单在思路只要理清按照步骤走基本能一路通关。下面我按实际操作的顺序从上到下讲一遍。1. 整体思路与版本选型1.1 为什么选择 Hadoop 3.1.3 Spark 3.4.4Hadoop 3.1.3是Apache Hadoop 3.x里一个非常稳定的基础版本HDFS和YARN的功能都已经很成熟很多生产环境用的就是这个版本。Spark 3.4.4则属于Spark 3.4系列的最新维护版本对PySpark的Python API支持相当完整包括pandas UDF、Structured Streaming等功能在3.4版本里都有明显改进。有人会担心Spark官方下载页写的是Pre-built for Apache Hadoop 3.3会不会和Hadoop 3.1.3不兼容实际上这里的3.3指的是Spark自带的Hadoop客户端库版本而不是说你必须运行一个Hadoop 3.3的集群。Spark 4.x之前的设计都是通过Hadoop Client连接外部集群只要你的HDFS和YARN对外提供的协议兼容3.1.3完全能配合Spark 3.4.4工作。我自己测试过HDFS读写、YARN资源申请都没有问题。选这个组合还有一个实际原因不少公司现有集群就是Hadoop 3.x早期版本而团队想用更新的Spark来做数据处理。你不可能把整个Hadoop集群升级所以在Spark侧做适配是更现实的做法。这个组合既能吃到Spark新版本的红利又不用动Hadoop那摊庞大的底层设施性价比很高。1.2 核心组件与架构关系先理清各组件之间的关系这样才能在配置时知道每条路径、每个变量的意义。Hadoop负责两件事存储和管理文件也就是HDFS资源调度和任务分配也就是YARN。Spark则是一个计算引擎本身不存数据它跑在HDFS之上从HDFS读数据算完之后再写回HDFS。YARN在这里可以看作是Spark的房东Spark应用以Container的形式租用YARN上的CPU和内存资源。PySpark是Spark为Python用户提供的一套API。你在Python里写from pyspark import SparkContextPySpark会通过Py4J在背后启动一个JVM把你的Python代码翻译成JVM指令去执行。所以这套环境里Python解释器、Java JDK、Spark本来要用到的Scala库三者是缺一不可的。这就是为什么很多人在Spark环境里遇到Python not found或者JAVA_HOME没有设置这类错误正是因为这三者的关联没配置正确。2. 环境准备与前置条件2.1 Linux系统基础配置我这次用的系统是Ubuntu 22.04CentOS 7序列的原理也完全一样只是包管理命令不同。硬件上建议内存至少8GB最好16GB因为NameNode、DataNode、ResourceManager再加上Spark进程至少要占用4~6GB内存。首先更新系统源并安装基础工具sudo apt update sudo apt install -y openssh-server rsync vim curl wget net-tools然后建议单独创建一个非root用户比如叫hadoop因为Hadoop和Spark的守护进程都不建议用root跑防止误操作和权限问题。sudo useradd -m -s /bin/bash hadoop sudo passwd hadoop sudo usermod -aG sudo hadoop su - hadoop配置SSH免密登录这一步很关键后面start-dfs.sh启动HDFS时会通过SSH分发命令到各个节点。即使是单机环境也要把本机localhost做免密否则会卡在输入密码的交互上。ssh-keygen -t rsa -P -f ~/.ssh/id_rsa cat ~/.ssh/id_rsa.pub ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys做完后测试ssh localhost应该不需要密码就能登录。这一步基本上每个新手都会被坑一次我第一次配的时候忘了做免密启动脚本一直停在那里还以为是网络问题。2.2 Java JDK 安装与配置Hadoop 3.1.3官方要求Java 8或Java 11Spark 3.4.4官方支持Java 8/11/17。为了最大兼容性我用的是OpenJDK 1.8这个版本经过大量生产环境验证踩雷概率最低。装JDK的时候注意不要用系统自带的很老的openjdk最好指定版本。sudo apt install -y openjdk-8-jdk java -version然后配置JAVA_HOME写入~/.bashrcecho export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 ~/.bashrc echo export PATH$PATH:$JAVA_HOME/bin ~/.bashrc source ~/.bashrc如果你的Java安装在别的路径用readlink -f $(which java)找到真实路径再填进去。这里有个容易忽略的点Spark和Hadoop的脚本都会直接读JAVA_HOME环境变量如果你在/etc/profile里设置了但当前Shell没有重新加载就还是找不到。所以我每次都习惯把这两个export写到~/.bashrc里因为普通登录Shell默认会加载它。2.3 Python 3 环境准备PySpark要求Python 3.6及以上实测3.8、3.10、3.11都能跑。Ubuntu 22.04自带Python 3.10可以直接用。但为了环境隔离我建议装一个虚拟环境特别是在机器上同时有其他项目的时候。这里给出基于venv的做法sudo apt install -y python3-venv python3-pip python3 -m venv ~/spark_env source ~/spark_env/bin/activate稍后配置PySpark时要让Spark知道用哪个Python解释器。因为PySpark启动时会执行python、python3等命令如果你有多个Python必须显式指定。这个我放到后面Spark配置一节单独说因为它是PySpark3能否正常工作的关键。3. Hadoop 3.1.3 部署与配置3.1 下载安装与目录规划到Apache Hadoop官网下载hadoop-3.1.3.tar.gz或者使用国内镜像加快速度。下载后解压到/opt下然后做一个软链接方便后续切换版本。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 -C /opt sudo ln -s /opt/hadoop-3.1.3 /opt/hadoop sudo chown -R hadoop:hadoop /opt/hadoop接着设置Hadoop环境变量同样写入~/.bashrcexport HADOOP_HOME/opt/hadoop export HADOOP_CONF_DIR$HADOOP_HOME/etc/hadoop export PATH$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin这里面的HADOOP_CONF_DIR很重要Spark在连接HDFS时会从这里读取core-site.xml等配置。所以后面配置Spark时要么显式把HADOOP_CONF_DIR列入CLASSPATH要么让它自己去找。还要在$HADOOP_HOME/etc/hadoop/hadoop-env.sh里明确JAVA_HOME因为Hadoop的启动脚本在通过SSH远端执行时不一定能继承你Shell里的JAVA_HOME变量。打开这个文件找到JAVA_HOME那行取消注释并改成export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64这一步经常被忽略结果就是本地跑命令正常但start-dfs.sh启动在各个节点上一会儿JVN就报错。3.2 核心配置文件详解Hadoop的配置都在$HADOOP_HOME/etc/hadoop目录下单机模式只需要改三个文件core-site.xml、hdfs-site.xml、yarn-site.xml。core-site.xml主要设置NameNode的地址和临时目录configuration property namefs.defaultFS/name valuehdfs://localhost:9000/value /property property namehadoop.tmp.dir/name value/opt/hadoop/data/tmp/value /property /configurationhdfs-site.xml设置NameNode和DataNode的数据存储目录以及副本数。单机环境副本数设为1configuration property namedfs.namenode.name.dir/name value/opt/hadoop/data/namenode/value /property property namedfs.datanode.data.dir/name value/opt/hadoop/data/datanode/value /property property namedfs.replication/name value1/value /property /configurationyarn-site.xml设置ResourceManager和NodeManager的基本参数configuration property nameyarn.nodemanager.aux-services/name valuemapreduce_shuffle/value /property property nameyarn.nodemanager.aux-services.mapreduce_shuffle.class/name valueorg.apache.hadoop.mapred.ShuffleHandler/value /property /configuration这三组配置里的路径都建议放到/opt/hadoop/data下而不是默认的/tmp因为/tmp系统重启会清空数据目录就没了又要重新格式化很坑。另外目录权限必须是当前运行用户有读写权限我用的是hadoop用户。3.3 格式化NameNode并启动HSDFS/YARN在第一次启动之前必须格式化NameNode这会初始化文件系统的元数据hdfs namenode -format看到successfully formatted字样就算成功。然后启动HDFSstart-dfs.sh再启动YARNstart-yarn.sh用jps命令检查进程正常情况下应该看到NameNode DataNode SecondaryNameNode ResourceManager NodeManager有时候jps本身没有安装用sudo apt install -y openjdk-8-jdk-headless会顺便装上或者ps aux | grep java来看。如果某个进程缺失去对应日志文件里查原因日志路径在$HADOOP_HOME/logs/。3.4 验证HDFS与YARN功能先看Web UINameNode默认在9870端口ResourceManager默认在8088端口。用浏览器打开http://localhost:9870能看到NameNode状态和DataNode列表说明HDFS启动成功。打开http://localhost:8088能看到YARN集群资源和运行中的应用。命令行验证更直接hdfs dfs -mkdir -p /user/hadoop hdfs dfs -put /etc/hostname /user/hadoop/ hdfs dfs -cat /user/hadoop/hostname如果mkdir报错多半是端口9000没监听或者NameNode没有启动。检查netstat -tlnp | grep 9000。另外YARN验证可以运行一个示例yarn jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar pi 2 10能输出PI的近似值说明YARN调度、NodeManager、MapReduce都正常。这一步做完Hadoop侧就完全OK了。4. Spark 3.4.4 安装与PySpark环境集成4.1 下载Spark与选择适合的包Spark下载页面提供了多个预编译选项比如Pre-built for Apache Hadoop 3.3和Pre-built with user-provided Apache Hadoop。我推荐选后者也就是spark-3.4.4-bin-without-hadoop.tgz因为这样可以彻底避免Hadoop客户端库版本冲突。Spark会根据HADOOP_CONF_DIR和CLASSPATH去连接你实际安装的Hadoop集群。当然直接用spark-3.4.4-bin-hadoop3.3.tgz也可以它能自动识别外部HDFS。但为了避免某些Jar包版本不一致导致的NoClassDefFoundError我还是用without-hadoop包把CLASSPATH交给Hadoop自己管理这样最干净。wget https://archive.apache.org/dist/spark/spark-3.4.4/spark-3.4.4-bin-without-hadoop.tgz sudo tar -zxvf spark-3.4.4-bin-without-hadoop.tgz -C /opt sudo ln -s /opt/spark-3.4.4-bin-without-hadoop /opt/spark sudo chown -R hadoop:hadoop /opt/spark然后设置Spark环境变量写入~/.bashrcexport SPARK_HOME/opt/spark export PATH$PATH:$SPARK_HOME/bin:$SPARK_HOME/sbin4.2 配置spark-env.shSpark的核心配置文件是$SPARK_HOME/conf/spark-env.sh它默认不存在先把模板复制一份cp $SPARK_HOME/conf/spark-env.sh.template $SPARK_HOME/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_DIST_CLASSPATH$(/opt/hadoop/bin/hadoop classpath) export PYSPARK_PYTHON/home/hadoop/spark_env/bin/python export PYSPARK_DRIVER_PYTHON$PYSPARK_PYTHON这里解释一下每项的作用。HADOOP_CONF_DIR告诉Spark去哪里找HDFS的core-site.xml这样spark命令里的hdfs://localhost:9000路径才能被解析。SPARK_DIST_CLASSPATH是Spark连接HDFS、YARN所需的Hadoop类路径用hadoop classpath命令生成比自己手动拼Jar包靠谱得多。PYSPARK_PYTHON是执行器的Python解释器路径PYSPARK_DRIVER_PYTHON是驱动端的路径两者通常一致。需要注意如果你把Python放在虚拟环境里那么PYSPARK_PYTHON必须指向该虚拟环境的python绝对路径。因为Spark的worker在远端相同的路径上寻找Python解释器如果路径写错或者该路径在别人的机器上不存在就会报python: not found。实际生产中最好让所有节点Python安装路径一致。4.3 PySpark Python绑定与JVM内存配置好spark-env.sh后建议先用一个简单的命令行测试PySpark能否启动同时观察JVM和Python进程的情况$SPARK_HOME/bin/pyspark --master local[2]正常的话会进入一个Python交互式Session屏幕上有Welcome to Spark横幅。这个过程中你会注意到后台其实有一个Java进程在运行这就是PySpark通过Py4J启动的JVM。如果这里一直报错多半是JAVA_HOME或SPARK_DIST_CLASSPATH没有设置对。对于内存配置可以在spark-env.sh里额外设置export SPARK_DRIVER_MEMORY2g export SPARK_EXECUTOR_MEMORY2g如果机器内存有限建议先调小防止启动时OOM。这块我建议用默认值测试真正跑大任务时再通过--executor-memory参数或者spark-defaults.conf调整。PySpark还涉及Python worker的内存Spark会默认从执行器内存中划一部分给Python worker也可以在spark-env.sh里设置spark.executor.pyspark.memory来控制。这个参数在处理超大DataFrame时比较关键不然容易出现Python侧内存溢出。4.4 连接HDFS测试PySpark读写进入PySpark交互环境后先试试HDFS连通性。创建一个测试文件并写入HDFS然后通过PySpark读取rdd sc.textFile(hdfs://localhost:9000/user/hadoop/test.txt) rdd.count()如果返回文件行数说明Spark已经能访问HDFS。再测试写入rdd.saveAsTextFile(hdfs://localhost:9000/user/hadoop/output)然后去HDFS上确认内容是否存在。这里的重点是hdfs路径前要加hdfs://localhost:9000不然Spark会认为路径是本地文件。如果你在core-site.xml里已经把fs.defaultFS配好了也可以简写为/user/hadoop/test.txtSpark会自动补全scheme。这一步能通就说明PySpark和Hadoop集群已经完整打通。之后再想用Spark on YARN模式只要保证YARN配置正确用--master yarn --deploy-mode client就能把Spark任务提交到YARN上运行。5. 实操验证与常见问题排查5.1 运行第一个PySpark任务WordCount实战作为对这个环境的最终验收我跑了一个最经典的WordCount。在PySpark里开一个Python脚本文件wordcount.pyfrom pyspark.sql import SparkSession spark SparkSession.builder.appName(PySparkWordCount).getOrCreate() sc spark.sparkContext lines sc.textFile(hdfs://localhost:9000/user/hadoop/input.txt) words lines.flatMap(lambda line: line.split( )) pairs words.map(lambda word: (word, 1)) counts pairs.reduceByKey(lambda a, b: a b) output counts.collect() for (word, count) in output: print(f{word}: {count}) spark.stop()先用hdfs dfs -put上传一个input.txt到HDFS然后用spark-submit提交$SPARK_HOME/bin/spark-submit --master local[2] wordcount.py运行结束后终端会打印每个单词的出现次数。这里有一个很小的细节Spark对空字符串的处理如果你用默认的split( )空行会生成空字符串导致计数里多出空值。生产环境我会用split(\\s)并过滤掉空项words lines.flatMap(lambda line: line.split(\\s)).filter(lambda w: w ! )这也是我第一次写WordCount时踩到的小坑明明文本很简单结果多了一堆空串。5.2 常见错误与解决方案我把自己实际遇到过的问题整理成一张速查表方便各位对照排查错误现象常见原因解决办法jps没有NameNode未格式化或NameNode崩溃运行hdfs namenode -format后重启start-dfs.sh卡住SSH免密配置缺失按2.1节设置localhost免密连接localhost:9000拒绝core-site.xml的fs.defaultFS错误检查地址和端口确认NameNode监听PYSPARK报python: not foundPYSPARK_PYTHON路径不对用which python确认路径并更新spark-env.shSpark连接HDFS报NoClassDefFoundError缺少Hadoop类路径设置SPARK_DIST_CLASSPATH$(hadoop classpath)YARN Web UI访问不了防火墙或ResourceManager未启动检查8088端口看ResourceManager日志Worker OOM资源分配过小调大spark.executor.memory或减小worker数量HDFS DataNode目录权限错误hadoop.tmp.dir/data目录权限不足保证属主是当前用户并给予读写权限Spark运行Map任务特别慢同一主机HDFS走loopedback配置/etchosts将主机名映射到内网IP而不是127.0.0.1最后一条特别容易忽略。默认情况下localhost映射到127.0.0.1但这会导致DataNode之间走回环网络数据传输性能骤降。正确做法是在/etc/hosts里把主机名指向实际的内网IP比如192.168.1.100 master这样HDFS的节点间通信才能真正走网卡。5.3 我的踩坑经验总结配置这套环境的过程中我最大的体会就是版本匹配并不是最难的难在把各种配置路径和环境变量让每个进程都能正确继承。Java环境、Python环境、Hadoop配置、Spark配置任何一个环节脱节后面就会出现诡异的错误。一个很实际的经验是所有配置尽量写在~/.bashrc和spark-env.sh里不要只写在当前终端。因为系统Service脚本和spark-submit在提交任务时的Shell上下文可能完全不同特别是通过SSH和crontab启动的时候环境变量会丢失。另外每次修改hadoop-env.sh或spark-env.sh后要确保重启所有相关进程不要图省事只重开一个pyspark有时候CLASSPATH缓存还在改了等于白改。还有一点我在生产环境配置时会刻意避开系统自带的Python因为系统升级往往会动它造成PySpark的路径突然失效。创建一个独立的venv或者在/usr/local/bin下放一个稳定的Python解释器是更稳妥的做法。最后想说PySpark的调试思路和普通Python程序很不一样。普通Python报错直接看堆栈PySpark有些错误是JVM抛出来的要在Spark日志里找关键信息。遇到问题不要慌先看$SPARK_HOME/logs和$HADOOP_HOME/logs里面几乎都能找到根因。这套环境搭好之后后续跑数据处理、机器学习特征工程都会顺手很多尤其是Spark 3.4对Python 3的兼容性已经非常成熟作为大数据入门和日常开发环境完全够用。