Apache Toree 0.5.0-incubating 快速入门与集成示例

用 10 分钟在 Jupyter 中跑通 Apache Toree 0.5.0-incubating 内核的第一个 Scala 与 Spark 程序,覆盖 RDD 求和、SparkSession SQL、%%SQL 与 %%HTML magic、%AddJar 引入 commons-math3 的集成示例与错误行为实测,全部已在 Ubuntu 22.04 实测。

Apache ToreeApache Toree2026/10/15

Apache Toree 0.5.0-incubating 快速入门与集成示例

目标:10 分钟内在 Jupyter 中跑通 Apache Toree 0.5.0-incubating 内核的第一个 Scala 与 Spark 程序;掌握内核输出的三种类型(stream/display_data/execute_result);实测 RDD 计算、SparkSession SQL、%%SQL、%%HTML、%AddJar 引入第三方 jar 的集成链路与典型错误行为。以下全部命令与回显均已在 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(java -version 实测;Toree 0.5.0 + Spark 3.0.3 在 JDK 17 下不可用,实测结论见第 1 篇第三章)
  • Python:3.10.12,Jupyter notebook 7.6.3
  • 内核:Apache Toree 0.5.0(apache-toree pip 包,内核 jar toree-assembly-0.5.0-incubating.jar)
  • Spark:spark-3.0.3-bin-hadoop2.7(/usr/local/spark,SPARK_HOME)
  • 内核注册名:apache_toree_scala(display_name「Apache Toree - Scala」)
  • 验证用工作目录:/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

安装与注册步骤见第 1 篇《Ubuntu 22.04 安装 Apache Toree 指南》第四、五章;CentOS 8 见第 2 篇。

二、10 分钟最小可用

本节从空目录到跑通第一个程序共 3 步:生成 notebook(2 分钟)→ nbconvert 执行(内核冷启动约 40~60 秒,首次执行含 Spark 初始化)→ 提取结果验证。

2.1 生成第一个 notebook

Toree 内核最稳的执行方式是 jupyter nbconvert --to notebook --execute(JupyterLab/notebook 界面点单元格运行等效)。生成包含一个 Scala 单元格的 notebook(文件路径 /root/toree-test/first.ipynb):

mkdir -p /root/toree-test && cd /root/toree-test && python3 - <<'EOF'
import json
nb = {
 'cells': [
  {'cell_type':'code','execution_count':None,'metadata':{},'outputs':[],
   'source':['val msg = "Hello Apache Toree"\n','println(msg)\n','1 + 1']},
 ],
 'metadata': {'kernelspec': {'display_name': 'Apache Toree - Scala', 'language': 'scala', 'name': 'apache_toree_scala'},
              'language_info': {'name': 'scala'}},
 'nbformat': 4, 'nbformat_minor': 5
}
open('first.ipynb','w').write(json.dumps(nb, indent=1))
print('notebook written')
EOF
notebook written

单元格源码等价于在 Jupyter 界面输入三行 Scala:val msg = "Hello Apache Toree"、println(msg)、1 + 1。notebook 元数据里的 kernelspec.name 决定 nbconvert 用哪个内核(apache_toree_scala)。

2.2 执行与提取结果

cd /root/toree-test && timeout 300 jupyter nbconvert --to notebook --execute --ExecutePreprocessor.timeout=240 first.ipynb --output first-out.ipynb
[NbConvertApp] Writing 1302 bytes to first-out.ipynb

提取执行结果(复用性脚本,后文所有示例都用它):

python3 -c "
import json
nb=json.load(open('/root/toree-test/first-out.ipynb'))
for c in nb['cells']:
    for o in c.get('outputs',[]):
        t=o.get('output_type')
        if 'text' in o: print('['+t+']', ''.join(o['text']).rstrip())
        elif 'data' in o: print('['+t+']', {k: ''.join(v).rstrip() for k,v in o['data'].items()})
"
[stream] Hello Apache Toree
[display_data] {'text/plain': 'msg = Hello Apache Toree'}
[execute_result] {'text/plain': '2'}

2.3 输出类型与验证点

Toree 内核的单元格输出分三类,识别它们是排障的基础:

