这是一个或许对你有用的社群
一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料:
《项目实战(视频)》:从书中学,往事中“练”
《互联网高频面试题》:面朝简历学习,春暖花开
《架构 x 系统设计》:摧枯拉朽,掌控面试高频场景题
《精进 Java 学习指南》:系统学习,互联网主流技术栈
《必读 Java 源码专栏》:知其然,知其所以然
这是一个或许对你有用的开源项目
国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。
功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号等等功能:
Boot 地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
Cloud 地址:https://gitee.com/zhijiantianya/yudao-cloud
视频教程:https://doc.iocoder.cn
来源:blog.csdn.net/lkx444368875
/article/details/127268108
Jprofile - CPU检测
线程等待
一个其他团队的比较老的dubbo服务,spring的版本在3.2.x范围,用的还是spring那一套。
由于这个服务比较核心,而且集成的组件比较多:rabbit、dubbo、es、kafka、zk、redis、cas等等一系列组件,然后开发的痛点就是本地启动时间太慢了,常常耗时接近10分钟、机器配置差点夸张到10+。抱着好奇的心理开始这一次排查之旅。
❝
启动耗时 : Artifact xxxx:war exploded: Deploy took 730,358 milliseconds
优化成果: Artifact xxxx:war exploded: Deploy took 95,078 milliseconds
❞
由于老服务启动日志偏少,索性开始之前使用过的JProfiles来作为本次的分析工具,它可以实时侦测所有代码的运行耗时。对一些启动慢链路追踪有非常好的帮助。
基于Jprofile的使用文章
❝
https://blog.csdn.net/lkx444368875/article/details/108799142
❞
通过JProfile检测,发现了一个比较耗时的点:就是
这个类的作用是:
// 通过反射判断实例是否存在
public static boolean isPresent(String className, ClassLoader classLoader) {
try {
forName(className, classLoader);
return true;
} catch (Throwable var3) {
return false;
}
}
/**
* The class name of AnnotatedElementUtils that is introduced since Spring Framework 4
*/
public static final String ANNOTATED_ELEMENT_UTILS_CLASS_NAME = "org.springframework.core.annotation.AnnotatedElementUtils";
// 往上追踪的话是查找AnnotatedElementUtils是否存在
ClassUtils.isPresent(ANNOTATED_ELEMENT_UTILS_CLASS_NAME, classLoader)
很显然这个过程就是nacos会判断AnnotatedElementUtils是否存在,通过注释我们可以了解到AnnotatedElementUtils这个类是Spring 4的版本,而这个服务还只是3.2.x,如果这个类没有相当于在扫描bean容器查看nacos相关注解的时候,所有类都会经过classLoader判断一遍,这些基本上都是无效判断,类多了耗时严重。原本想着升级Spring版本,发现牵一发动全身~那既然版本没跟上那么就直接关闭该功能呗,
通过继续往上追踪com.alibaba.spring.util.AnnotationUtils#getAnnotationAttributes到这个类时有一个tryMergedAnnotation属性的判断:
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
Class extends Annotation> annotationType,
PropertyResolver propertyResolver,
boolean classValuesAsString,
boolean nestedAnnotationsAsMap,
boolean ignoreDefaultValue,
boolean tryMergedAnnotation,
String... ignoreAttributeNames) {
AnnotationAttributes attributes = null;
if (tryMergedAnnotation) {
attributes = tryGetMergedAnnotationAttributes(annotatedElement, annotationType, propertyResolver,
classValuesAsString, nestedAnnotationsAsMap, ignoreDefaultValue, ignoreAttributeNames);
}
if (attributes == null) {
attributes = getAnnotationAttributes(annotatedElement, annotationType, propertyResolver,
classValuesAsString, nestedAnnotationsAsMap, ignoreDefaultValue, ignoreAttributeNames);
}
return attributes;
}
这个说明只有这个属性为true的时候,才会扫描加载AnnotatedElementUtils,嗯,我们可以自己定义去覆盖原本nacos自动注册的类,然后坑此坑次的在配置文件加上了。
嗯,这个问题解决了,开始启动。
效果还是有的,时间减少了40%吧,但还是远远不够,继续观察。
❝
Artifact xxxx:war exploded: Deploy took 462,016 milliseconds
❞
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
视频教程:https://doc.iocoder.cn/video/
通过日志发现经过一段时间后,不打印了。
[2022-09-28 17:15:56.913][ WARN][][][SimpleAsyncTaskExecutor-1][o.s.a.r.l.SimpleMessageListenerContainer|1168]:Consumer raised exception, processing can restart if the connection factory supports it. Exception summary: org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection timed out: connect
连接超时,这个一看是rabbitMQ的,我想应该也不会阻塞主线程吧?
这个时候我在想:主线程在干啥呀!?
"RMI TCP Connection(3)-127.0.0.1@1830" daemon prio=5 tid=0x14 nid=NA waiting
java.lang.Thread.State: WAITING
at sun.misc.Unsafe.park(Unsafe.java:-1)
at java.util.concurrent.locks.LockSupport.(LockSupport.java:-1)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedNanos(AbstractQueuedSynchronizer.java:1037)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.tryAcquireSharedNanos(AbstractQueuedSynchronizer.java:1328)
at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:277)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.getStartupException(SimpleMessageListenerContainer.java:992)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doStart(SimpleMessageListenerContainer.java:666)
- locked <0x3314> (a java.lang.Object)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.start(AbstractMessageListenerContainer.java:458)
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:167)
at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:51)
at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:339)
at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:143)
at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:108)
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:945)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
发现线程居然阻塞等待了!!!
通过观察线程栈找到SimpleMessageListenerContainer这个类的getStartupException方法,看到了令我绝望的代码
private final CountDownLatch start = new CountDownLatch(1);
public FatalListenerStartupException getStartupException() throws TimeoutException, InterruptedException {
start.await(60000L, TimeUnit.MILLISECONDS);
return this.startupException;
}
public void run() {
SimpleMessageListenerContainer.this.redeclareElementsIfNecessary();
this.consumer.start();
this.start.countDown();
// 省略
restart(this.consumer);
}
它其实是有唤醒的操作的,但是由于启动不成功,唤醒不了,又触发重试,继续等待。但我没想明白的是这个东西怎么是在主线程做的,也不确定是不是包版本太低的原因。
Rabbit连不上这个其实也是有原因的,因为我们这边的本地的都是连的测试环境的,nacos的测试环境配置是阿里云内网的地址,导致本地肯定是连不上的,所以我找了个公网地址试了一下,发现果然有用。
❝
优化成果: Artifact xxxx:war exploded: Deploy took 95,078 milliseconds
❞
其实还发现了一些其他阻塞,不过都是属于连接方面的,比如zk、数据库等等,目前这个古老的项目1分半也属于偏正常范围吧,主要还是组件太多,代码也多。
好了,本次排查到此为止。排查的手段和方案可以给大家提供一种思路~
欢迎加入我的知识星球,全面提升技术能力。
加入方式,“长按”或“扫描”下方二维码噢:
星球的内容包括:项目实战、面试招聘、源码解析、学习路线。
文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)