只要花10分钟,就能让你的应用在Aone上启动的速度可能提高100%左右

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介:

问题的提出

应用启动慢,估计是很多开发同学在平时开发过程中面临的比较痛苦问题之一,本文分享一下个人之前在应用启动优化方面的经验: 如何快速优化应用的启动速度;

启动时间过程分析

找到应用启动的慢的原因,首先都得定位一下慢慢的原因在那里,下面是我们应用latour在aone上一次启动的时间统计:
image.png
根据上图应用在 一次启动过程中所花时间主要由拉取镜像时间 + 停止应用时间 + 升级容器时间 + 启动应用时间 + 应用自检时间,其中拉取镜像时间,停止应用时间 ,升级容器时间,应用自检时间,我们暂时是无法做相关的优化,唯一能做的就是针对启动应用的时间进行优化,这个也是占应用启动时间的大头;

方案的选择

由于我们应用绝大部分都用的都是 Spring框架,由于spring bean的创建都是用反射来进行bean创建,bean创建的时间,主要是涉及到类的加载,我们基本上很难做优化,但是bean的初始化这个时间可以控制, 因为重点是放在监控bean的初始化上, 我们大家都知道,Spring框架优秀的扩展性,我们可以对 Spring容器一次启动中所有bean的初始化的时间 进行监控;可能首先想到的是用AOP,但 aop监控的可能不是太方便, 每个bean的初始化方法除了afterProperties之外,还有其它各自配置的初始化方法, 用 aop方法进行 bean的初始化监控就有点过度了,看过Spring源码的或对Spring内部机制比较熟悉同学可能都知道BeanPostProcessor , 一个bean在初始化的时候,会调用BeanPostProcessor 实现类的前置处理方法和后置处理方法,这两个方法分别每 一个bean的初始化之前和初始化之后进行相关的处理操作,在这里提一下,AOP的动态代理的实现用的是Spring框架中前置处理器进行动态代理的创建,spring中前置处理器的接口如下:

package org.springframework.beans.factory.config;

import org.springframework.beans.BeansException;

public interface BeanPostProcessor {
     /**
      * Apply this BeanPostProcessor to the given new bean instance  before  any bean 
      *    initialization callbacks
     **/
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    
    /**
      Apply this BeanPostProcessor to the given new bean instance any bean
    * initialization callbacks 
    **/
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

有了BeanPostProcessor 扩展接口,就有办法了,我们可以实现一个简单的统计bean初始化时间的BeanPostProcessor 实现类, 并放到我们应用的Spring容器中,代码如下:

package com.tmall.latour.application.performance;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/*
 * desheng.tds
 */
@Service
public class BeaninitializationPerformanceMonitor implements BeanPostProcessor {

    private static Logger logger = LoggerFactory.getLogger(BeaninitializationPerformanceMonitor.class);

    private Map<String, Long> staticsMaps = new ConcurrentHashMap<String, Long>();

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

        staticsMaps.put(beanName, System.currentTimeMillis());
        logger.error("id={},class={}", beanName, bean.getClass().getName());
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        if (staticsMaps.get(beanName) != null) {
            Long t = System.currentTimeMillis() - staticsMaps.get(beanName);
            logger.error("id={},class={},t={}ms", beanName, bean.getClass().getName(), t);
        }
        return bean;
    }
}

加上监控日志配置:

<appender name="ClassPerformanceMonitor_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${loggingRoot}/latour_classPerformanceMonitor.log</file>
        <encoding>UTF-8</encoding>
        <append>true</append>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${loggingRoot}/latour_classPerformanceMonitor.log.%d{yyyy-MM-dd}</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] - %msg%n</pattern>
        </layout>
    </appender>
 <logger name="com.tmall.latour.application.performance.BeaninitializationPerformanceMonitor" level="info" additivity="false">
        <appender-ref ref="ClassPerformanceMonitor_LOG"/>
</logger>

