Zeppelin中Spark Scala语句报$iw类ClassCastException求助排查
解决Zeppelin中Spark Scala代码的ClassCastException($iw类加载器冲突)
问题现象
在Zeppelin中执行Scala编写的Spark语句时,抛出ClassCastException,提示来自不同类加载器的$iw类无法互相转换:
- 一个来自
org.apache.spark.repl.ExecutorClassLoader - 另一个来自
scala.tools.nsc.interpreter.IMain$TranslatingClassLoader
异常栈信息:
org.apache.spark.SparkException: Job aborted due to stage failure: Task 0 in stage 3.0 failed 1 times, most recent failure: Lost task 0.0 in stage 3.0 (TID 3) (192.168.1.153 executor driver): java.lang.ClassCastException: class $iw cannot be cast to class $iw ($iw is in unnamed module of loader org.apache.spark.repl.ExecutorClassLoader @69bd1241; $iw is in unnamed module of loader scala.tools.nsc.interpreter.IMain$TranslatingClassLoader @5a68e587) at org.apache.spark.sql.catalyst.expressions.GeneratedClass$GeneratedIteratorForCodegenStage1.deserializetoobject_doConsume_0$(Unknown Source) at org.apache.spark.sql.catalyst.expressions.GeneratedClass$GeneratedIteratorForCodegenStage1.processNext(Unknown Source)
但相同代码在spark-shell中可正常执行,示例代码如下:
import spark.implicits._ case class Flight(DEST_COUNTRY_NAME: String, ORIGIN_COUNTRY_NAME: String, count: BigInt) val flightsDF = spark.read .parquet("/home/lebihan/dev/apprentissageDev/Spark-The-Definitive-Guide/data/flight-data/parquet/2010-summary.parquet/") val flights = flightsDF.as[Flight] // 执行转换操作 flights .filter(flight_row => flight_row.ORIGIN_COUNTRY_NAME != "Canada") .map(flight_row => flight_row) .take(5) flights .take(5) .filter(flight_row => flight_row.ORIGIN_COUNTRY_NAME != "Canada") .map(fr => Flight(fr.DEST_COUNTRY_NAME, fr.ORIGIN_COUNTRY_NAME, fr.count + 5))
原因分析
$iw是Spark REPL为包装代码块生成的内部类,Zeppelin的解释器类加载机制与spark-shell存在差异:
spark-shell的REPL类加载器在Driver和Executor端保持一致- Zeppelin的解释器会使用独立的类加载器隔离不同笔记或代码块,导致Driver端(
IMain$TranslatingClassLoader)和Executor端(ExecutorClassLoader)加载的$iw类属于不同的类实例,序列化/反序列化时触发类型转换异常。
排查与解决步骤
1. 调整Zeppelin Spark解释器的类加载优先级
在Zeppelin的Spark解释器设置中,添加或修改以下Spark配置:
spark.driver.userClassPathFirst=true:让Driver端优先加载用户类路径下的类spark.executor.userClassPathFirst=true:让Executor端优先加载用户类路径下的类zeppelin.spark.classloader.parents=first:调整Zeppelin解释器的类加载顺序,优先使用Spark的类加载器
2. 确保Scala版本完全一致
Spark 3.1.1默认适配Scala 2.12.x,检查以下内容:
- 对比Zeppelin安装目录
lib下的scala-library*.jar版本与Spark安装目录lib下的对应版本,必须完全一致 - 在Zeppelin Spark解释器设置中开启
zeppelin.spark.useSparkClassLoader=true,强制Zeppelin使用Spark自带的类加载器,避免依赖Zeppelin自身的Scala库 - 若版本不一致,替换Zeppelin的Scala库为Spark对应版本的文件
3. 修改Zeppelin解释器隔离模式
Zeppelin默认使用共享解释器模式,容易引发类加载冲突:
- 在Zeppelin的解释器管理页面,找到Spark解释器,将隔离模式从
shared改为per note或per user,让每个笔记/用户使用独立的解释器实例
4. 优化代码编写方式
避免在Zeppelin代码块中直接定义自定义类(如case class),改为以下方式:
- 将自定义类打包成独立的Jar文件,通过Zeppelin解释器的"依赖"功能添加到类路径中
- 若必须在Zeppelin中定义类,将类定义放在单独的代码块中,执行完成后再运行后续的数据处理代码,避免跨代码块的类加载冲突
5. 切换Spark序列化方式为Kryo
Java序列化对类加载器敏感,切换为Kryo序列化可缓解类加载冲突:
- 在Zeppelin Spark解释器设置中添加:
(将spark.serializer=org.apache.spark.serializer.KryoSerializer spark.kryo.classesToRegister=com.yourpackage.Flightcom.yourpackage.Flight替换为实际的类全限定名,若使用Jar包方式则需要对应路径)
内容的提问来源于stack exchange,提问作者Marc Le Bihan
相关产品推荐
相关产品推荐

