2025 年 5 月 23 日,Apache Spark 4.0 正式发布。作为 4.x 系列的首个版本,它汇聚了超过 390 名贡献者的智慧,解决了数千个 JIRA Issue,堪称自 Spark 2.0 以来最具变革意义的一次大版本跃迁。

但与任何大版本升级一样,Spark 4 并非"无痛升级"。它在带来 20-50% 性能提升的同时,也引入了多项破坏性变更——默认开启 ANSI SQL 模式、强制要求 JDK 17、放弃 Scala 2.12 支持、移除 Mesos 调度器。如果你的生产管道正在 Spark 3.x 上运行,盲目升级几乎必然导致运行时异常甚至作业失败。

本文并非官方文档的简单翻译,而是结合多方信息源和实战经验,对 Spark 3 到 Spark 4 的迁移进行一次系统性梳理。我们将从"必须改"、"容易踩坑"、"值得利用"三个维度,帮助你制定一个清晰的迁移路线图。

一、硬性前置条件:不满足就无法启动

在讨论任何功能改进之前,你需要先确保运行环境达标。Spark 4.0 在运行时层面设置了几道硬门槛,不满足这些条件,应用甚至无法编译或启动。

1.1 JDK 17 成为最低要求

Spark 4.0 彻底放弃了对 JDK 8 和 JDK 11 的支持,最低要求 JDK 17,同时兼容 JDK 21。这不仅仅是换一个 JDK 版本那么简单——它意味着你的 CI/CD 流水线、Docker 基础镜像、EMR/Dataproc 集群配置、甚至本地开发环境都需要同步升级。

值得注意的是,JDK 17 引入的模块化系统(JPMS)和更严格的反射访问控制,可能导致一些依赖深层反射的第三方库出现 InaccessibleObjectException。建议在迁移初期添加必要的 --add-opens JVM 参数,后续再逐步清理。

1.2 仅支持 Scala 2.13

Spark 4.0 完全移除了 Scala 2.12 的构建支持。对于 Scala 用户来说,这是一项非平凡的迁移任务:

  • scala.collection.JavaConverters 需要替换为 scala.jdk.CollectionConverters

  • 集合转换语法变更:.to[List] 需改为 .to(List)

  • 如果使用了并行集合 .par,需要额外引入 scala-parallel-collections 依赖

如果你的项目中使用了大量 Scala 原生代码编写的 ETL 逻辑,建议先独立完成 Scala 2.13 迁移并在 Spark 3.5 上验证通过,再进行 Spark 版本升级。将两步拆开,可以显著降低排查问题的复杂度。

1.3 Python 最低版本 3.9

Python 3.7 和 3.8 已不再被支持。PySpark 用户需要确保运行环境使用 Python 3.9 及以上版本。考虑到 Python 3.9 在类型注解、字典合并操作符等方面的改进,这对大多数现代 Python 项目来说应该不是障碍,但仍需在集群节点上统一验证。

二、ANSI 炸弹:最容易导致线上事故的变更

如果你只能记住 Spark 4 迁移中的一件事,那就是这个:spark.sql.ansi.enabled 默认值从 false 变为 true

2.1 变更的本质

在 Spark 3.x 的默认模式下,很多"不合法"的操作会被静默处理:整数溢出会回绕(wrap-around)、CAST('hello' AS INT) 会返回 null、除以零会返回 null 或 Infinity。这种宽松策略让很多数据质量问题被掩盖在管道深处,只在最终报表出现"奇怪的数字"时才被发现。

Spark 4.0 默认开启 ANSI 模式后,这些操作将直接抛出 SparkArithmeticException 或 SparkNumberFormatException。换言之,你之前那些"能跑但有 bug"的查询,现在会直接失败

2.2 典型受影响场景

场景

Spark 3.x 默认行为

Spark 4.0 默认行为

2147483647 + 1

返回 -2147483648(整数溢出回绕)

抛出 SparkArithmeticException

CAST('abc' AS INT)

返回 null

抛出 SparkNumberFormatException

10 / 0

返回 null

抛出 SparkArithmeticException

数值类型隐式截断

静默截断

抛出异常

