• 记一次奇妙的 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
    ------------恢复内容结束------------

  • 相关阅读:
    hibernate篇章六--demo(Hibernate之第1解之-hibernate_demo_1)
    hibernate篇章六--demo(0.准备工作)
    博客装扮3-博客园界面装扮优化教程
    java学习笔记--1_常见输入输出语句熟悉篇章
    矩阵相乘问题
    Algorithm
    hibernate篇章五--Hibernage工作原理
    hibernate篇章四-- Hibernate配置文件中hiberante.hbm2ddl.auto四个参数的配置
    hibernate篇章三-- hibernate配置文件hibernate.cfg.xml的详细解释
    hibernate篇章二--成就搭建hibernate框架
  • 原文地址:https://www.cnblogs.com/Phantom01/p/14052878.html
Copyright © 2020-2023  润新知