输出类型来源本例实测
streamprintln 等标准输出Hello Apache Toree
display_data解释器对 val 定义与复杂对象的回显(如 DescriptiveStatistics 的表格展示)msg = Hello Apache Toree
execute_result单元格最后一行表达式的求值2

验证点清单:first-out.ipynb 中三类输出齐全、execute_result 为 2,即内核通道全部正常;若 nbconvert 报 Kernel died before replying to kernel_info,是 JDK/Spark 配对问题(见第 1 篇第九章 FAQ Q1)。

三、Spark 计算与 SQL

本节实测内核预置变量与 Spark 交互的最常用路径:sc(SparkContext)、spark(SparkSession)、%%SQL magic。生成 spark-first.ipynb 并执行:

cd /root/toree-test && python3 - <<'EOF'
import json
def code(src): return {'cell_type':'code','execution_count':None,'metadata':{},'outputs':[],'source':src.splitlines(True)}
cells=[
 code('val rdd = sc.parallelize(1 to 100)\nprintln("sum=" + rdd.sum())\nprintln("count=" + rdd.count())'),
 code('spark.sql("SELECT 1+1 AS two, \'toree\' AS name").show()'),
 code('%%SQL\nSELECT 1+1 AS two, \'toree\' AS name'),
]
nb={'cells':cells,'metadata':{'kernelspec':{'display_name':'Apache Toree - Scala','language':'scala','name':'apache_toree_scala'},'language_info':{'name':'scala'}},'nbformat':4,'nbformat_minor':5}
open('spark-first.ipynb','w').write(json.dumps(nb, indent=1))
print('written')
EOF
timeout 420 jupyter nbconvert --to notebook --execute --allow-errors --ExecutePreprocessor.timeout=380 spark-first.ipynb --output spark-first-out.ipynb
written
[NbConvertApp] Writing 5758 bytes to spark-first-out.ipynb

三个单元格的实测输出(提取方式同 2.2 节):

===== cell 1(RDD)
[stream] sum=5050.0
[stream] count=100
[display_data] {'text/plain': 'rdd = ParallelCollectionRDD[0] at parallelize at <console>:27'}
[execute_result] {'text/plain': 'ParallelCollectionRDD[0] at parallelize at <console>:27'}

===== cell 2(SparkSession SQL)
[stream] +---+-----+
|two| name|
+---+-----+
|  2|toree|
+---+-----+

===== cell 3(%%SQL magic)
[execute_result] {'text/plain': '+---+-----+\n|two| name|\n+---+-----+\n|  2|toree|\n+---+-----+\n'}

逐项解读:

  • cell 1:内核预定义了 sc(SparkContext)。sc.parallelize(1 to 100) 创建真实 RDD,sum() 得 5050.0(1 到 100 求和,手算核对一致)、count() 得 100。execute_result 显示的是 RDD 对象描述而非数据,取数据需 .collect()/.sum() 等动作算子。
  • cell 2:内核预定义了 spark(SparkSession)。注意实测中 sqlContext 未预定义——直接用 sqlContext 报 error: not found: value sqlContext(Toree 0.5.0 + Spark 3.0.3 组合的实测行为)。
  • cell 3:%%SQL 是单元格 magic(写在首行、作用于整个单元格),整个单元格按 SQL 解释,execute_result 直接返回格式化结果表,无需 .show()。

⚠️ 注意:%%SQL 只在 Scala 内核上实测可用;--interpreters=SQL 注册的 SQL 专属内核在 0.5.0 实测存在执行挂起问题(AskTimeoutException),生产使用请坚持 Scala 内核 + %%SQL 的组合,详见第 5 篇。

四、第三方 jar 集成:%AddJar 引入 commons-math3

本节实测最常用的依赖集成链路:%AddJar 从 Maven Central 下载 jar → import → 使用。以 Apache Commons Math 3.6.1 为例(零依赖、单 jar,适合演示):

