思路:
1. 考虑各种配置链接耗时
比如数据库连接、redis连接、mq连接等这些链接地址可能因为网段不通导致连接超时,影响启动时长。
下面是我的一次排查记录
查看控制台日志
发现耗时2分钟左右,找到耗时的类DruidDataSource,根据日志找追踪源码,994行打印,耗时应该在994行上面的代码。
找到源码打印的地方,往上逐一排查可能耗时的逻辑,可以把这个初始化方法打断点挨着找,
我是找到循环的地方,可能有多次循环或者链接等待超时,如下图
继续排查:先说第2个地方可能线程阻塞耗时,进去上面两个方法发现内部没有太多的耗时操作,断点过程也发现这里CountDownLatch数量2,没有影响耗时。
继续排查:第1个地方,断点发现这里处理初始化连接数量,由于我的项目是多数据源,前面的数据源初始化连接数量都设置initialSize=1,很快就创建完成,最后一个数据源初始化连接设置initialSize=100,这里需要一直创建出设置的数量,耗时巨大。
2. 检查spring bean 加载耗时
准备了两个类,在Spring boot项目启动的时候记录bean的加载耗时时间。
/**
* @author : Eric
*
*/
@Component
public class LaunchTimeManager implements ApplicationListener<ContextRefreshedEvent> {
private Map<String, LaunchTime> launchTimeMap = new ConcurrentHashMap<>();
public void beanStart(String beanName, Long startTime) {
if (Objects.nonNull(launchTimeMap.get(beanName))) {
System.out.printf("------------%s Bean重复启动 !!!-------------\n", beanName);
return;
}
LaunchTime launchTime = new LaunchTime();
launchTime.setBeanName(beanName);
launchTime.setStartTime(startTime);
launchTimeMap.putIfAbsent(beanName, launchTime);
}
public void beanEnd(String beanName, Long endTime) {
if (Objects.isNull(launchTimeMap.get(beanName))) {
System.out.printf("------------%s Bean还未启动 !!!-------------\n", beanName);
return;
}
launchTimeMap.computeIfPresent(beanName, (k, v) -> {
v.setEndTime(endTime);
return v;
});
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("Spring容器启动完成");
launchTimeMap.values().stream()
.sorted((e1, e2) -> Long.valueOf(e2.getCostTime() - e1.getCostTime()).intValue())
.limit(20)
.forEach(e -> {
System.out.printf("启动耗时, beanName:%s, 耗时:%s \n", e.getBeanName(), e.getCostTime());
});
}
/**
* Bean启动时间
*/
public static class LaunchTime {
private String beanName;
private Long startTime;
private Long endTime;
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public Long getStartTime() {
return startTime;
}
public void setStartTime(Long startTime) {
this.startTime = startTime;
}
public Long getEndTime() {
return endTime;
}
public void setEndTime(Long endTime) {
this.endTime = endTime;
}
public Long getCostTime() {
return (endTime == null ? 0L : endTime) - (startTime == null ? 0L : startTime) ;
}
}
}
/**
* @author : Eric
*
*/
@Component
public class LaunchTimeBeanPostProcessor implements BeanPostProcessor, InstantiationAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor {
@Autowired
private LaunchTimeManager launchInfoManager;
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
}
@Override
public void resetBeanDefinition(String beanName) {
MergedBeanDefinitionPostProcessor.super.resetBeanDefinition(beanName);
}
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
launchInfoManager.beanStart(beanName, System.currentTimeMillis());
return InstantiationAwareBeanPostProcessor.super.postProcessBeforeInstantiation(beanClass, beanName);
}
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
launchInfoManager.beanEnd(beanName, System.currentTimeMillis());
return InstantiationAwareBeanPostProcessor.super.postProcessAfterInstantiation(bean, beanName);
}
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
return InstantiationAwareBeanPostProcessor.super.postProcessProperties(pvs, bean, beanName);
}
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
return InstantiationAwareBeanPostProcessor.super.postProcessPropertyValues(pvs, pds, bean, beanName);
}
}
最后结合自己的项目情况,逐步排查,相信大家也能找到对应的问题所在