背景

线上项目checkHealth(超时时间5min)超时,然后显示服务启动失败。
其实项目启动时间已经做过几次时间调整了,从1-3-5min。

启动时间长问题会影响到:(1) 故障发生时如果需要应用回滚、扩容或者重启,启动时间长会拖慢故障恢复时间;(2) 上线时间长也是一个很大的痛点,当服务器数量比较多时,服务启动时间的影响会更明显。

设计方案

项目的耗时由那些组成:容器耗时 + 初始化Bean + 初始化任务
其中比较好把我的是初始化任务和初始化Bean 这部分,我们本文主要聊一聊这一点
方案是:

  1. 对于耗时长的任务进行手动优化,是否存在不合理的启动点
    • 比如初始化Cache中查询MySQL,是否耗时过长,查询步长可以增大等
    • Bean是否必须启动时进行初始化动作,是否可以懒加载,懒处理
  2. 并行初始化
    • 如果单任务耗时问题已解决,就可以针对必须长耗时任务进行Bean并行初始化
    • 耗时预计 从原本串行 Sum(taskTimeOut) => Max(taskTimeOut)

Spring Bean 启动耗时查询

耗时长任务统计

解决的是 @PostConstruct、InitializingBean(afterPropertiesSet)、initMethod方法
有目标或者指标才知道我们要如何优化,怎么优化
在Spring Bean中有注入点:BeanPostProcessor接口,实现postProcessorBeforeInitialization、postProcessAfterInitialization方法,对bean的总耗时进行打印,找到其中耗时较长的bean,进行针对性优化

@Component
public class SpringbeanAnalyse implements BeanPostProcessor,
        ApplicationListener<ContextRefreshedEvent> {
    private static Logger log = LoggerFactory.getLogger(SpringbeanAnalyse.class);
    private Map<String, Long>  mapBeantime  = new HashMap<>();
    private static volatile AtomicBoolean started = new AtomicBoolean(false);
 
 
    @Autowired
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws
            BeansException {
        mapBeantime.put(beanName, System.currentTimeMillis());
        return bean;
    }
 
    @Autowired
    public Object postProcessAfterInitialization(Object bean, String beanName) throws
            BeansException {
        Long begin = mapBeantime.get(beanName);
        if (begin != null) {
            mapBeantime.put(beanName, System.currentTimeMillis() - begin);
        }
        return bean;
    }
    @Override
    public void onApplicationEvent(final ContextRefreshedEvent event) {
        if (started.compareAndSet(false, true)) {
            for (Map.Entry<String,Long> entry: mapBeantime.entrySet()) {
                if (entry.getValue() > 100) {
                   log.warn("slowSpringbean => :",entry.getKey());
                }
            }
        }
    }
}

耗时长任务并行初始化

为什么不能自动:全局自动配置完成所有的bean需要解决相互依赖的问题,这个问题不是很好解决,原因在于循环依赖和Spring提供了大量的扩展能力,这样导致我们无法得到一个spring bean的全局依赖图。因此无法通过自动配置的手段来解决spring bean单线程加载的问题。
手动如何实现:
定义全局线程池,重写bean的initmethod,将任务提交到线程池中处理,最终判断线程池任务全部结束,则并行初始化完成。

异步初始化线程池一般设定多大:
当前机器CPu核数+1(线程在服务启动后自动销毁)