为了方便监控 spring 在整个启动过程中的情况,我们还可以加上如下的日志配置

<appender name="SPRING_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
       <file>${loggingRoot}/spring_info.log</file>
       <encoding>UTF-8</encoding>
       <append>true</append>
       <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
           <fileNamePattern>${loggingRoot}/spring_info.log.%d{yyyy-MM-dd}</fileNamePattern>
           <maxHistory>7</maxHistory>
       </rollingPolicy>
       <layout class="ch.qos.logback.classic.PatternLayout">
           <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}][%-5level][%logger{0}] - %msg%n</pattern>
       </layout>
   </appender>

<!--spring 日志 start-->
   <logger name="org.springframework.web.context.support.XmlWebApplicationContext" level="debug" additivity="false" >
       <appender-ref ref="SPRING_LOG" />
   </logger>
   <logger name="org.springframework.beans.factory.xml.XmlBeanDefinitionReader" level="debug" additivity="false" >
       <appender-ref ref="SPRING_LOG" />
   </logger>
   <logger name="org.springframework.context.support.PropertySourcesPlaceholderConfigurer" level="debug" additivity="false" >
       <appender-ref ref="SPRING_LOG" />
   </logger>
   <logger name="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" level="debug" additivity="false" >
       <appender-ref ref="SPRING_LOG" />
   </logger>
   <logger name="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" level="debug" additivity="false" >
       <appender-ref ref="SPRING_LOG" />
   </logger>
   <logger name="org.springframework.web.servlet.DispatcherServlet" level="debug" additivity="false" >
       <appender-ref ref="SPRING_LOG" />
   </logger>
   <logger name="org.springframework.context.support.DefaultLifecycleProcessor" level="debug" additivity="false" >
       <appender-ref ref="SPRING_LOG" />
   </logger>
   <logger name="org.springframework.web.context.ContextLoader" level="debug" additivity="false" >
       <appender-ref ref="SPRING_LOG" />
   </logger>
   <!--spring 日志 end-->

监控效果

加上上面这些监控代码之后,我们只需要提交一下本地代码,在aone重新部署一下,观察相关的日志如下,从监控日志中,我们要以看出一些初始化比较耗时的bean,下图示例中:keyStore这个bean初始化消耗6s多,是比较耗时间的
id=keyStore,class=com.taobao.common.keycenter.keystore.KeyStoreImpl,t=6586ms
image.png

从启动的监控日志中,我们要看出应用中绝大部分bean的初始化快都是非常快的,只是有些中间件(RocketMQ, 精卫,notify, TDDL, tair, 燎原)的客户端和个别业务client(uic, ic, cargo, honolulu, mtop, IC, KFC 等)启动比较慢, 然后我们可以根据需需要,对这些客户端的bean进行适当的改造,在 预发环境满足条件的,启动的速度可以大大地提高 ,此外我们还可以监控到Spring容器的启动过程:

如何优化启动慢的bean

拿latour 这个应用来说, 发现IcClient, mtop, uic, kfc这几个bean的初始化速度比较度,拿Mtop这个bean来说,原来是配置是这样的:

<bean id="mtopHsfagent" class="com.taobao.mtop.api.agent.MtopAgent" init-method="init">-->
        <property name="appName">
            <value>latour</value>   <!--特别注意,此处要修改成PE或者aone的应用名-->
        </property>
</bean>

我们发现这个bean的启动先后不影响其它bean的启动,但这个bean的启动太消耗时间,如于考虑修改成异步初始始,由于mtop这个类没有实现InitializingBean接口,因此可以实现一个Mtop的子类如下:

package com.tmall.latour.application.performance;

import com.taobao.mtop.api.agent.MtopAgent;
import org.springframework.beans.factory.InitializingBean;

/**
 * mtop启动加速
 * desheng.tds
 */
public class LatourMtopAgent extends MtopAgent implements InitializingBean {

