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 集群部署时的大多数依赖问题都能提前规避。

相关推荐
心中有国也有家10 小时前
GE图引擎深度解析——CANN的计算图优化与执行引擎
人工智能·pytorch·python·学习·numpy
卷毛的技术笔记11 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
编程大师哥11 小时前
匿名函数 lambda + 高阶函数
java·python·算法
isyangli_blog12 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb20081112 小时前
FastAPI APIRouter
开发语言·python
Benszen12 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆12 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木12 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
adrninistrat0r12 小时前
Java调用链MCP分析工具
java·python·ai编程
杨充12 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法