2.3 推荐的迁移策略

实践中最有效的策略是分阶段推进:

  1. 第一阶段:升级到 Spark 4.0 但显式设置 spark.sql.ansi.enabled=false,先让应用跑起来

  2. 第二阶段:在测试环境中开启 ANSI 模式,逐一修复抛出的异常

  3. 第三阶段:将每个 ANSI 异常视为数据质量问题进行根因分析和修复

对于不得不兼容旧行为的场景,可以使用 TRY_CASTTRY_ADDTRY_DIVIDE 等安全函数,或者在 SQL 中通过 CASE WHEN 显式处理边界条件。

三、更多破坏性变更:别让它们在生产环境给你"惊喜"

除了 ANSI 模式,Spark 4.0 还有一系列需要关注的行为变更。

3.1 CREATE TABLE 默认数据源变更

在 Spark 3.x 中,不指定 USING 或 STORED AS 子句的 CREATE TABLE 语句默认创建 Hive 表。Spark 4.0 将默认改为 spark.sql.sources.default 配置的值(通常是 Parquet)。如果你的 ETL 流程中有大量未显式指定格式的建表语句,这个变更可能导致元数据不兼容。

恢复旧行为:设置 spark.sql.legacy.createHiveTableByDefault=true

3.2 JDBC 类型映射大面积调整

