最近测试反馈了一个bug,经过查看日志发现是mq消息重复消费导致的,一开始连续重试,等重试了一会,后来就变成了每隔两分钟重试一次。这应该是mq框架的功能,遇到某种情况,mq框架认为消费者没有能成功把消息消费掉,所以一直不断尝试,重新消费。
最大的可能就是业务代码抛异常了,一路抛给了mq框架,所以mq框架才会如此鬼畜。
但是在项目日志中完全没看到异常日志啊?怎么回事。
可能正是因为异常一路抛出去了,在业务代码中都没有catch,因此也没有打印。
继续查看现有日志和涉及到的代码,并在接收mq消息的listener方法处,把所有代码都try catch起来,打印出日志,然后再抛出去,如下:
@Override public void onMessage(List<Message> messages) throws Exception { try { //业务代码 } catch (Exception e) { logger.error("catch exception: "+e); e.printStackTrace(); throw e; } }异常终于打出来了,是空指针异常… 报错的代码行如下:
if (member != null && member.getHasAutoLevel() == AutoLevelEnum.NO.value) {已经确定 member 不为空了,想了半天都不知道这行代码为啥报空指针异常。
hasAutoLevel 类型为 Short,AutoLevelEnum.NO.value为int。hasAutoLevel 的确为空,但这怎么会导致空指针异常呢,又没有操作它。
后来发现,这是一个 Short引用类型与int基本类型的 == 比较,编译器会自动将引用的包装类型进行拆箱,即调用 Short.shortValue(),而hasAutoLevel 为null,在null上调用因此会报空指针!
总结: 1. 此次修复这个bug,耗时几乎三天,之所以耗时这么长,最主要的原因是对日志框架以及异常传播行为不熟悉。由于业务代码中不可能将所有代码全部包在try catch(Exception)中,因此有些运行时异常(RuntimeException)不能被捕捉到,因此会沿着方法调用链一层一层地往上抛,如果最终抛到了一个第三方框架中,业务层代码没有捕捉到该异常,而项目中配置的日志收集器并不收集此第三方框架的日志,那么项目日志中便记录不到此异常信息。 2. 包装类型与基本类型进行 == 比较会涉及到自动拆箱,如果包装类型为null,那么就会抛出空指针异常。 3. 消费者在消费mq消息时,如果把异常抛给mq框架,那么mq框架会认为消息没有成功被消费,因此会一直重试消费,直到将其消费完毕。