记一次奇妙的 spring-boot + spark debug 经历

------------恢复内容开始------------

最近在使用 spark,做了一个分词 + 词频统计求 topK 的 spark app。为了方便使用,顺便复习一下原来的 spring 和 java 知识,把它封装成了一个 spring-boot 服务。

本来用 java main 函数 + 打 jar 包的模式运行的好好的,结果上 spring-boot 就炸了。报了一个如下的错误:

java.lang.ClassCastException: cannot assign instance of java.lang.invoke.SerializedLambda to field org.apache.spark.rdd.MapPartitionsRDD.f of type scala.Function3 in instance of org.apache.spark.rdd.MapPartitionsRDD

看上去像是一个反序列化的错误。是说不能把一个 java.lang.invoke.SerializedLambda 对象转换成 scala.Function3 对象。然后发现这个错的第一反应是 spark context 的 jars 属性没设对,因为反序列化的错误多半是 jar 包路径没弄对,毕竟远程执行可能涉及序列化这边的东西,发到对面去再反序列化出来执行。如果不能 load 相应的 class 自然是不能成功反序列化的。

所以,就去看了下结果路径下面果然没有 jar 包。才发现 mvn spring-boot:run 不会打 jar 包,只会直接执行。打包还是需要 mvn package

打了一个 jar 包,并且在 spark 的 context config 里加上了这个 jar 的路径。没想到这个错仍然存在。然后就去网上搜了这个错误。网上的解决方案都是说加上相应的 jar 就完了。但是,我确实这么做了呀,反反复复试了很久都不能解决。

中间甚至怀疑会不会是 spark 或者 jdk 版本的问题。毕竟当时用的最新版的 spark 和 jdk,出现某些没人遇到过的问题也不是没有可能。但是都没能解决问题。甚至把代码切会之前非 spring 版的时候又试了试,也没啥问题。

后来又搜到一个人的 spring-boot + spark 的集成,他在使用了 transient 关键字来避免序列化一些东西。照做了但是没啥用。这时陷入了一种绝望,甚至出现了一些幻想。觉得是不是函数序列化的时候 closure 太大了,把某些 context 包含进去了。然后 spring 在依赖注入的时候不是喜欢用某些代理吗,是不是某种代理使反序列化的时候类和原来对不上了。然后,写了几个测试 api,把 spark 声明阶段那几个 rdd 实例都序列化了,也都能反序列化回来。甚至把提供服务的 service bean 也给序列化了,也没啥问题。

然后,就又回到了网上漫无目的的搜索有没有人遇见过相似情况的状态。最终逛了很久,发现有人说这个类型转换错误并不是根本的报错,而是在做函数反序列化的过程中遇到了某些问题,而最终报这个类型转换异常。多半时候类加载的问题。这个时候突发奇想,我都加上这个包了还是有问题,会不会是 jar 包本身打的有问题。就去解了一下那个 jar 包 (众所周知,jar 包和 war 包其实是一些 class 文件和其它文件的压缩包)。jar 包中目录结构如下:

├── BOOT-INF
│   ├── classes
│   ├── classpath.idx
│   └── lib
├── META-INF
│   ├── MANIFEST.MF
│   └── maven
└── org
    └── springframework

正常情况下,jar 包中顶级目录应该就是你的包名,然后 META-INF 里会有一些额外的物料说明清单,比如 main class 在哪里之类的。

而这个目录结构和预想的不太一样。比如顶级只有 springframework 的报名,而我的包名并不在这里。

这才破案了,确实是对面 spark 的 class loader 找不到类,因为 spring-boot 的打包结构比较奇怪。研究了一下,发现用户定义的 class 其实在 BOOT-INF/classes 然后 BOOT-INF/lib 里是所需的依赖包。

后面修改了下 maven 的配置,打了个相对正常的包才解决问题。

想想,可能用 submodule 把 spark operator 的逻辑和 spring 逻辑分别打成两个包,然后让 spark 引用 operator 那个包也是一种比较好的做法。

参考资料:
https://stackoverflow.com/questions/28186607/java-lang-classcastexception-using-lambda-expressions-in-spark-job-on-remote-ser/28367602#28367602

https://stackoverflow.com/questions/28079307/unable-to-deserialize-lambda/28084460#28084460
------------恢复内容结束------------

posted @ 2020-11-28 15:53  Phantom01  阅读(813)  评论(1编辑  收藏  举报