为何Cython编译的代码在Spark Standalone和YARN client模式正常,YARN cluster模式报错
问题原理说明
1. 部署模式的本质差异
你遇到的表现差异核心来自不同模式下Driver进程的运行位置与序列化执行上下文的区别:
- Spark Standalone、YARN Client模式:Driver运行在你提交作业的本地节点,序列化操作在本地已经完成Cython模块加载的Python进程中执行。cloudpickle(PySpark默认的序列化器)对于当前进程内存中已加载的Cython闭包函数,会通过内存上下文补充查找路径,可正常完成序列化,发送到Executor端后,因为所有节点都安装了同版本myapi库,Executor可正常反序列化执行。
- YARN Cluster模式:Driver被YARN调度到集群任意工作节点全新启动,Python进程冷加载Cython版myapi库后再执行序列化逻辑。Cython编译后的局部嵌套lambda函数不会在模块全局命名空间注册对应的属性,也没有保留Python原生lambda的完整元信息,cloudpickle无法通过模块属性查找定位到对应的lambda定义,直接抛出你遇到的
pickle.PicklingError错误。
2. Cython编译导致序列化失败的根因
你代码中change_it函数内部定义的a~f共6个lambda,以及compute方法中reduce操作的lambda都是局部嵌套函数:
- 纯Python模式下,cloudpickle支持序列化嵌套函数,会自动捕获闭包上下文完成序列化
- Cython编译后,这类局部lambda会被转换为C级别的cyfunction,默认不会生成
__module__、__qualname__等pickle所需的元信息,也不会在myapi模块的全局属性表中留下可查找的引用路径,pickle执行属性查找时直接失败。
修复方案参考
- 方案1:把所有lambda替换为全局定义的普通函数,避免在函数内部嵌套定义匿名函数
- 方案2:编译Cython库时开启binding编译指令,为cython函数保留完整Python元信息,你的setup.py中cythonize配置可修改为:
from setuptools import setup from Cython.Build import cythonize setup( ext_modules = cythonize( "myapi/**/*.py", compiler_directives={ "binding": True, "always_allow_keywords": True } ) )
- 方案3:提交YARN Cluster作业时添加参数
--conf spark.pyspark.serializer=org.apache.spark.serializer.PythonSerializer,强制使用cloudpickle的全上下文序列化逻辑(该方案会增加序列化开销)
内容的提问来源于stack exchange,提问作者Jane Wayne
相关产品推荐
相关产品推荐

