新买了一个Macbook Pro . 之前的工程搬家过来, 这天要跑个单元测试。 发现Junit4 一直处于运行中。没有错误信息,没有用例执行结果。遂开始排查原因。
这里插一句,苹果芯片的Mbp还是很好用的,除了性能够用之外,最主要是安静+不热。 这对比我之前的i7版本的真是太明显了。 之前的i7在外接显示器的情况下,风扇狂转,还降频。苹果抛弃Intel还是有道理的。
回正题, 这类比较诡异问题的排查,一般思路就是猜+试 ,但试和猜的逻辑要清晰。本文主旨也是在于思路总结。
一、测试类是不是写的有问题?
很好排查,跑跑其他之前写的测试类, 果然也不好用了。 说明跟具体的测试类无关。
二、是工程的问题还是我自身环境的问题?
小X, 我这儿为啥跑不了单元测试了。 你试试能跑了。 小X: “我这里可以跑,没问题”。 可见应该跟我的环境配置有关。
——持续在执行中,且没有错误,并且与个体环境有关。 如果有经验的同学,很容易想到,这可能说明,测试过程在正常执行,只是非常缓慢。环境相关的性能问题,往往与网络有关。
三、化简工程后,是否正常工作?
实际项目的工程往往比较复杂, 管理的Bean动辄,几百上千。 这些Bean可能有各种初始化方法, 阻塞了程序的启动,导致了Junit 卡在Spring上下文建立的过程中。测试过程就无法开始运行。
因此,缩小扫描路径范围, 对一个依赖其他Bean较少的Bean做单元测试。 发现执行正常。
——这说明应该确实是卡在了某些Bean的创建过程中
四、缩小范围
从上面的试验结果很容易想到,如果逐步将Beans加入到扫描路径,就可以定位出具体是哪个Bean引起的阻塞问题。经过尝试发现,工程用到了dubbo,其中的 provider service 越少,@Test执行的越快,当多到一定程度时,
就会表现为,一直处于运行中的状态了。
——这里已经可以定位到,导致问题的表面原因了。猜测可能是dubbo与注册中心的网络通信过程,造成的影响。
五、 源码调试
开源的好处是,源码之前了无秘密,但通常源码也没那么容易看懂 o(* ̄︶ ̄*)o。 跟了一段执行过程的源码,暂时没有发现明确的原因
——源码面前了无秘密肯定是真理,耐心跟一定会发现原因的。
六、柳暗花明
跟源码跟的不耐烦之际,觉得这么调有点费劲。想在忙碌的过程中直接调试器暂停执行,也许会断到引起阻塞的问题代码附近。于是,重新调试执行测试,在等待一段超出正常范围的时间后,按下调制器的暂停。
通过调用栈往上找找,果然是在dubbo的包内。观察了下附近有一个非常可疑的。
InetAddress.getLocalHost().getHostAddress()
ChatGpt了下,Gpt说:
注意:在生产环境中,反复调用 InetAddress.getLocalHost().getHostAddress()
是不推荐的,因为这可能是一个耗时的操作。通常,获取本地IP地址是一个一次性的任务,应该在应用程序启动时执行,并缓存结果以供后续使用。
然后阅读了附近的源码,发现dubbo当取得prodiver 的hostip 是 127.0.0.1 或者 localhost 时。会调用这个方法, 取得一个网络上的IP。(例如192.168.1.X之类)。
尝试将prodiver 的 host 从 127.0.0.1 配置为 网络IP后, 问题解决。
总结:
问题的原因是当provider host配置为 127.0.0.1 时,会调用耗时操作。 当provider 提供的service 很多时。就会成倍放大这个耗时操作的时间。这个原因跟猜测,以及经验,现象都是能相互验证的。
宝贵经验:
1. InetAddress.getLocalHost().getHostAddress()
是一个耗时操作,在某些网络环境下可能非常慢。
2.今后遇到这种一直执行中不报错的情况,应该直接点暂停,读源码 -_-||。
针对现如今高并发场景的业务系统,“并发问题” 终归是必不可少的一类(占比接近10%),每次出现问题和事故后,需要耗费大量人力成本排查分析并修复。那如果能在事前尽可能避免岂不是很香?