cd /root/toree-test && python3 - <<'EOF'
import json
def code(src): return {'cell_type':'code','execution_count':None,'metadata':{},'outputs':[],'source':src.splitlines(True)}
cells=[
 code('%AddJar https://repo1.maven.org/maven2/org/apache/commons/commons-math3/3.6.1/commons-math3-3.6.1.jar'),
 code('import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics\nval ds = new DescriptiveStatistics()\nArray(9,2,5,4,12,7).foreach(v => ds.addValue(v.toDouble))\nprintln("mean=" + ds.getMean)\nprintln("sd=" + ds.getStandardDeviation)'),
]
nb={'cells':cells,'metadata':{'kernelspec':{'display_name':'Apache Toree - Scala','language':'scala','name':'apache_toree_scala'},'language_info':{'name':'scala'}},'nbformat':4,'nbformat_minor':5}
open('addjar.ipynb','w').write(json.dumps(nb, indent=1))
print('written')
EOF
timeout 420 jupyter nbconvert --to notebook --execute --allow-errors --ExecutePreprocessor.timeout=380 addjar.ipynb --output addjar-out.ipynb
written
[NbConvertApp] Writing 5758 bytes to addjar-out.ipynb

实测输出:

===== cell 1(%AddJar)
[stream] Starting download from https://repo1.maven.org/maven2/org/apache/commons/commons-math3/3.6.1/commons-math3-3.6.1.jar
[stream] Finished download of commons-math3-3.6.1.jar

===== cell 2(import 与计算)
[stream] mean=6.5
[stream] sd=3.6193922141707713
[display_data] {'text/plain': 'DescriptiveStatistics:\nn: 6\nmin: 2.0\nmax: 12.0\nmean: 6.5\nstd dev: 3.6193922141707713\nmedian: 6.0\nskewness: 0.45556128329...'}

逐项解读:

  • %AddJar 后接远程 URL 或 file:// 本地路径(本文实测 Maven Central 直链);同一 URL 第二次执行时输出 Using cached version of commons-math3-3.6.1.jar,直接走缓存。
  • %AddJar 对 import org.apache.commons.math3.* 生效的前提是 import 语句写在 AddJar 之后的单元格(Scala 解释器的类加载链在编译单元格时才解析)。
  • DescriptiveStatistics 的 display_data 是 Toree 对已注册展示器的富对象渲染:数据 {9,2,5,4,12,7} 均值 6.5(和 39/6)、标准差 3.619…,与 commons-math3 的样本方差语义一致。
  • 与 Maven 坐标方式的选择:已知 group/artifact/version 时优先 %AddDeps org.apache.commons commons-math3 3.6.1(自动解析坐标与传递依赖,实测输出 Marking org.apache.commons:commons-math3:3.6.1 for download、Obtained 2 files 后同样可直接 import 计算,实测 mean=4.0,数据 {2.0,4.0,6.0});下载地址固定时用 %AddJar。两者详见第 4 篇。

💡 技术拓展:交互式 Scala 计算环境里引第三方库的三种方式对比如下——

方式作用范围特点适用场景
%AddJar运行时 classpathURL/file 直链,第二次走缓存jar 地址固定、本地已下载
%AddDeps运行时 classpathMaven/Ivy 坐标自动解析,可带传递依赖知道坐标的常规依赖
spark-submit --jars内核启动前重启内核才能改依赖固定、追求启动即就绪

本文实测采用 %AddJar + %AddDeps 双通道:快速验证用 %AddJar(直链可见下载进度),正式 notebook 用 %AddDeps(坐标可审计)。

五、富输出与类型提示:%%HTML 与 %Showtypes

本节实测两个展示类 magic。生成 rich-out.ipynb 并执行:

cd /root/toree-test && python3 - <<'EOF'
import json
def code(src): return {'cell_type':'code','execution_count':None,'metadata':{},'outputs':[],'source':src.splitlines(True)}
cells=[
 code('%Showtypes on\nval t = (1, "a")\nt'),
 code('%%HTML\n<h1>Toree HTML Magic</h1>'),
]
nb={'cells':cells,'metadata':{'kernelspec':{'display_name':'Apache Toree - Scala','language':'scala','name':'apache_toree_scala'},'language_info':{'name':'scala'}},'nbformat':4,'nbformat_minor':5}
open('rich-out.ipynb','w').write(json.dumps(nb, indent=1))
print('written')
EOF
timeout 420 jupyter nbconvert --to notebook --execute --allow-errors --ExecutePreprocessor.timeout=380 rich-out.ipynb --output rich-out-out.ipynb
written
[NbConvertApp] Writing 5758 bytes to rich-out-out.ipynb

