0


传Livy闭包head of empty list报错排查步骤

报错日志:

问题分析过程

1、报错各式各样的方法栈,但是终归于head of empty list,于是去看scala相关代码。

2、为什么List在为空的情况下,还要继续执行head方法呢,scala List map代码:

3、google “scala head of empty list”, list.map method throws "head of empty list" exception · Issue #9584 · scala/bug · GitHub中讲到Nil是一个object单例,如果有反序列化或反射的情况,就会出现单例破坏。看看Nil的代码:

PS:使用jmap、jhat,实锤有多个Nil实例情况

在spark上找到driver用的端口,netstat查找端口对应java的【pid】

把jvm内存dump下来:

jmap -dump:live,format=b,file=heap.hprof 【pid】

将jvm内存展示到web前端8087端口:

jhat -J-Xmx2g -port 8087 heap.hprof

进入web页面,搜索Nil$,查看Nil的全部实例Instances,实锤有多个Nil实例被创建。

4、接下来就是要找到到底是哪块代码将Nil反序列化或反射了,需要用到arthas工具。

需要在对应spark任务的ApplicationMaster机器上,执行java -jar arthas-boot.jar

选择ApplicationMaster进程

看下Nil的方法:sm scala.collection.immutable.Nil$

*Nil被序列化或是被反射都需要初始化,所以stack Nil*的<init>**方法

stack scala.collection.immutable.Nil$ <init>

然后执行后端接口,触发报错,捕捉Nil被初始化的方法栈,发现是livy这块反序列化的时候创建了Nil实例。查看自己的业务代码,发现确实代码是通过闭包 Job的形式提交给livy执行的。代码就定位到闭包反序列化这块。

5、具体是哪一个接口任务的Job反序列化了呢,就需要远程调试了。

远程调试步骤:

需要远程调试spark的driver端,增加livy session的spark参数:
**“spark.driver.extraJavaOptions”:”-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=15005”**

调试端口为15005,在IDEA中配置如下图步骤:

运行启动脚本,提交的session中包含调试参数

livy启动的spark driver就会等待调试触发
driver stdout日志:Listening for transport dt_socket at address: 15005

然后点击debug调试刚才的运行配置。

6、远程调试配置好,按照4步stack的方法栈,打断点到创建对象的地方,增加断点条件为当object是Nil$对象的时候暂停。
object **instanceof **scala.collection.immutable.Nil$

然后运行后端接口任务,等待执行到此处。然后在打断点到Job被反序列化出来

然后查看job对象,里面有Nil$被反序列化出来都是不允许的。都需要修改对应的接口Job闭包。(可以按照job对象的字段信息,可以找到对应的Job闭包代码)

问题代码

问题代码在形成闭包的时候,传入了scala的List,livy用的org.apache.livy.shaded.kryo.kryo.Kryo类,是java写的 没考虑到scala的一些转化,所以会在反序列化的时候,创建新的Nil实例。

最小化还原了错误代码...

规范提示

在调用livy Job任务闭包的时候,闭包中不要传入scala的List类型实例(闭包里初始化的对象不会被序列化),因为livy的序列化kryo类对scala类型不支持,会造成Nil单例破坏。

**PS**: Nil是有readResolve方法的,只是livy的kryo没去用JavaSerializer去反序列化,而是用的FieldSerializer。
标签: scala

本文转载自: https://blog.csdn.net/zhouh2/article/details/124506102
版权归原作者 zhouh2 所有, 如有侵权,请联系我们删除。

“传Livy闭包head of empty list报错排查步骤”的评论:

还没有评论