这一批坑有个共同形状:每一步都返回成功,坏的东西要到第一次使用时才暴露。等到 PySpark 报错时,我已经把注意力放到 Spark 上了。

01./configure 不会因为少装一个 -devel 包而失败

编译前的第一道坎是真缺编译器:no acceptable C compiler found in $PATH,装 gcc make 之后过了。但接下来的问题不是“过不去”,而是“过得很顺”——configure 对 OpenSSL、zlib、libffi 这些依赖是可选检测:找不到头文件就把对应模块从这次构建里剔除,继续走完,退出码还是 0。

于是四个模块分别把症状推迟到四个不同的时间点:

text
缺 openssl-devel  → pip 连 HTTPS:  SSL module is not available
缺 zlib-devel     → pip 装包:      can't decompress data; zlib not available
缺 libffi-devel   → PySpark 启动:  ModuleNotFoundError: No module named '_ctypes'

pip install --upgrade pip 那次也一样:报的还是 SSL module is not available,但看起来像 pip 的问题,不像解释器的问题。

机制上这是同一件事:_ssl / _zlib / _ctypes 都是 C 扩展模块,configure 检测不到库就不编它们;解释器本身完整可用,所以没人报错。少一个 -devel 头文件包,代价是“装完之后重编一遍”。

02依赖链的形状:补一个库要整轮重编

我最后是这么收的:yum install -y openssl-devel zlib-devel libffi-devel,然后重新 configure + make。中途还试过用 pip 的独立脚本给系统解释器补 pip(get-pip.py),因为发行版自带的 Python 里 python3 -m pip 直接是 No module named pip。

这一段的教训不是“要多装几个包”,而是这类故障的成本结构和普通 bug 不一样:报错点在运行期,根因在构建期,中间隔着一次完整编译。我把三个 -devel 一次补齐之前,重复了三轮“编译—试用—发现还缺”。

03第二个解释器:pip 装到了另一个 Python 上

真正耗我时间更多的不是编译,是机器上同时存在两个可用的 Python,而 PATH 上的 python3 和 Spark 用的解释器不是同一个:

bash
pip install numpy            # 装进了自编的那个解释器的 site-packages
# PySpark 起 executor 时报 No module named 'numpy'

pyspark 脚本按它自己的环境找解释器,找到的是系统那个;我装包用的是 PATH 上第一个 pip。两边都没错,但装的东西到不了运行时。修法是永远绑定解释器而不是绑定命令名:

bash
<目标解释器> -m pip install numpy

这条现在看着平淡,但它和上面那节是同一个原则的两次出现:“我装的”和“运行时会用的”是两个不同的地址空间。Flask 起不来那次我也绕了远路——它和 debug=True 都依赖 _ctypes,最后在系统解释器上跑起来、并把 reloader 关掉才干净。

04同一类沉默:Spark 自带的两个 Jackson 互相不认

集群上 Spark 起 SQL 就报:

text
com.fasterxml.jackson.databind.JsonMappingException:
Scala module 2.15.2 requires Jackson Databind version >= 2.15.0 and < 2.16.0
  - Found jackson-databind version 2.12.7-1

第一反应是“我的 fat jar 里带了旧 databind”。实际相反:是 Spark 发行版自带的两个 jar 版本不匹配——jackson-module-scala 是新的,jackson-databind 还是旧的,而 module-scala 在初始化时会硬校验 databind 的版本区间。那个带 -1 后缀的版本串也提示它来自发行版打包,不是 Maven Central 上的常规坐标。

所以修的位置不在项目依赖里,而在集群运行时:替换 Spark jars 目录里的那只 jar。这件事我后来在同类依赖上复用成一条判读规则——报“版本区间不满足”时,先看冲突双方是不是同一个发行版一起装进来的;如果是,改构建配置永远改不动它。Deequ 必须带 -spark-3.5 这个构建后缀也是同一族问题:不匹配的是坐标里的构建目标,而不是版本号。

05事后加的检查

这类故障没法靠“装完了”来确认,只能靠“用到的第一样东西”来确认。我把环境交付的检查压成一条:

bash
<解释器> -c "import ssl, zlib, ctypes; print(ssl.OPENSSL_VERSION)"

能打印出来,就说明三个 C 扩展都编进去了、而且解释器就是我以为的那个。这比 python -V 有用得多。

06还没做到的

  • 没有自动化环境前置检查。 这几个依赖现在还是靠人记住,换一台机器就要重跑一遍判断。合理形态是把它们写进一个安装脚本并按字节/按导入双重验证,我还没做。
  • 多解释器共存没有收敛。 我的处置是“改用系统解释器”,但机器上那个自编解释器仍在 PATH 可达范围内,同类错配还可能再发生。
  • Jackson 的修复没有留版本记录。 替换过 jars 目录这件事只存在于当时的操作里,没有落成“哪只 jar、什么版本、为什么”的清单,下次重装集群要靠重踩。

07相关文章