实测输出:

===== cell 1(%Showtypes on)
[stream] Types will be printed.
[display_data] {'text/plain': 't: (Int, String) = (1,a)'}
[execute_result] {'text/plain': '(Int, String) = (1,a)'}

===== cell 2(%%HTML)
[execute_result] {'text/html': '<h1>Toree HTML Magic</h1>'}

逐项解读:

  • %Showtypes on 是行 magic,开启后每个求值结果带类型标注:元组显示为 (Int, String) = (1,a),调试复杂对象时直观。关闭用 %Showtypes off。
  • %%HTML 是单元格 magic,首行声明后单元格内容按 HTML 输出(text/html MIME),在 JupyterLab/notebook 界面渲染为标题;nbconvert 导出的 JSON 里同样保留 text/html 字段。

六、错误行为实测

理解内核的报错形态,能加快排障。以下两类错误均为实测复现:

编译错误以 error 输出返回,内核不崩溃。使用未定义变量 sqlContext(见第三章)的实测 error 输出:

[error] Compile Error : <console>:26: error: not found: value sqlContext
       sqlContext.sql("SELECT 1+1 AS two, 'toree' AS name").show()
       ^

类型不匹配错误同样以 error 输出返回。Array(9,2,5,4,12,7).foreach(ds.addValue)(Int 数组直接传给期望 Double 的重载方法)实测:

[error] Compile Error : <console>:49: error: type mismatch;
 found   : Double => Unit
 required: Int => ?
       Array(9,2,5,4,12,7).foreach(ds.addValue)
                                      ^

修正写法是显式转换:Array(9,2,5,4,12,7).foreach(v => ds.addValue(v.toDouble)),修正后 4.5 节输出正常(mean=6.5)。要点:Toree 的 Scala 编译错误发生在单元格编译期,不影响后续单元格;Compile Error 信息完整保留了 Scala 编译器的行号与符号提示,与本地 scala REPL 一致。

七、常见问题(FAQ)

Q1:单元格输出顺序里 display_data 和 execute_result 为什么都出现? 单元格内每个 val 定义会触发一次 display_data 回显(如 msg = ...、rdd = ParallelCollectionRDD[0]...),单元格最后一行表达式产生 execute_result。两者共存属正常,不是重复输出。

Q2:sc.parallelize(...) 的 execute_result 显示 ParallelCollectionRDD[0] at parallelize at <console>:27,怎么看到数据? RDD 是惰性分布式对象,显示的是对象描述。取数据用动作算子:.collect().mkString(", ")、.sum()、.take(5).foreach(println) 等。

Q3:%%SQL 能不能带参数? 0.3.0 起 SQL 语句支持预定义变量注入(官方 RELEASE_NOTES),常规做法是先在 Scala 单元格定义变量再在 %%SQL 中引用;复杂场景建议直接用 spark.sql(...) 写完整 Scala 表达式,可控性最好。

Q4:%AddJar 后 import 仍报 object apache is not a member of package org? 把 import 放到 %AddJar 之后的独立单元格,且两者在同一个内核会话中执行;跨会话(内核重启后)需要重新 %AddJar。仍失败时检查 jar 内实际包名(unzip -l xxx.jar | head)。

Q5:内核启动要多久? 实测冷启动(含 spark-submit 与本地 SparkContext 初始化)约 4060 秒;Jupyter 界面左侧的圆圈变实心即就绪。nbconvert 批量执行时给足 --ExecutePreprocessor.timeout(本文用 240380 秒)。

八、参考链接

  1. Apache Toree 官方 Quick Start:https://toree.apache.org/docs/current/user/quick-start
  2. 官方 Using with Jupyter Notebooks:https://toree.apache.org/docs/current/user/using-with-jupyter-notebooks
  3. Apache Toree 安装(本文配套第 1 篇):/docs/Apache Toree/Ubuntu 22.04 安装 Apache Toree 指南.md
  4. Maven Central commons-math3 坐标(本文演示依赖):https://repo1.maven.org/maven2/org/apache/commons/commons-math3/
  5. Spark SQL 官方文档:https://spark.apache.org/docs/3.0.3/sql-programming-guide.html