    /**
     *表示是否是是异步启动
    */
    private boolean asyn;

    @Override
    public void afterPropertiesSet() throws Exception {

        if (!asyn) {
            this.init();
        } else {
            new Thread(new LatourMtopAgentRunnable(this)).start();
        }
    }

    private static class LatourMtopAgentRunnable implements Runnable {

        private LatourMtopAgent latourMtopAgent;

        public LatourMtopAgentRunnable(LatourMtopAgent latourMtopAgent) {

            this.latourMtopAgent= latourMtopAgent;
        }

        @Override
        public void run() {
            latourMtopAgent.init();
        }
    }

    public void setAsyn(boolean asyn) {
        this.asyn = asyn;
    }
}

新的bean配置如下

<bean id="mtopHsfagent" class="com.tmall.latour.application.performance.LatourMtopAgent" >
        <property name="appName" value="latour"/>
        <property name="asyn" value="${asyn}" />
</bean>

再看IcClient这个类,监控发现这个类初始化的时间特别长,30s~50s不等,这个bean的启动也不太影响其它的bean(至少来说在预发环境是这样的),但这个bean的启动优化方案不能参考上面mtop优化方案,因为IcClient这个类实现了InitializingBean接口,可以考虑用代理的方法来解决这个问题,我们也重新写一个类,命名为LatourIcClient,代码如下:

package com.tmall.latour.application.performance;

import com.taobao.item.bootstrap.IcClient;
import com.taobao.item.exception.IcException;
import com.taobao.uic.common.util.ClientInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;

public class LatourIcClient implements InitializingBean{

    private static Logger logger = LoggerFactory.getLogger(LatourIcClient.class);

    private IcClient icClient;

    private boolean asyn;

    public void afterPropertiesSet() throws IcException {

        if (!asyn) {
            icClient = createIcClientInstance();
            icClient.afterPropertiesSet();
        } else {
            new Thread(new LatourIcRunnable(this)).start();
        }

    }

    private static IcClient createIcClientInstance() {
        IcClient icClient = new IcClient();
        icClient.setAppName("latour");
        icClient.setAllowCustomClientInfo(false);
        ClientInfo clientInfo = new ClientInfo();
        clientInfo.setAppName("latour");
        clientInfo.setEmail("*********");
        icClient.setClientInfo(clientInfo);
        return icClient;
    }

    private static class LatourIcRunnable implements Runnable {

        private LatourIcClient latourIcClient;

        public LatourIcRunnable(LatourIcClient latourIcClient) {

            this.latourIcClient = latourIcClient;
        }

        @Override
        public void run() {
            try {
                latourIcClient.icClient = createIcClientInstance();
                latourIcClient.icClient.afterPropertiesSet();
            } catch (Exception e) {
                logger.error("LatourIcClient init failed", e);
                throw new RuntimeException(e);
            }
        }
    }

    public void setAsyn(boolean asyn) {
        this.asyn = asyn;
    }
}
<bean id="icClient" class="com.taobao.item.bootstrap.IcClient">
        <property name="appName" value="latour"/>
        <property name="clientInfo" ref="clientInfoForUic" />
</bean>

新的bean如下:

<bean id="icClient" class="com.tmall.latour.application.performance.LatourIcClient">
        <property name="asyn" value="${asyn}"/>
</bean>

在此特别说明一下,优化Spring bean的初始化时间,方法有多种,但一定要结果具体的bean的情况进行具体分析,一般有继承子类,代理,lazy-init等方法,为了安全起见,asyn这个环境变量,在日常环境和预发环境设置为true,表示慢bean的初始化用异步的方式,在线上最好还是设置成false,表示慢bean的初始化用同步的方式,毕竟大家不经常线上发布应用,不会在乎这多余2min左右的启动时间,但是优化启动时间对开发环境 日常和预发的意义就比较大;

优化效果

