PySpark 依赖管理集群环境下如何分发 Python 包

1. 为什么 PySpark 需要做依赖管理

当 PySpark 程序运行在集群上时,Executor 也要执行 Python 代码。如果你的程序依赖 pandaspyarrow 之类的第三方库,那么这些库必须在每个 Executor 上都能被找到,否则就会报错,例如:

text 复制代码
ModuleNotFoundError: No module named 'pyarrow'

这类问题在使用 Pandas UDF 时尤其常见,因为底层依赖 pyarrow

2. 一个典型示例

下面这段官方示例代码使用了 Pandas UDF,因此需要在 Driver 和 Executor 上都具备 pandaspyarrow 相关依赖。

python 复制代码
import pandas as pd
from pyspark.sql.functions import pandas_udf
from pyspark.sql import SparkSession

def main(spark):
    df = spark.createDataFrame(
        [(1, 1.0), (1, 2.0), (2, 3.0), (2, 5.0), (2, 10.0)],
        ("id", "v")
    )

    @pandas_udf("double")
    def mean_udf(v: pd.Series) -> float:
        return v.mean()

    print(df.groupby("id").agg(mean_udf(df["v"])).collect())

if __name__ == "__main__":
    main(SparkSession.builder.getOrCreate())

3. PySpark 依赖管理的几种方式

官方文档总结了几种主要方案:

  • PySpark 原生方式
  • Conda
  • Virtualenv
  • PEX

它们的核心差别在于:能否打包解释器、能否打包带原生代码的依赖、是否适合大规模集群环境。

4. 方式一:使用 PySpark 原生能力

PySpark 可以通过以下方式把 Python 代码分发到 Executor:

  • 配置 spark.submit.pyFiles
  • 使用 --py-files
  • 在程序中调用 SparkContext.addPyFile()

4.1 常见用法

bash 复制代码
spark-submit --py-files deps.zip app.py

或者在代码里:

python 复制代码
spark.sparkContext.addPyFile("deps.zip")

4.2 适用场景

这种方式适合分发:

  • 单个 .py 文件
  • .zip 打包的 Python 包
  • .egg 文件

4.3 局限性

它不能分发 Wheel,也不适合携带带有本地编译代码的依赖,因此像某些依赖 C/C++ 扩展的包就不太适合这种方式。官方明确指出,这种方式不支持包含原生代码依赖的场景。

5. 方式二:使用 Conda

Conda 是最常见的 Python 包管理工具之一。官方推荐使用 conda-pack 把整个 Conda 环境打包后分发到 Driver 和 Executor。这样不仅带上依赖,还能带上 Python 解释器本身。

5.1 构建环境

bash 复制代码
conda create -y -n pyspark_conda_env -c conda-forge pyarrow pandas conda-pack
conda activate pyspark_conda_env
conda pack -f -o pyspark_conda_env.tar.gz

5.2 使用 spark-submit 提交

bash 复制代码
export PYSPARK_DRIVER_PYTHON=python
export PYSPARK_PYTHON=./environment/bin/python
spark-submit --archives pyspark_conda_env.tar.gz#environment app.py

5.3 在普通 Python Shell 或 Notebook 中使用

python 复制代码
import os
from pyspark.sql import SparkSession
from app import main

os.environ["PYSPARK_PYTHON"] = "./environment/bin/python"

spark = SparkSession.builder.config(
    "spark.archives",
    "pyspark_conda_env.tar.gz#environment"
).getOrCreate()

main(spark)

5.4 在 pyspark shell 中使用

bash 复制代码
export PYSPARK_DRIVER_PYTHON=python
export PYSPARK_PYTHON=./environment/bin/python
pyspark --archives pyspark_conda_env.tar.gz#environment

5.5 注意事项

官方特别说明:在 YARN 或 Kubernetes 的 cluster mode 下,不要设置 PYSPARK_DRIVER_PYTHON

6. 方式三:使用 Virtualenv

Virtualenv 也是常见的 Python 隔离环境方案。PySpark 可以配合 venv-pack 打包虚拟环境,并通过 --archivesspark.archives 分发到 Executor。

6.1 构建虚拟环境

bash 复制代码
python -m venv pyspark_venv
source pyspark_venv/bin/activate
pip install pyarrow pandas venv-pack
venv-pack -o pyspark_venv.tar.gz

6.2 spark-submit 提交方式

bash 复制代码
export PYSPARK_DRIVER_PYTHON=python
export PYSPARK_PYTHON=./environment/bin/python
spark-submit --archives pyspark_venv.tar.gz#environment app.py

6.3 Notebook / Python Shell 用法

python 复制代码
import os
from pyspark.sql import SparkSession
from app import main

os.environ["PYSPARK_PYTHON"] = "./environment/bin/python"