这是一个容易被忽略但影响面极广的变更。Spark 4.0 对 PostgreSQL、MySQL、Oracle、SQL Server、DB2 等主流数据库的 JDBC 类型映射进行了"纠正":

  • PostgreSQLTIMESTAMP WITH TIME ZONE 现在统一映射为 TimestampType;写入方向,TimestampType 对应 TIMESTAMP WITH TIME ZONE

  • MySQLSMALLINT 映射为 ShortType(以前是 IntegerType);FLOAT 映射为 FloatType(以前是 DoubleType

  • SQL ServerTINYINT 映射为 ShortTypeDATETIMEOFFSET 映射为 TimestampType(以前是 StringType

这些变更的出发点是让类型映射更准确,但如果你的下游消费者(BI 工具、ML 特征表、其他服务)依赖了旧的类型推断结果,可能导致 Schema 不匹配。建议在迁移前对所有 JDBC 数据源的读写 Schema 进行全量审计。

3.3 ORC/Parquet 默认压缩编解码器变更

ORC 的默认压缩从 snappy 变为 zstd,这在压缩比上有明显优势,但如果下游系统(如 Hive 2.x、Presto 旧版本)不支持 zstd 解码,会导致读取失败。同时,Parquet 的 LZ4 压缩编解码器名称从 lz4raw 更正为 lz4_raw

3.4 CTE 优先级策略变更

spark.sql.legacy.ctePrecedencePolicy 默认值从 EXCEPTION 变为 CORRECTED。在嵌套 CTE 场景下,内层定义将覆盖外层同名定义,而非抛出异常。如果你的 SQL 中有复杂的嵌套 CTE 逻辑,务必验证这一行为变更的影响。

3.5 Servlet API 从 javax 迁移到 jakarta

Spark 内部将 Servlet API 的引用从 javax 命名空间迁移到了 jakarta。如果你有自定义的 Spark Web UI 扩展或 REST API 集成,相关的 import 语句需要更新。

四、基础设施层面的重大调整

4.1 彻底告别 Apache Mesos

Spark 4.0 完全移除了对 Apache Mesos 的支持。如果你的集群仍然在 Mesos 上运行,必须迁移到以下三种方案之一:

  • Kubernetes(推荐):Spark 4.0 对 K8s 的支持更加成熟,默认批量 Pod 分配从 5 增至 10,PersistentVolume 访问模式升级为 ReadWriteOncePod

  • YARN:传统 Hadoop 生态的稳定选择

  • Standalone:适合轻量级部署

从技术趋势看,Kubernetes 是最具前瞻性的选择。它不仅是 Spark 社区的主力投入方向,也与容器化、云原生的行业大趋势一致。

4.2 Shuffle Service 后端默认切换为 RocksDB

外部 Shuffle 服务的存储后端从 LevelDB 变更为 RocksDB。RocksDB 在大规模 Shuffle 场景下具有更好的写入吞吐和空间效率,但这意味着升级过程中旧的 LevelDB 状态文件不会被自动迁移。如果你依赖外部 Shuffle 服务的持久化状态(如 Dynamic Allocation 场景),需要规划好滚动升级策略。

4.3 核心依赖版本大幅跃迁

Spark 4.0 的第三方依赖版本发生了大幅升级,这是 Fat Jar 冲突的重灾区:

依赖

Spark 3.5

Spark 4.0

风险等级

Guava

14.0.1

33.4.0-jre

Jackson

2.15.2

2.18.2

Apache Arrow

12.0.1

18.1.0

Hadoop

3.3.x

3.4.1

其中 Guava 的跨越最为激进——从 14.x 直接跳到 33.x,API 兼容性差异巨大。如果你的应用或其依赖库直接使用了 Guava API(如 OptionalCacheListenableFuture),必须仔细审计 Shading 规则和类路径隔离策略。

五、值得兴奋的新能力:Spark 4 带来了什么

迁移的阵痛是暂时的,而 Spark 4 带来的能力提升是长期的。以下几项新特性尤其值得架构师和 AI 工程师关注。

5.1 Spark Connect:真正的客户端-服务器解耦

Spark Connect 在 3.4 中首次引入,但在 4.0 中才真正达到与 Classic 模式的功能对等。它的核心价值在于:

  • 轻量级客户端:PySpark 客户端仅约 1.5 MB,无需在客户端机器上运行完整的 JVM 驱动

  • 多语言生态:除 Python 和 Scala 外,新增了社区支持的 Go、Rust 和 Swift 客户端

  • 资源隔离:客户端与集群解耦,Notebook 崩溃不会影响 Spark 集群,反之亦然

对于 AI 工程师来说,这意味着你可以从轻量级的 Jupyter Notebook 或 Python 脚本远程提交 ML 训练作业,而不需要在本地配置复杂的 Spark 环境。通过 spark.api.mode="connect" 即可切换。

5.2 VARIANT 数据类型:半结构化数据的一等公民

Spark 4.0 引入了类似 Snowflake 的 VARIANT 数据类型,专为半结构化数据(JSON、嵌套 Map)设计。它允许在单列中存储复杂的嵌套结构,同时保持对嵌套字段的高效查询能力。

在 AI/ML 场景中,特征数据、模型元数据、实验配置往往天然是半结构化的。VARIANT 类型让你无需在"扁平化存储"和"JSON 字符串存储"之间艰难抉择——既保留了结构灵活性,又获得了列存查询性能。

5.3 SQL 语言的重大增强

Spark 4.0 的 SQL 能力有了质的飞跃:

  • SQL UDF:允许在 SQL 中定义可重用的自定义函数,避免频繁切换到 Python/Scala

  • PIPE 语法:通过 |> 操作符实现函数式链式调用,大幅提升复杂查询的可读性

  • SQL 脚本:支持多步骤 SQL 工作流,包含局部变量和控制流语句

  • 排序规则(Collation):支持语言、重音和大小写感知的字符串比较

  • 会话变量:引入会话局部变量和参数标记,有助于防止 SQL 注入

这些增强使得部分 ETL 逻辑可以完全用纯 SQL 表达,减少了对 UDF 的依赖,同时也降低了跨语言协作的摩擦成本。

5.4 Python 生态的全面强化

PySpark 在 4.0 中获得了显著增强:

  • 原生绘图:DataFrame 支持直接调用 .plot() 方法(基于 Plotly),无需先收集到 Pandas

  • Python 数据源 API:完全用 Python 实现自定义数据源连接器(批处理和流处理),不再需要 Java/Scala 知识

  • 多态 UDTF:支持根据输入动态返回不同 Schema 的用户定义表函数

  • UDF 性能分析器:提供 PySpark UDF 的性能指标收集,便于识别瓶颈

5.5 性能提升:20-50% 的加速并非空话

Spark 4.0 在多个层面实现了性能优化:

  • Adaptive Query Execution (AQE) 增强:运行时更智能地调整 Join 策略、减少数据倾斜

  • Arrow 18 集成:降低 JVM 与 Python 之间的序列化开销

  • RocksDB 状态后端:将流处理状态移出 JVM 堆,减少 GC 压力

  • 推测执行优化:默认配置变得更加保守(multiplier=3, quantile=0.9),减少不必要的推测任务浪费资源

在 Databricks 的基准测试中,多种工作负载实现了 20-50% 的端到端加速。对于大规模 ETL 和 ML 训练场景,这意味着真金白银的成本节省。

5.6 结构化流处理的成熟化

  • Arbitrary Stateful Processing v2transformWithState):支持 ValueState、ListState、MapState 多种状态类型,支持 TTL 和事件时间计时器

  • 状态数据源:允许像查询表一样查询流查询的内部状态,极大提升了可观测性和调试效率

  • 检查点格式优化:改进了 SST 文件重用和快照管理

六、迁移检查清单:一个可执行的行动方案

基于上述分析,建议按以下顺序推进迁移:

阶段

任务

优先级

1

将 Scala 代码迁移到 2.13,在 Spark 3.5 上验证

P0

2

升级运行环境到 JDK 17(含 CI/CD、Docker 镜像)

P0

3

升级到 Spark 4.0,显式设置 spark.sql.ansi.enabled=false

P0

4

运行全量测试套件,修复编译和运行时错误

P0

5

开启 ANSI 模式,逐一修复数据质量异常

P1

6

审计 JDBC 数据源的类型映射变更

P1

7

检查 CTE、ORC/Parquet 压缩、格式化字符串等行为变更

P1

8

从 Mesos 迁移到 K8s/YARN,验证 Shuffle Service

P1

9

审计 Fat Jar 依赖(重点:Guava 和 Jackson)

P2

10

更新 Servlet 导入(javax -> jakarta

P2

11

探索 Spark Connect、VARIANT 类型等新能力

P3

七、给技术管理者的建议

7.1 不要因为怕变更而延迟升级

Spark 3.x 系列的维护周期终将结束。Spark 4 带来的性能提升和新功能(特别是 Spark Connect 和 VARIANT 类型),对于构建现代数据平台和 AI 基础设施具有战略价值。每多延迟一个季度,你的团队就多承担一个季度的技术债利息。

7.2 但也不要仓促上线

ANSI 模式的变更在大型数据管道中的影响面是难以预估的。建议先在非核心管道上试点,积累经验后再推广到关键路径。同时,为迁移留出足够的缓冲时间——根据社区反馈,中大型企业的完整迁移周期通常在 2-4 个月。

7.3 将 Spark 4 迁移与平台现代化结合

如果你正在考虑以下任何一项,不妨将其与 Spark 4 迁移一并推进:

  • 从 Mesos 迁移到 Kubernetes

  • 从 JDK 8/11 升级到 JDK 17/21

  • 引入 Spark Connect 实现计算与客户端解耦

  • 统一半结构化数据的存储策略

将多个基础设施升级合并执行,可以减少总的变更窗口和回归测试轮次。

结语

Spark 4.0 不是一次温和的小版本迭代,而是一次带有阵痛的代际升级。ANSI 模式的"炸弹"、JDK 17 的硬门槛、Scala 2.12 的告别、Mesos 的退场——这些变更传递的信号很明确:Spark 正在甩掉历史包袱,全力拥抱现代化

对于数据工程师和 AI 工程师来说,这意味着短期的迁移投入,换来的是更准确的数据语义、更强大的 SQL 能力、更轻量的客户端架构、以及实实在在的性能红利。

迁移之路没有捷径,但有方法。希望这份指南能为你的 Spark 4 升级之旅提供一张可靠的地图。

参考资料:

  1. Apache Spark 4.0.0 Migration Guide - Official Documentation

  2. Introducing Apache Spark 4.0 - Databricks Blog

  3. Apache Spark 3 to Apache Spark 4 Migration - DZone

  4. Upgrading from Spark 3.x to Spark 4.0: A Practical Guide - Sparking Scala

  5. Apache Spark 4.0: A New Era for Scalable Machine Learning and AI - Databricks Community

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