赵高升(毅航)
一、背景
在对pay应用的branch777分支做单元测试并使用jacoco统计覆盖率时,发现以下问题:
1)执行时间很长,达到42分钟~5小时不等
2)没有覆盖率数据产生
由图中可看到,多次执行后,覆盖率数据都是0.00%,且单测结果有很多Error。
二、初步分析
1、对比其他分支
使用pay应用的master分支进行验证,结果如图:
1)覆盖率数据正常生成
2)执行时长都在2~3分钟左右,耗时正常
2、验证是否跟执行环境有关
1)使用master分支分别在服务器和本机执行用例,结果如下
服务器:耗时13分钟
本机:耗时12分钟
在用例执行顺畅的情况下,服务器和本机执行耗时基本一致。
2)使用branch777分支分别在服务器和本机执行用例,结果如下
服务器:一次耗时29分钟
一次耗时1小时15分,且出现java.lang.OutOfMemoryError: Java heap space
本机:耗时4小时以上,且出现OutOfMemoryError
服务器和本机都出现OutOfMemoryError。
用例执行异常时,本机执行时长明显比服务器耗时更久。
3、没有覆盖率生成常见原因检查
1)pom.xml中未配置jacoco-plugin
经检查,发现pom中有配置该plugin
2)虽然有用例执行,但用例调用的module和需要获取覆盖率的module不一致
经检查,module配置是正确的。
3)用例虽然执行了,但用例是空的,没有执行业务相关代码
经检查,发现确实有多条空用例
4、用例执行时间太长可能的原因
1)用例很多,确实需要较长时间执行
2)个别用例做了复杂的环境初始化、连接zk、调用其他接口并等待返回结果等操作。
初步结论:
1)耗时过长和机器无关,问题在branch777分支本身。
2)存在一些空用例(bug)
三、详细分析
1、查看服务器和本地执行时有问题的分支的log
1)两次均出现大量org.apache.zookeeper.ClientCnxn - Got ping response for sessionid
2)后者在38分钟后开始出现
org.apache.zookeeper.ClientCnxn - Session 0x66b8d3cd3c7f7a7 for server 172.16.120.140/172.16.120.140:2186, unexpected error, closing socket connection and attempting reconnect
java.lang.OutOfMemoryError: Java heap space
之后是长达37分钟的缓慢执行阶段。
2、查找存在问题的用例
日志中没有用例详细执行信息,无法直接定位具体哪个用例的问题,需要实际执行用例来找出具体原因。
1)比较分支和master代码的差异
3、分析问题用例
1)分析其中一条用例HopAccountQueryApiTest#remind
2)单独执行这条用例,长达3分50秒才初始化完毕,且初始化失败
执行该用例对应测试类中的其他两条用例,初始化时长为7分钟,即每条用例都会经历一个长耗时过程。
3)用例本身逻辑看起来没有特别处理,问题可能出在继承的基类上,基类有问题,会导致大量用例都出问题,导致整体耗时很长。
可看到,初始化时加载了classpath*:test-context.xml文件,和master分支的该文件进行对比,没有差异该文件没问题。
查看该文件内加载的classpath*:test-config.properties文件,和master分支的进行对比,增加了:
#注册batch任务
ons.batchSerial.topic=yangtuo-serialReceive-test
ons.batchSerial.producerId=PID_serialReceive_test
ons.batchSerial.enable=true
ons.batchSerial.rocketEnable=true
dubbo.hsc.register.group=master
dubbo.account.register.group=master
和开发确认,这个test-config.properties配置没有问题,不会对单测产生影响。
继续查看test-context.xml文件,发现其调用了pay-biz模块的classpath*:biz-context.xml文件
4)比对该分支和master的biz-context.xml文件
发现问题分支中添加了redis缓存处理
经和开发同学确认,原因就是添加的这个配置导致。
四、解决办法
1、开发修改了biz-context.xml配置,把公司封装的asd redis改为原生配置
2、重新测试,结果如图
1)覆盖率数据能正常生成
2)耗时3分钟,正常
3)Error的用例大幅减少
五、深入分析redis问题
1、公司封装的asd redis理应比较稳定,其他应用使用都正常。这个应用使用报错,很大可能还是配置有误,我们继续深入分析。
2、查看asd redis使用方法:
问题应用使用方式:
区别在于使用的注解不同
看下两个注解的区别:https://www.cnblogs.com/jichi/p/10073404.html
当使用@Autowired注解时,当没有找到有效的bean时,会抛异常NoSuchBeanDefinitionException: No qualifying bean ...
和下图我们遇到的报错信息一致:
3、解决办法
1)使用@Resource注解,用例执行通过。(注:@Resource没有指定具体的bean时,会自动按名称查找匹配的bean进行装配,也可以显式指定type或name。)
2)使用@Autowired注解,但使用@Qualifier("redisCache")指定具体的bean。
理论上应该也是可以的,但仍会报上面的错误,这又是为什么?
参考其他资料:
https://blog.csdn.net/neweastsun/article/details/78775371;(使用Spring @DependsOn控制bean加载顺序)
https://blog.csdn.net/Jintao_Ma/article/details/51088867;(depends-on的用法)
这是因为程序会先从缓存中尝试加载这个Cache bean,但这个bean是通过单例实现的,加载时还没有在spring容器中注册,加载时就会找不到。
解决办法:使用@DependOn,代码如下
这个mqClient在xml中的配置
DependOn这个MqClient,是因为是MqClient去执行拷贝Cache bean到父容器中操作的。这样加载时就可以找到了。
注:spring容器载入bean顺序是不确定的,spring框架没有约定特定顺序逻辑规范。而且不同系统,比如PC和Mac打jar包后,内部的文件顺序可能会是不同的,也会导致文件加载顺序不同,bean如果有相互依赖时,也可能会提示bean找不到。
六、深入分析OutOfMemoryError问题
1、大规模出现ZK ClientCnxn 异常日志的原因:
经调查,发现172.16.120.140:2184/2185/2186几个zk的配置都是通的,连接超时和报错前发生过OutOfMemoryError,应该是连接操作时内存不足引起的。
2、OutOfMemoryError原因分析:
因为内存泄漏不是必现的,所以考虑可能根源不单纯在于这个应用,可能跟当时的环境相关,看下当时任务执行情况:
2019-12-20 17:37:24~2019-12-20 21:37:58这次任务执行时间内,有118次jacoco任务执行,平均2分钟启动一个新任务执行。且当时同时有2个sonar任务执行,1个hop测试任务执行。多个任务同时执行,导致服务器整体内存不足。
但为什么只有这个任务出现内存泄漏呢?
这个任务每条用例都会做全量加载spring配置等初始化的操作,每次操作消耗资源都比较大,任务内存整体上处于增长趋势,最终导致OutOfMemoryError。
3、继续分析Java heap space的原因
分析思路:出现Java heap space,肯定有具体时间点,找到当时该任务正在执行哪条case及当时系统都在做什么,并结合进程使用的内存及系统剩余内存趋势一起分析,应该能有收获。
编写小工具,同步获取系统剩余内存及任务执行期间的内存趋势。
1)重新执行任务,复现问题,记录下Java heap space时间点,14:52:27
2)查看该时间点,系统剩余内存为217M,该任务进程内存为862M
系统内存偏低,与Java heap space时间点吻合。
3)任务进程内存PSS值平均都在862M上下,且无波动,可以肯定原因不在该任务本身
4)分析该时间点前后星河任务执行情况,
发现14:52前后有多个jacoco任务同时执行,系统剩余内存20秒内从1342M直接下降到217M,
5)同时考虑到该Java heap space虽然复现率高,但不是必现的,且任务执行所耗内存一直平稳,可以断定是系统同时执行任务较多,引起的内存不足。
6)本想同时获取用例执行的时间点,来定位具体执行到哪条用例。但因为用例执行前的bean初始化时就出错了,无法执行到具体的用例体,使用mvn test暂不能直接拿到当时的用例信息。有空可以再研究下。
7)问题在平台端的原因是:星河平台是单服务器执行所有任务,且有些任务执行时本身就很耗内存,多任务同时执行时就容易出现这种情况。且本例中的任务执行本身就耗时超长,中间会遇到多次系统内存低的时间段,更容易触发Java heap space异常。
8)后续会迁移部分任务到其他服务器执行,并增加任务排队功能。
七、总结
1、用例编写问题:
存在空用例,导致该部分用例执行成功,但产生的覆盖率为0
2、执行耗时原因:
1)每条用例都要经历相同的初始化过程,导致整体增加了很多不必要的耗时
2)使用公司封装的asd redis,但配置方式不对,导致bean一直加载失败,增加了耗时
3、无覆盖率数据的问题:
除了空用例外,其他用例基本都是初始化时就失败了,导致用例没有执行到方法体,于是不会产生覆盖率数据
4、OutOfMemoryError原因:
测试平台同时执行的任务较多,且都比较占内存,当单测任务初始化失败时会频繁重试并持续吃内存,导致偶现内存不足。