spark = SparkSession.builder.config(
    "spark.archives",
    "pyspark_venv.tar.gz#environment"
).getOrCreate()

main(spark)

6.4 pyspark shell 用法

bash 复制代码
export PYSPARK_DRIVER_PYTHON=python
export PYSPARK_PYTHON=./environment/bin/python
pyspark --archives pyspark_venv.tar.gz#environment

6.5 注意事项

官方说明,venv-pack 打包的 Python 解释器是符号链接,因此集群中所有节点都需要安装相同的 Python 解释器。也就是说,Virtualenv 方式对节点环境一致性要求更高。

7. 方式四:使用 PEX

PEX 是另一种常见方案,它会把依赖打成一个 .pex 可执行文件。与 Conda 和 Virtualenv 不同,PEX 文件本身不包含 Python 解释器,因此集群上所有节点也要有相同版本的 Python。

7.1 构建 PEX 文件

bash 复制代码
pip install pyarrow pandas pex
pex pyspark pyarrow pandas -o pyspark_pex_env.pex

7.2 验证 PEX 文件

bash 复制代码
./pyspark_pex_env.pex -c "import pandas; print(pandas.__version__)"

7.3 spark-submit 提交方式

bash 复制代码
export PYSPARK_DRIVER_PYTHON=python
export PYSPARK_PYTHON=./pyspark_pex_env.pex
spark-submit --files pyspark_pex_env.pex app.py

7.4 Notebook / Python Shell 用法

python 复制代码
import os
from pyspark.sql import SparkSession
from app import main

os.environ["PYSPARK_PYTHON"] = "./pyspark_pex_env.pex"

spark = SparkSession.builder.config(
    "spark.files",
    "pyspark_pex_env.pex"
).getOrCreate()

main(spark)

7.5 pyspark shell 用法

bash 复制代码
export PYSPARK_DRIVER_PYTHON=python
export PYSPARK_PYTHON=./pyspark_pex_env.pex
pyspark --files pyspark_pex_env.pex

7.6 注意事项

因为 .pex 是普通文件,而不是目录或压缩包,所以它需要通过 --filesspark.files 传输,而不是 --archives

8. 四种方案怎么选

如果只是分发你自己的少量 Python 代码,原生 --py-files 最简单。

如果需要完整、可迁移的 Python 运行环境,Conda 会更稳。

如果团队已经统一使用 virtualenv,也可以用 Virtualenv + venv-pack

如果你想把依赖收敛成一个单独文件,PEX 会更方便。

更直白一点:

  • 只分发纯 Python 代码 :优先 --py-files
  • 依赖复杂,包含解释器和环境:优先 Conda
  • 已有 virtualenv 体系:可选 Virtualenv
  • 希望单文件分发:可选 PEX

9. 总结

PySpark 在集群环境中的依赖管理,本质上是在解决一个问题:如何让 Driver 和所有 Executor 拥有一致的 Python 运行环境。官方文档给出的四种方案中,没有绝对最好的选项,只有更适合当前场景的方案。简单场景可以用原生方式,复杂依赖环境更适合 Conda 或 Virtualenv,追求单文件交付则可以考虑 PEX。只要这个思路想清楚,PySpark 集群部署时的大多数依赖问题都能提前规避。

相关推荐
兵慌码乱7 小时前
基于Python+PyQt5+SQLite的药房管理系统实现:事务一致性与界面解耦全流程解析
python·sqlite·信号与槽·pyqt5·数据库设计·桌面应用开发·事务处理
金銀銅鐵8 小时前
[Python] 体验用欧几里得算法计算最大公约数的过程
python·数学
FreakStudio12 小时前
W55MH32L-EVB 上手测评:硬件 TCP/IP 加持的以太网单片机,MicroPython 零门槛开发
python·单片机·嵌入式·大学生·面向对象·并行计算·电子diy·电子计算机
用户03321266636713 小时前
使用 Python 从零创建 Word 文档
python
Csvn18 小时前
Python 两大经典坑点 —— 可变默认参数 & 闭包延迟绑定
后端·python
曲幽19 小时前
别再用网页翻译看源码了!你的私人翻译神器LibreTranslate,部署避坑指南来了
python·docker·web·pot·translate·libretranslate·arogstranslate
用户5569188175320 小时前
#从脚本到独立程序:Python + Playwright 批量抓取的完整踩坑记录
python·自动化运维
兵慌码乱1 天前
基于 MediaPipe 与 PySide2 的手势交互音乐控制系统实现:轻量化视觉交互全流程解析
python·opencv·计算机视觉·人机交互·手势识别·mediapipe·pyside2
luckdewei2 天前
FastAPI 资产管理系统实战:复杂 ORM 关联、Alembic 迁移与 N+1 查询优化
python