Apache Toree 0.5.0-incubating Spark 集成进阶实战详解
深入 Apache Toree 0.5.0-incubating 与 Spark 的集成实战,覆盖版本配对的 Scala 2.12 根因与两种 JDK 失败模式、spark-context-initialization-mode 实测、--spark_opts 调参与运行时覆盖、SQL 双路径实测(SQL 专属内核挂起结论)与 %AddDeps 依赖链路,全部已在 Ubuntu 22.04 实测。
Apache Toree 0.5.0-incubating Spark 集成进阶实战详解
目标:深入掌握 Apache Toree 0.5.0-incubating 与 Apache Spark 集成的高级主题——版本配对的 Scala 2.12 根因与两种实测失败模式、spark-context-initialization-mode 的真实行为、--spark_opts 调参与运行时覆盖、SQL 双路径(Scala 内核 %%SQL vs SQL 专属内核)实测结论、%AddDeps 依赖链路与排障手法。以下全部命令与回显均已在 Ubuntu 22.04(x86_64)、OpenJDK 1.8.0_504、Spark 3.0.3 实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- JDK:OpenJDK 1.8.0_504(另装 openjdk-17-jdk 17.0.20.1 用于失败模式实测)
- Python:3.10.12,Jupyter notebook 7.6.3、jupyter_core 5.9.1
- 内核:apache-toree 0.5.0(pip 包,内核 jar toree-assembly-0.5.0-incubating.jar)
- Spark:spark-3.0.3-bin-hadoop2.7(主配置)、spark-2.4.8-bin-hadoop2.7(失败模式实测)
- 内核注册:
apache_toree_scala(默认)、toree_none_scala(none 模式)、toree_local2_scala(local[2])、toree_dual_sql(SQL 专属) - 验证用工作目录:
/root/toree-test
确认四个实测内核已注册:
jupyter kernelspec list
Available kernels:
apache_toree_scala /usr/local/share/jupyter/kernels/apache_toree_scala
python3 /usr/local/share/jupyter/kernels/python3
toree_dual_sql /usr/local/share/jupyter/kernels/toree_dual_sql
toree_local2_scala /usr/local/share/jupyter/kernels/toree_local2_scala
toree_none_scala /usr/local/share/jupyter/kernels/toree_none_scala
各内核注册命令见第四章;安装基础步骤见第 1 篇《Ubuntu 22.04 安装 Apache Toree 指南》。
二、版本配对深入:为什么是 Spark 3.0.3 + JDK 8
Toree 0.5.0 与 Spark 的耦合点在内核 jar 的编译期 Scala 版本与 spark-submit 的运行期 JVM 版本,配对错误的两种失败模式均已实测复现。
官方测试基线:Toree 仓库 v0.5.0-incubating 标签的集成测试环境定义(toree-dev 构建文件)使用 Spark 3.0.3 + OpenJDK 8,即 Scala 2.12 构建的 Spark 发行版(jars 下为 spark-core_2.12-3.0.3.jar)。
失败模式一:Spark 用了 Scala 2.11 构建(如 spark-2.4.8-bin-hadoop2.7)。内核按 Scala 2.12 编译,运行时被 Spark 自带的 scala-library-2.11.12 抢先加载,报错(手动 spark-submit 复现实测):
Exception in thread "main" java.lang.NoSuchMethodError: 'void scala.App.$init$(scala.App)'
核对 Spark 构建侧的 Scala 版本:
ls /usr/local/spark/jars/ | grep -E 'spark-core|scala-library'
scala-library-2.11.12.jar
spark-core_2.11-2.4.8.jar
失败模式二:JDK 用了 17(Spark 3.0.x 只支持 JDK 8/11)。换回 Spark 3.0.3 后在 JDK 17.0.20.1 下实测:
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make private java.nio.DirectByteBuffer(long,int) accessible: module java.base does not "opens java.nio" to unnamed module @4f51ab48
可用组合的判定清单:
| 检查项 | 预期 | 说明 |
|---|---|---|
| `ls $SPARK_HOME/jars | grep spark-core` | spark-core_2.12-3.0.3.jar |
java -version | 1.8.0_xxx | run.sh 经 spark-submit 间接使用该 JVM |
| spark-submit 手动复现 | 无异常输出,进入等待连接状态 | 见 7.2 节定位法 |
💡 技术拓展:同为「Jupyter 上的 Spark」,Toree 与 sparkmagic 的耦合层次完全不同——
维度 Apache Toree sparkmagic Spark 部署形态 内核进程内经 spark-submit 直接起 Spark(local/standalone/yarn 均可) 依赖独立 Livy 服务代理远端 Spark 版本耦合 与 Spark 同 JVM,Scala 版本必须匹配(本文实测结论) REST 协议解耦,Spark 侧版本由 Livy 决定 适用 单机/自管集群交互开发 多用户共享远端集群 本文采用 Apache Toree:无中间服务、延迟低、变量与依赖直接落在内核进程内,代价是必须严守版本配对(本节即配对判定方法)。
三、SparkContext 初始化模式实测
--toree_opts='--spark-context-initialization-mode 取值' 控制 SparkContext 的创建时机,官方可选值有 eager/lazy/none。本文实测默认(未显式设置)与 none 两种模式在同一台机器上的差异。
注册 none 模式内核:
jupyter toree install --spark_home=/usr/local/spark --kernel_name='Toree None' --toree_opts='--spark-context-initialization-mode none' --replace
[ToreeInstall] Creating kernel Scala
[ToreeInstall] Installed kernelspec toree_none_scala in /usr/local/share/jupyter/kernels/toree_none_scala
执行测试 notebook(cell1 纯 Scala,cell2 访问 sc):
cd /root/toree-test && python3 - <<'EOF'
import json
def code(src): return {'cell_type':'code','execution_count':None,'metadata':{},'outputs':[],'source':src.splitlines(True)}
nb={'cells':[code('val z = 7\nprintln(z * 6)'),
code('sc.parallelize(1 to 10).sum()')],
'metadata':{'kernelspec':{'display_name':'Toree None - Scala','language':'scala','name':'toree_none_scala'},'language_info':{'name':'scala'}},'nbformat':4,'nbformat_minor':5}
open('nb-none.ipynb','w').write(json.dumps(nb, indent=1))
print('written')
EOF
timeout 360 jupyter nbconvert --to notebook --execute --allow-errors --ExecutePreprocessor.timeout=300 nb-none.ipynb --output nb-none-out.ipynb
written
实测输出:
===== cell 1
[stream] 42
[display_data] {'text/plain': 'z = 7'}
[execute_result] {'text/plain': '7'}
===== cell 2
[execute_result] {'text/plain': '55.0'}
关键实测结论:即使声明 none 模式,首次访问 sc 时仍会按需创建 SparkContext(1 to 10 求和返回 55.0)——0.5.0 的 none 模式只是跳过启动期 eager 初始化,不是禁用 Spark。两种模式的实际差别在内核启动耗时与启动失败面:默认模式下内核就绪时 SparkContext 已在创建链路上,Spark 配置错误会直接导致 Kernel died;none 模式下内核先就绪,错误推迟到首次访问 sc 的单元格。若确实需要「无 Spark 的纯 Scala 内核」,应从 Spark 依赖装配层面另行处理,而不是依赖 none 模式。
启动耗时参考(实测同一台机器):默认模式内核就绪约 40~60 秒(含 spark-submit 与本地 SparkContext 初始化日志),none 模式内核就绪明显更快,首次访问 sc 的单元格耗时与默认模式相当。
四、Spark 参数调优:--spark_opts 与运行时覆盖
4.1 注册期固定参数
注册时用 --spark_opts 固定 master 与资源参数,写入 kernel.json 的 env.__TOREE_SPARK_OPTS__:
jupyter toree install --spark_home=/usr/local/spark --kernel_name='Toree Local2' --spark_opts='--master=local[2]' --replace
[ToreeInstall] Creating kernel Scala
[ToreeInstall] Installed kernelspec toree_local2_scala in /usr/local/share/jupyter/kernels/toree_local2_scala
执行验证单元格 println("master=" + sc.master) 与 RDD 求和:
master=local[2]
appName=Apache Toree
sum=5050.0
验证点:master=local[2] 说明 --spark_opts 生效;appName=Apache Toree 是 run.sh 固定注入的 --name 'Apache Toree'。常用参数组合:--master=local[*](按核数并行)、--driver-memory 2g(本地模式堆大小)、--conf spark.sql.shuffle.partitions=8(小规模 SQL 探索减少小任务)。
4.2 运行期覆盖
run.sh 的优先级逻辑(源码核对):外部环境变量 SPARK_OPTS/TOREE_OPTS 非空时覆盖 kernel.json 内的占位值。因此同一内核可在启动 Jupyter 前临时换 master:
export SPARK_OPTS='--master=local[4]' && jupyter notebook
同理会话内 TOREE_OPTS 可临时覆盖 --spark-context-initialization-mode 等内核参数,无需重注册。
4.3 接独立集群的参数骨架
从 local 切到 standalone/YARN 只需改 --master 一处:standalone 形如 spark://spark-master:7077(spark-master 为实际集群主节点地址),YARN 直接用 --master yarn,均保持 --deploy-mode client。内核以 client 模式运行时 driver 在内核进程内,notebook 中的 sc 即集群任务的 driver,本文实测范围为 local 模式,集群模式参数请按目标集群版本核对 Spark 官方文档。
五、SQL 双路径实测
Toree 0.5.0 有两条 SQL 路径:Scala 内核上的 %%SQL 单元格 magic,与 --interpreters=SQL 注册的 SQL 专属内核。实测结论先行:生产用 Scala 内核 + %%SQL。
5.1 Scala 内核 + %%SQL(可用)
在 apache_toree_scala 内核上执行(见第 3 篇第三章):
%%SQL
SELECT 1+1 AS two, 'toree' AS name
[execute_result] {'text/plain': '+---+-----+\n|two| name|\n+---+-----+\n| 2|toree|\n+---+-----+\n'}
同一结果表也可用 spark.sql(...) 显式写法获得,好处是能接 Scala 变量与算子链(实测 spark.sql("SELECT 1+1 AS two, 'toree' AS name").show() 输出一致)。
5.2 SQL 专属内核(实测挂起)
--interpreters=SQL 注册的内核(toree_dual_sql,DEFAULT_INTERPRETER=SQL)执行 SQL 单元格实测 300 秒无回复,nbconvert 报超时:
[NbConvertApp] ERROR | Timeout waiting for execute reply (300s).
26/10/15 12:59:40 ERROR InterpreterActor: Could not execute code kernel.magics.SQL("""SELECT 2*3 AS six""") because of exception: Ask timed out on [Actor[akka://spark-kernel-actor-system/user/interpreter/execute_request_task#1868367090]] after [21474835000 ms]. Message of type [scala.Tuple3] was sent by [Actor[akka://spark-kernel-actor-system/user/interpreter#1912344793]]. A typical reason for `AskTimeoutException` is that the recipient actor didn't send a reply.
结论:0.5.0 的 SQL 专属解释器存在执行挂起缺陷(Akka actor 应答超时),同一 SQL 在 Scala 内核的 %%SQL 上秒级返回,因此不要把 SQL 专属内核用于生产 notebook。
六、依赖集成进阶:%AddDeps 与 %AddJar
6.1 %AddDeps 坐标解析链路
实测 org.apache.commons:commons-math3:3.6.1(Maven 坐标以空格分隔):
[stream] Marking org.apache.commons:commons-math3:3.6.1 for download
[stream] Obtained 2 files
Obtained 2 files 对应 jar 与 pom。随后单元格 import 并计算(实测输出):
[stream] mean=4.0
[display_data] {'text/plain': 'DescriptiveStatistics:\nn: 3\nmin: 2.0\nmax: 6.0\nmean: 4.0\nstd dev: 2.0\nmedian: 4.0\nskewness: 0.0\nkurtosis: NaN'}
实测细节:Marking/Obtained 两条日志各出现两次(0.5.0 的重复日志行为),不影响加载;对带传递依赖的坐标,Obtained 文件数会相应增加。依赖已由 --spark_opts/spark-submit --jars 预置时无需 magic,直接 import 即可。
6.2 %AddJar 与 %AddDeps 的分工
| 场景 | 首选 | 理由 |
|---|---|---|
| 知道 Maven 坐标 | %AddDeps | 自动解析坐标与传递依赖,声明可审计 |
| jar 直链/本地文件 | %AddJar | URL 或 file:// 直接拉取,第二次走缓存 |
| 依赖固定不变 | spark-submit --jars(经 spark_opts 传入) | 内核启动即就绪,避免会话内网络下载 |
两者叠加使用时注意类加载顺序:后加载的 jar 不会替换已加载的类,同一依赖不要既 AddJar 又 AddDeps。
七、运维与排障
7.1 常见 WARN 的定性
以下 WARN 均为实测捕获,不影响功能:
WARN StandardComponentInitialization$$anon$1: Unable to control initialization of REPL class server!:REPL 类加载器初始化控制受限的告警,实测全部单元格正常执行与求值。WARN KernelOutputStream: Suppressing empty output: '':内核抑制空输出的正常行为。- Spark 侧
Using Spark's default log4j profile ... To adjust logging level use sc.setLogLevel(newLevel).:Spark 默认日志提示,可在单元格sc.setLogLevel("WARN")降噪。
7.2 手动复现定位法
内核启动失败时,绕开 Jupyter 直接复现(最快的根因暴露方式,实测见第二、三章):
timeout 60 /usr/local/spark/bin/spark-submit \
--name 'Apache Toree' \
--class org.apache.toree.Main \
/usr/local/share/jupyter/kernels/apache_toree_scala/lib/toree-assembly-0.5.0-incubating.jar \
--spark-context-initialization-mode none 2>&1 | head -20
正常时输出 Spark banner 与内核组件加载日志后进入等待连接状态(timeout 60 秒后被杀属预期);异常时首屏即给出 NoSuchMethodError/InaccessibleObjectException 等根因。
7.3 版本升级路径
升级 Toree:pip3 install -U apache-toree 后对每个注册名重跑 jupyter toree install ... --replace(kernelspec 内复制的 jar 不会随 pip 升级自动更新)。升级 Spark:解压新版本到新目录(如 /usr/local/spark-3.0.3),按第二章判定清单核对 Scala 构建后用 --spark_home --replace 重注册,回滚只需指回旧目录。
八、常见问题(FAQ)
Q1:none 模式下能不能彻底禁用 Spark?
不能。实测 none 模式首次访问 sc 仍会创建 SparkContext(第三章 55.0 的求和结果)。它的价值是「先起内核、按需初始化」,用于隔离 Spark 配置错误对内核启动的影响。
Q2:内核就绪后第一个单元格特别慢? 默认模式下首单元格在等待 SparkContext 完成创建(实测约 40~60 秒冷启动)。要加快纯 Scala 验证速度,用 none 模式内核(第三章)。
Q3:%%SQL 的结果想转成结构化数据在 Scala 里继续处理?
改用 val df = spark.sql("SELECT ...") 拿到 DataFrame 后自由操作(.show()、.collect()、.write 等);%%SQL 魔法输出的是格式化文本表,适合展示不适合链式处理。
Q4:SQL 专属内核一定不能用吗?
实测 0.5.0 存在 AskTimeoutException 挂起缺陷(第五章),同一 SQL 在 Scala 内核 %%SQL 上正常。除非后续版本修复,生产统一用 Scala 内核 + %%SQL。
Q5:能同时跑多个 Toree 内核会话吗?
可以。每个 notebook 会话是独立的 spark-submit 进程,各自持有独立 SparkContext;local 模式下注意多会话的内存与核数分配(--driver-memory、--master=local[2] 按会话限流),避免本机资源争抢。
Q6:%AddDeps 报解析失败/超时?
先在单元格内验证网络与仓库可达性(curl repo1.maven.org),内网环境配置自建 Nexus resolver;地址固定的 jar 改用 %AddJar。两者行为差异见 6.2 节分工表。
九、参考链接
- Apache Toree 官方 How it works:https://toree.apache.org/docs/current/user/how-it-works
- 官方 Advanced Topics:https://toree.apache.org/docs/current/user/advanced-topics
- Spark 3.0.3 官方文档(master/deploy-mode 参数):https://spark.apache.org/docs/3.0.3/submitting-applications.html
- Toree 源码仓库(v0.5.0-incubating 标签与测试镜像):https://github.com/apache/incubator-toree
- 本文配套第 4 篇(命令与 magic 总表):/docs/Apache Toree/Apache Toree 0.5.0-incubating 常用命令与 magic 详解.md
- 本文配套第 1 篇(安装与版本选型):/docs/Apache Toree/Ubuntu 22.04 安装 Apache Toree 指南.md