前言
近来帮忙排查问题的时候,发现一些刚上手的同事不太会查问题。当然,这不是啥大问题,找人帮忙也是解决的方法之一,职场上最让人难受的是藏着问题,等着暴雷。但是我发现也有些坏习惯,就是通常遇到一点框架或者中间件的代码报错,往往直接选择百度或者必应,如果搜不出来就摆烂了,默认无法解决。但是一些新一点的中间件往往没有很多人来分享,网上资料很少,为了解决这个问题,我觉得还是要养成从源码入手解决问题的习惯,接下来会根据一个小案例来切入。
Elastic-Job中间件报错导致项目无法启动
事件起源于同事的一张图,没有前因后果,上来问了一下是不是部署的Elastic-Job中间件宕机了,我看了下控制台,链接都是正常的。因为这个中间件部署正式环境有两年了,不会是配置问题,所以判定是代码问题。
在控制台也能看见该作业,这里分片待调整是还没有运行过,运行过一次后会正常变成RUNNING,状态是正常的。
回看图中的异常来解释,RegException是注册异常,提示期望状态是STARTED但是实际上是STOPPED。
但是这种中间件的异常,往往不能单单看最终的异常,因为嵌入到SpringBoot环境中,很多组件需要通过SpringBoot本身的加载机制注入。比如某个bean没有创建成功或者SpringBoot的某个组件加载失败,但加载失败并不影响SpringBoot启动,这时候就可能会导致三方的中间件因为缺失某个核心类失败,最后打印的堆栈异常也是这个核心类缺失。很明显这个堆栈日志的问题是表象,我们需要根据SpringBoot给出的详细日志进一步分析。
上面这个问题,同事说是重启一下再看看,多说一句,重启大法好!下午的时候,另一个同事过来说重启了没用,然后就这个问题进行了更进一步的现象描述
- 同样是测试环境启动,会注册到测试的定时任务平台,只要有一个节点成功,后面的就会报上面那个错误,启动失败
- 有个同事写了一个新的定时任务后就出现了这个问题,并且排除掉这个新写的定时任务就能正常启动(很好的补充,哈哈,迅速锁定代码范围)
- 在Elastic-Job的后台中手动删除该任务后,再次启动就能成功,但是后面的节点还是不能成功启动
从DevOps平台找到对应的项目,拉下代码,本地用测试环境启动,成功失败,启动不能,但是眼尖的我按照我的习惯,找到了如上一个warn,注意喔,这并不是error。找到这个Job名对应的代码块如下
都是常规写法,详见我之前写的定时模块Timer-Common(Elastic-Job包)。因为这个写法确实没问题且参考同事提供的现象2,所以怀疑是代码问题。
定时代码很简单,不过这里写法有问题,切面不生效,因为类内部调用,应该再主动注入HttpFailSchedule,用新的注入对象进行调用。当然这个在本文里不重要,看了这个代码后就排除了是业务代码导致的问题,进一步缩小问题范围。
重新回到这个warn日志,这里提示jobConfig这个自定义的定时调度配置类创建失败,原因是当前任务与注册中心冲突。具体原因是任务Dashboard-HttpFailSchedule在定时调度注册中心的类是 "com.ruijie.dashboard.schedule.supply.HttpFailScheduleEnhancerBySpringCGLIB$ 4795bf8b", 而我提交的类是 "com.ruijie.dashboard.schedule.supply.HttpFailScheduleEnhancerBySpringCGLIB$c43bef1d",如果经常Debug的朋友们应该能快速反应过来,这是很明显的一个代理后的类名。上面这个图和我描述的有出入,是因为这是后面截的图,是删除了注解后的Bean名。
在Idea上下载了依赖的中间件源码后,打开全局搜索选择作用域后输入Job conflict with register center,跳转对应源码位置。上面这里报错的地方是一个判断,判断当前JobClassName和当时注册的原始名称是否相同,为了查找这个入参newJobClassName的由来,一路向上查传入引用方法。
因为我用了ScheduleJobBootstrap的构造方法来创建定时调度任务,结合上面的传入引用跳转,找到了获取newJobClassName的方法如上图,这也就是上面错误提示中输出"com.ruijie.dashboard.schedule.supply.HttpFailSchedule <math xmlns="http://www.w3.org/1998/Math/MathML"> E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB </math>EnhancerBySpringCGLIB4795bf8b"的原因。
再结合代码,说明@Autowired引入的是一个代理对象,也就变相说明HttpFailSchedule有需要被代理进行处理的代码,说人话就是可能有切面,顺着代码中的@MonitorError("ptm-dashboard")注解,找到如上切面。
重新Debug,发现注入对象果然是一个代理对象,这也是报错的原因。因为每次生成的代理对象并不是相同的,所以必然过不了Elastic-Job的初始化校验。
当我把这个切面用到的注解去掉后,此时注入的是正常对象,并且启动正常。补充一下,启动前手动在Elastic-Job后台删一下任务。
话说之前确实没怎么注意,SpringAop居然干了这么多事,如果用@Autowired注入一个类时,如果这个类有切面,那么Spring会使用JDK动态代理(对于接口)或CGLIB(对于类)来创建一个代理对象注入。
启动困难,四个人很急
起因如图,无法启动,上来就是类找不到,不过他问问题的态度和思路都不错,截图和自己的分析都有,很棒!
通常这种类找不到的问题原因有很多,可能是IDEA出BUG了,加载不到JDK或者依赖之类的。也有可能是依赖冲突,版本问题,总之问题多种多样。
从GIT仓库拉下来代码,结果发现IDEA默认按照SVN导入,真是奇了怪。从IDEA的设置这里选择GIT进行切换,对应.idea文件夹下的vcs.xml会自动切换成git。切换后查一下历史记录,发现是一个一两年前的老项目,那没事了,该改就改。
拉下来之后启动也是啥错误没有,一般可以选择修改代码如右侧,trycatch一下启动方法,debug一下看下报错。根据错误类型发现是配置类少东西,和同事描述不一致,直接修改后启动成功。
修改后发一个新的分支,让同事测试后自行合并删除该分支,事件结束,耗时十分钟,EZ!SpringBoot这里启动方法最好都加上TryCatch,避免该打印的异常堆栈信息被吞掉,这也算是一个开发小技巧吧。
写在最后
这个案例呢,说实在的比较简单,其实我很想找一个,根据源码去做自定义修改的案例,但是最近没有正好的,只好用这个先凑合一下。养成看源码的习惯,我觉得很重要,当然也得有兴趣才行,可以先看看别人写的博客,跟着总结好的思路去看会事半功倍。最近又有好多好多事想干,之前做事有点浮躁而且也急功近利了,趁着1月份休息,会做一些有意思的事,拜啦,下篇见!