ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Windows下PySpark环境搭建完整指南:从JDK到winutils的避坑实操

Windows下PySpark环境搭建完整指南:从JDK到winutils的避坑实操 很多人在 Windows 上想跑 PySpark第一反应就是下载 Spark 解压、配好 JAVA_HOME、然后 pyspark 就能跑。但真正上手你会发现在 Windows 上这套流程远没这么顺畅环境装完能顺利跑通的人其实不多。这篇文章我把自己在 Windows 上搭建 Python Spark 环境的完整过程、踩过的坑、以及最终每天在用的开发配置整理出来希望你看完能少走弯路。1. 先搞清楚Windows上要搭的Spark环境到底是哪一种1.1 local模式根本不要求你装一个Hadoop集群很多新手在搜索 Spark 环境搭建时会被大量Spark 集群搭建Hadoop 开发环境搭建的教程绕晕然后在 Windows 上一边装 Hadoop、一边装 Spark最后怎么也起不来。先说一个关键结论如果你只是想在 Windows 上用 Python 写 PySpark 代码做数据分析、原型验证那么你要搭的是Spark local 模式这种模式根本不需要安装独立的 Hadoop 集群。Spark 发行包本身内置了 Hadoop Client 依赖它会在本地以单进程的方式启动 Driver 和 Executor数据读写也默认走本地文件系统。所以你不必去装一个庞大的 Hadoop 集群。但 local 模式有一个 Windows 专属的例外Spark 内部的 Hadoop 本地库需要访问winutils.exe这个文件缺少它就会出现Failed to locate the winutils binary in the Hadoop binary directory之类的错误。这是 Windows 上搭 Spark 环境时最典型、也是网上教程最少提到的一个坑。1.2 版本矩阵JDK、Hadoop、Spark、Python怎么配对版本选型是 Windows 环境下容易出问题的第一关。网上很多教程只告诉你安装最新版但 Spark、Java、Python 之间是有兼容关系的版本不匹配会以各种诡异报错的形式表现出来。下面这是我实测下来比较稳定的一套组合也是我目前每天在用的组件推荐版本说明JDKJava 8 或 11Spark 3.x 均支持优先选 11Python3.8 3.11Spark 3.5 系列官方支持范围Spark3.5.x尽量选最新稳定小版本Hadoop库3.3.x对应 Spark 预编译的 hadoop3 版本PySpark与 Spark 版本一致例如 3.5.1 对应 pyspark3.5.1不建议装 Java 17 以上的版本跑 Spark 3.4 或更早的版本因为部分反射调用会报模块访问错误。至于 Python太新的版本比如 3.13往往不在 PySpark 的官方支持列表中除非你有明确需求否则不要用最新版去做 Spark 开发。1.3 选pip还是选发行包两条路线的取舍搭建 PySpark 环境有两条常见路线这两条路线各有各的适用场景不用太纠结路线 Apip install pyspark。这个包内部自带 Spark jar 和 Python API装完就能在 Python 里直接写代码最快最省事。它适合以 Python 为主、不需要 Scala 交互或 spark-submit 标准提交的本地开发场景。路线 B下载spark-x.y.z-bin-hadoop3.tgz发行包解压使用。这种方式能获得完整的pyspark命令、spark-shell、spark-submit等所有 Spark 内置工具适合需要完整调试环境、又想对比 Scala 和 Python 行为的人。我自己在实际开发中选择了路线 B同时在 Conda 环境里对齐安装对应版本的 pyspark。这样既能使用spark-submit提交任务又能在 Jupyter 和 VSCode 中以 Python 包的形式无缝导入 pyspark。如果你只做简单数据处理路线 A 完全够用可以省掉很多环境变量配置的麻烦。2. JDK、Hadoop本地库与winutilsWindows专属三件套2.1 JDK版本其实比你想的更敏感JDK 是第一步也是后续报错的一个隐藏来源。很多人会习惯性打开命令行执行java -version看到有版本就认为 Java 已经装好其实这一步在 Windows 上经常有陷阱。先说安装环节。我建议直接安装 .msi 格式的 JDK 安装包这样可以自动处理注册表信息比 zip 解压版少很多问题。安装路径不要放在C:\Program Files下因为这个路径带空格虽然 Spark 通常能处理但目前没有空间路径出各种毛病的先例。我自己习惯装在D:\Java\jdk-11.0.21这种简洁目录下。安装完成后需要手动配置系统环境变量新建系统变量JAVA_HOME值为 JDK 的安装根目录不要把\bin写进去。在Path变量中新增%JAVA_HOME%\bin。重新打开命令行依次执行java -version和javac -version确认两个命令都能正常输出版本号。这里特别提醒只验证java -version不够还要验证javac -version。如果javac找不到说明 JAVA_HOME 配置有问题运行 spark-shell 时会直接报 找不到或无法加载主类 之类的错误。另外确保 JAVA_HOME 指向的是 JDK 而非 JRE有些安装包会同时提供 JRE如果你之前手动配置过 JRE 路径运行 Spark 时会出现A JNI error has occurred的报错。2.2 没有winutils.exe会怎样winutils.exe是 Hadoop 在 Windows 平台上提供的一个辅助工具负责处理文件权限、本地文件系统操作等 Windows 特有的兼容问题。Spark 在 local 模式下启动时底层的 Hadoop 本地库会尝试加载这个工具如果找不到就会在日志中打印错误信息。这个报错出现得非常早通常在创建 SparkSession 的时候就会暴露。一旦缺少它你在 Python 中执行SparkSession.builder.getOrCreate()时控制台会打印类似这样的日志Failed to locate the winutils binary in the Hadoop binary directory java.io.IOException: Could not locate executable null\bin\winutils.exe in the Hadoop binaries.需要强调一下这个错误虽然不一定会直接中断 SparkSession 的创建但后续对本地文件系统的某些操作可能表现异常比如临时文件清理、读写本地路径权限校验等属于那种看似不影响启动、实则非常影响运行的隐性坑。所以不要心存侥幸该配就要配好。2.3 放置winutils并配置HADOOP_HOME解决方式其实很简单下载一个与 Spark 内置 Hadoop 版本匹配的 winutils.exe放到一个目录里然后配置HADOOP_HOME环境变量指向这个目录。具体步骤如下在 GitHub 上找到cdarlint/winutils这个开源仓库里面按 Hadoop 版本整理了对应的工具选择hadoop-3.3.x目录。下载该目录下bin子目录中的winutils.exe同时建议把hadoop.dll也下载下来。在磁盘上新建一个目录比如D:\hadoop\bin将下载好的winutils.exe和hadoop.dll放进去。新建系统变量HADOOP_HOME值为D:\hadoop。在Path变量中新增%HADOOP_HOME%\bin。配置完成后在命令行中直接输入winutils并回车如果出现正常的帮助信息而不是不是内部或外部命令说明环境已生效。注意修改环境变量后要重新打开命令行窗口否则不会加载新的配置。3. Spark安装与Python绑定目录、环境变量与第一次启动3.1 解压目录的规范无中文、无空格是底线从 Spark 官网下载spark-3.5.x-bin-hadoop3.tgz后解压到哪个目录直接决定了你后面会不会遇到一些莫名其妙的坑。我的建议是目录路径中不要包含中文、空格和特殊符号。虽然 Windows 上很多软件对路径要求没那么严格但 Spark 底层的 Hadoop 库对路径解析在某些场景下很敏感尤其是后续做文件读写和临时目录清理时中文路径或空格路径容易出现编码或解析问题。我自己的安装路径是D:\spark\spark-3.5.1-bin-hadoop3简洁明了。另外解压时建议使用支持长路径的工具比如 7-ZipWindows 自带的资源管理器解压有时候会因为路径过长报错这也是一个小坑。3.2 环境变量配置清单解压完成后需要配置以下环境变量变量名示例值作用SPARK_HOMED:\spark\spark-3.5.1-bin-hadoop3Spark 安装根目录PYSPARK_PYTHOND:\Miniconda3\envs\spark\python.exe指定 PySpark 使用的 Python 解释器PYSPARK_DRIVER_PYTHON同上或 jupyter指定 Driver 端 Python 解释器Path追加%SPARK_HOME%\bin使pyspark、spark-submit命令可用PYSPARK_PYTHON是这个环节最需要注意的变量。如果你不显式设置它PySpark 在启动 Worker 进程时会用默认的python命令去解析解释器路径一旦系统里存在多个 Python比如 Anaconda 和系统默认 Python 并存就会出现 Worker 端 Python 版本与 Driver 端不一致的问题。这个我们在第五章会详细展开。如果你使用 Conda 管理 Python 环境建议先创建独立环境conda create -n spark python3.10 -y conda activate spark然后获取环境中的python.exe路径填入PYSPARK_PYTHON。这样做的好处是Spark 运行所依赖的 Python 包互不干扰即使你在其他环境里装了冲突版本的依赖也不会影响 Spark 的开发环境。3.3 第一次启动pyspark与spark-shell环境变量配置完成后先别急着写代码先做两个基础验证。第一个验证在新开的命令行里输入spark-shell确认 Spark 的 Scala 接口能正常启动。如果这一步能进入 Scala 交互式界面并且没有报错说明 JAVA_HOME、SPARK_HOME、winutils 这些底层配置都是通的。第二个验证输入pyspark确认 Python 交互式界面能正常启动。如果看到类似 Python 交互式命令行的界面并且能输入代码说明 Python 绑定也没问题。注意这里如果 Python 启动的是 Jupyter Notebook说明你之前设置了PYSPARK_DRIVER_PYTHONjupyter这在后面集成 Jupyter 时是期望行为但在验证基础环境时可能会造成困惑建议基础验证阶段先不配这个变量。在pyspark交互界面中可以快速试一段代码spark.range(1, 5).show()如果正常输出 4 行数字Spark 环境就已经完全跑通了。这一步我建议每个人都做一遍因为它能一次性覆盖 90% 的配置正确性问题。4. PySpark跑起来的几种方式命令行、Jupyter和VSCode4.1 SparkSession初始化的最小代码环境跑通之后接下来的问题就是在 PySpark 代码里到底怎么初始化才是对的很多教程会直接写SparkSession.builder.appName(test).getOrCreate()这在某些场景下能跑通但如果你不显式指定masterSpark 会从环境变量和配置文件里猜运行模式一旦猜错就会出现无法连接等问题。我的习惯是在本地开发中始终显式指定运行模式from pyspark.sql import SparkSession spark SparkSession.builder \ .appName(windows-local-test) \ .master(local[*]) \ .config(spark.sql.shuffle.partitions, 4) \ .getOrCreate()这里的local[*]表示使用本机所有可用 CPU 核心来运行如果你希望限制资源可以写成local[2]只使用 2 个核心。显式指定master的价值在于你的代码在本地和集群之间切换时只需要改这一行不会被隐式行为误导。4.2 Jupyter Notebook里跑PySpark的正确姿势在 Jupyter Notebook 中使用 PySpark 有两种常见方式它们的适用场景不太一样。方式一是从命令行启动 Jupyter同时让 PySpark 自动注入 SparkSessionset PYSPARK_DRIVER_PYTHONjupyter set PYSPARK_DRIVER_PYTHON_OPTSnotebook pyspark这种方式下Jupyter 里会自动存在一个spark变量直接使用即可。但它的问题在于你每次启动都必须通过 pyspark 命令来进入 Jupyter无法直接像平时那样打开任意 notebook 再按需初始化。方式二更适合日常开发在 notebook 内部手动初始化 SparkSession。这时你只需要在代码里先设置环境变量然后再导入 pyspark 并初始化import os os.environ[SPARK_HOME] rD:\spark\spark-3.5.1-bin-hadoop3 os.environ[PYSPARK_PYTHON] rD:\Miniconda3\envs\spark\python.exe os.environ[HADOOP_HOME] rD:\hadoop from pyspark.sql import SparkSession spark SparkSession.builder.master(local[*]).getOrCreate()注意这里设置环境变量的代码必须在from pyspark.sql import SparkSession之前执行因为 PySpark 在导入时就会读取这些环境变量。如果你先导入了 pyspark 再设置环境变量看起来很合理但实际上已经不生效了这也是一个很容易踩的坑。4.3 VSCode里调试PySpark的注意事项在 VSCode 中调试 PySpark 代码时主要问题是 Python 解释器的选择。我的建议是打开命令面板选择已经安装了 pyspark 的那个 conda 环境的解释器。好处的第一层是语法提示和代码补全完全正确第二层是调试时的 Python path 就是开发环境的 path不会与其他环境混淆。另外在.vscode/launch.json中可以设置好环境变量这样按 F5 调试时就不需要每次手动设置{ version: 0.2.0, configurations: [ { name: Python: Spark Debug, type: debugpy, request: launch, program: ${file}, console: integratedTerminal, env: { SPARK_HOME: D:\\spark\\spark-3.5.1-bin-hadoop3, HADOOP_HOME: D:\\hadoop, PYSPARK_PYTHON: D:\\Miniconda3\\envs\\spark\\python.exe } } ] }这里有一个关键点env中的路径要写双反斜杠否则 JSON 解析会将\\识别为转义字符导致路径错误。4.4 本地模式与真正的集群模式差了哪些东西写到这里顺带解答一个高频疑问我在 Windows 上把 local 模式跑通了能直接把它当作生产集群环境吗答案很明确不能。本地模式只是方便开发和验证它的 Executor 和 Driver 在同一个进程中无法体现真正的分布式计算能力。生产环境通常运行在 Linux 的多节点集群上通过集群管理器来调度资源。Windows 上做的环境搭建更多是为了学习、开发和调试代码真正提交生产任务时代码逻辑可以复用但运行环境和资源参数需要重新适配。另外补充一点如果你的机器上装有 Docker Desktop也可以考虑用容器跑 Spark。但我不太建议在 Windows 上为了玩 Spark 而再引入 Docker因为文件挂载、端口映射和资源限制这些配置也需要单独理解一套体系对于刚上手 PySpark 的人等于多了一个变量。先把 local 模式跑透才是性价比最高的路径。5. Windows下最常见的PySpark报错排查链路5.1 报错一Failed to locate the winutils binary这个报错我在前面已经提过这里把完整的排查链路梳理一下。错误信息通常是Failed to locate the winutils binary in the Hadoop binary directory java.io.IOException: Could not locate executable null\bin\winutils.exe in the Hadoop binaries.排查链路先确认HADOOP_HOME环境变量是否已设置且指向的目录下确实存在bin\winutils.exe。再确认Path中是否已加入%HADOOP_HOME%\bin。检查 winutils 版本是否与 Spark 内置 Hadoop 版本大体匹配一般选择 3.3.x 即可。重新打开命令行手动执行winutils验证工具是否可访问。这个报错也有一种特殊情况日志中显示null\bin\winutils.exe说明 HADOOP_HOME 没有正确传递到 Java 进程。此时除了检查环境变量之外还可以尝试在代码中显式设置os.environ[HADOOP_HOME] rD:\hadoop5.2 报错二Python in worker has different version这个报错通常在 SparkContext 初始化时出现具体信息大致是Python in worker has different version 3.9 than that in driver 3.10, PySpark cannot run with different minor versions.这说明 Driver 端和 Worker 端使用了不同版本的 Python。在 Windows 上这种情况非常常见因为系统里往往安装了多个 Python官网 Python、Anaconda、Miniconda、虚拟环境等。排查链路确认PYSPARK_PYTHON环境变量的值是否指向了你希望使用的 Python 解释器。确认sys.executable在 Python 中输出的路径是否与PYSPARK_PYTHON一致。如果使用了 Conda 环境还要在激活环境中检查当前python命令指向的解释器。症状的本质是Spark 在启动 Worker 进程时默认使用PYSPARK_PYTHON指定的解释器启动 Python 子进程而 Driver 端是当前 Python 进程。如果两者路径不一致就会导致版本检查失败。解决方式就是让这两个路径保持一致或者都指向同一个解释器。5.3 报错三A JNI error has occurred / 找不到JAVA_HOMEA JNI error has occurred是 Windows 上 Spark 启动时一个存在感极强的报错很多用户遇到过但往往搞不清楚原因。常见原因有两个一是 JAVA_HOME 指向了 JRE 而非 JDK二是 JDK 的位数32位/64位与系统位数不匹配。在 64 位 Windows 上一定要安装 64 位的 JDK否则 JNI 调用会直接失败。排查链路在命令行执行echo %JAVA_HOME%确认路径正确指向 JDK 安装根目录。进入%JAVA_HOME%\bin目录确认存在javac.exe如果只有java.exe说明配置指向的是 JRE。执行java -version和javac -version确认 Java 可以正常编译。如果这些都没问题尝试卸载 Java 后重装并确保安装时勾选了正确的 JDK 组件。5.4 报错四Spark UI页面打不开或4040端口被占Spark 在 local 模式下默认会在http://localhost:4040启动 Web UI用来查看作业执行情况。如果你的 4040 端口被其他程序占用Spark UI 会尝试使用 4041以此类推。当你在代码中创建了多个 SparkSession比如同一个脚本中重复执行了 Cell旧的 UI 端口可能没有及时释放就会出现 4040 页面打不开的情况。排查方式比较直观在命令行执行netstat -ano | findstr 4040查看端口占用情况。如果确认端口被占用可以修改 Spark 配置指定一个不常用的端口spark SparkSession.builder \ .config(spark.ui.port, 4050) \ .getOrCreate()这里还要提醒一下在 Jupyter 中反复执行同一个 Cell 会导致创建多个 SparkContext容易引发端口冲突和内存堆积。开发时的安全做法是先执行spark.stop()释放资源再重新初始化。5.5 报错五读取文件时路径分隔符和中文乱码Windows 的路径分隔符是反斜杠\而 Spark 内部很多路径解析逻辑遵循 Unix 风格的正斜杠/。在读取文件时如果直接写 Windows 路径df spark.read.csv(D:\data\test.csv, headerTrue)在 Python 字符串中\d、\t会被当作转义字符处理导致路径解析异常。正确的写法有两种使用原始字符串或者将反斜杠替换为正斜杠df spark.read.csv(rD:\data\test.csv, headerTrue) # 或者 df spark.read.csv(D:/data/test.csv, headerTrue)我习惯统一使用正斜杠因为这样在 Linux 和 Windows 之间切换代码时路径逻辑基本不用改。另外如果你的文件路径包含中文建议先检查文件编码尽量使用 UTF-8 编码的路径并且设置spark.sql.session.timeZone默认值避免时区和编码问题交叉影响。5.6 报错六Python worker failed to connect back这个报错的完整信息大概是Python worker failed to connect back.它出现的原因比较多在 Windows 上最常见的有两种。第一种是防火墙拦截了 Python 进程的本地通信Spark 在 Driver 和 Worker 之间会建立本地 socket 连接如果 Windows Defender 防火墙拦截了 python.exe 或 java.exe 的通信就会导致连接失败。第二种是本地 hostname 解析异常Spark 会尝试连接本机的主机名如果 hosts 文件中没有对应的127.0.0.1条目就会出现连接不上。排查链路在命令行执行ping 你的主机名如果能解析到 IP 则说明 hostname 正常。打开C:\Windows\System32\drivers\etc\hosts确认其中是否包含127.0.0.1 localhost以及你的完整主机名。在 Windows 防火墙中暂时允许 python.exe 和 java.exe 的入站连接测试是否为防火墙拦截。如果问题依旧尝试在代码中显式设置spark.driver.host127.0.0.1spark SparkSession.builder \ .master(local[*]) \ .config(spark.driver.host, 127.0.0.1) \ .getOrCreate()这个设置在实际排查中非常有效它强制 Spark 使用 localhost 回环地址进行通信绕开 hostname 解析问题。6. 本地调优和一份可直接运行的验证脚本6.1 本地模式调参的重灾区shuffle partitionsSpark 的默认配置spark.sql.shuffle.partitions是 200这个参数在集群中通常没问题但在本地模式下却很容易拖垮性能。原因很简单本地模式下你的资源就是一台机器而 200 个 shuffle 分区意味着会产生 200 个任务其中大部分任务只是处理极小的一份数据任务调度的开销反而超过了计算本身。在本地开发时我通常会把 shuffle 分区数调整到 4 到 8spark SparkSession.builder \ .appName(windows-local-test) \ .master(local[*]) \ .config(spark.sql.shuffle.partitions, 4) \ .getOrCreate()这里有一组我实测的对比数据在本地处理 200MB 的 CSV 数据做分组聚合时使用默认 200 个分区耗时大约 46 秒调成 4 个分区后耗时降到 12 秒左右差距非常明显。6.2 driver内存与executor内存的取舍本地模式下Driver、Executor 和 Worker 都运行在同一台机器上所以内存分配更多是在系统总内存和你 Spark 程序之间做一个权衡。如果配置不当最常见的问题是Spark 程序还没跑完系统内存满了电脑卡死。我的建议是在小数据集上开发时spark.driver.memory设为 2g 就够不要贪多。在创建 SparkSession 时可以通过配置指定spark SparkSession.builder \ .master(local[*]) \ .config(spark.driver.memory, 2g) \ .getOrCreate()如果在命令行启动 pyspark可以在启动参数中指定pyspark --driver-memory 2g另外要正确理解一个概念在 local 模式下spark.executor.memory的实质意义有限因为并没有独立的 Executor JVM。真正起作用的是 Driver 的堆内存和操作系统层面的可用内存。所以不要一上来给 executor 配 8g那样往往只是白白预留了一段不会用到的内存。6.3 把临时文件和日志挪出C盘Spark 在运行过程中会产生大量临时文件shuffle 数据、中间结果等默认情况下这些临时文件会写入系统的临时目录在 Windows 上通常就是 C 盘。如果你的 C 盘空间不大跑一次稍大的作业可能就会把 C 盘塞满。调优方式是通过配置spark.local.dir指定临时目录的位置spark SparkSession.builder \ .master(local[*]) \ .config(spark.local.dir, D:/spark/tmp) \ .getOrCreate()提前创建好D:\spark\tmp目录再将日志级别调整为 WARN可以避免控制台被大量的 INFO 日志刷屏。修改方式是在%SPARK_HOME%\conf目录下将log4j2.properties.template复制为log4j2.properties将其中rootLogger.level的值改为WARN。6.4 一个完整的数据分析验证脚本环境搭好后建议用一份真实数据的简单分析脚本做验收它能同时检验环境配置、文件读取、数据转换、shuffle 操作和结果输出是否全部正常。我每天在 Windows 上跑通的第一个脚本大致如下import os os.environ[SPARK_HOME] rD:\spark\spark-3.5.1-bin-hadoop3 os.environ[HADOOP_HOME] rD:\hadoop os.environ[PYSPARK_PYTHON] rD:\Miniconda3\envs\spark\python.exe from pyspark.sql import SparkSession from pyspark.sql import functions as F spark SparkSession.builder \ .appName(windows-spark-verify) \ .master(local[*]) \ .config(spark.sql.shuffle.partitions, 4) \ .config(spark.driver.memory, 2g) \ .config(spark.local.dir, D:/spark/tmp) \ .getOrCreate() # 生成一份模拟数据做验证 data [ (apple, 10), (banana, 20), (apple, 15), (banana, 5), (cherry, 30), (cherry, 25) ] df spark.createDataFrame(data, [category, amount]) df.show() result df.groupBy(category) \ .agg(F.sum(amount).alias(total_amount)) \ .orderBy(F.desc(total_amount)) result.show() spark.stop()这个脚本如果能够顺利执行并输出apple25、banana25、cherry55这三行结果就说明你的 Windows Python Spark 环境已经达到可以日常开发的状态。最后分享一个我在日常开发里坚持的检验清单每次换机器、换版本或者重装环境后不要急着跑大作业先花两分钟跑一遍上述验证脚本如果这个脚本都过不了说明问题出在环境本身不要浪费时间去排查业务代码。环境搭建这件事一次配好长期收益花点时间把细节搞清楚后面的开发效率才能提上来。
返回列表