由于latour是一个比较老的应用,有部分代码是无用的,可以下线,经过精减代码和针对初始化比较慢的bean进行异步化优化改造,启动速度大提高,优化之前,latour的启动情况如下:
image.png
优化后,latour的启动情况如下:
image.png

整体启动时间由原来240s减少到110s 左右,应用启动速度整体上提高了近100%

目录
相关文章
|
Arthas 缓存 Java
Arthas 实战,助你解决同名类依赖冲突问题(上)
上篇文章『程序员需要了解依赖冲突的原因以及解决办法』中,阿粉分析 Maven 依赖冲突分为两类: 项目同一依赖应用,存在多版本,每个版本同一个类,可能存在差异。 项目不同依赖应用,存在包名,类名完全一样的类。 第二种情况,往往是这个场景,本地/测试环境运行的都是好好的,上线之后测试就是不行。这其实与 JVM 类加载有关,本地/测试环境加载正确类,而生产环境加载错的类,为什么会这样? 主要有两个原因: 同一个类只会被加载器加载一次 不同环境,类的加载顺序不同
Arthas 实战,助你解决同名类依赖冲突问题(上)
|
3月前
|
XML 前端开发 调度
上下文爆炸?揭秘智能压缩术:引用机制让多智能体飞起来​
本文探讨多智能体协作调度中的层级指挥模式及其在实际应用中的性能与体验优化。针对 React 模式在工具调用、上下文管理、任务总结与过程监督等方面的痛点,提出流式 XML 工具调用、上下文压缩、通用推理兜底、任务总结增强与 MCP 监督机制等改进方案,有效提升任务执行效率与系统稳定性,为多智能体系统优化提供实践参考。
239 0
|
9月前
|
前端开发 Java 关系型数据库
基于ssm的社区物业管理系统,附源码+数据库+论文+任务书
社区物业管理系统采用B/S架构,基于Java语言开发,使用MySQL数据库。系统涵盖个人中心、用户管理、楼盘管理、收费管理、停车登记、报修与投诉管理等功能模块,方便管理员及用户操作。前端采用Vue、HTML、JavaScript等技术,后端使用SSM框架。系统支持远程安装调试,确保顺利运行。提供演示视频和详细文档截图,帮助用户快速上手。
367 17
|
SQL Java OLAP
Hologres 入门:实时分析数据库的新选择
【9月更文第1天】在大数据和实时计算领域,数据仓库和分析型数据库的需求日益增长。随着业务对数据实时性要求的提高,传统的批处理架构已经难以满足现代应用的需求。阿里云推出的 Hologres 就是为了解决这个问题而生的一款实时分析数据库。本文将带你深入了解 Hologres 的基本概念、优势,并通过示例代码展示如何使用 Hologres 进行数据处理。
1190 2
|
小程序
【微信小程序-原生开发】实用教程21 - 分包
【微信小程序-原生开发】实用教程21 - 分包
745 0
|
算法 安全 Java
Java表达式和规则引擎的比较与考量
Java表达式和规则引擎的比较与考量
1010 0
|
人工智能 安全 Devops
让研发规范管得住,在流水线之上做研发流程
研发规范的目标,是为了解决或降低出现软件危机的风险。但传统流水线受限于工具的定位,无法解决研发规范的落地问题,需要在更高的层面来解决。阿里云云效团队经过内部启发后推出的新产品:云效应用交付平台 AppStack 给出了解决方案,快来使用体验吧!
79814 7
|
Java 测试技术
Java反射之Method的invoke方法详解
Java反射之Method的invoke方法详解
|
JSON Java Maven
使用Jackson进行 JSON 序列化和反序列化
使用Jackson进行 JSON 序列化和反序列化
463 0
|
5G 存储 人工智能
带你读《智慧光网络:关键技术、应用实践和未来演进》——1.4 光网络演变趋势
带你读《智慧光网络:关键技术、应用实践和未来演进》——1.4 光网络演变趋势

热门文章

最新文章