如果你的系统启动耗时250s以上,文章思路应该能够帮到你。
一、背景
近期,在做利用启动提速相干工作的过程中,咱们发现,利用启动速度次要的瓶颈在于bean的初始化过程(init,afterPropertiesSet办法的耗时)。很多中间件bean的初始化逻辑波及到网络io,且在没有相互依赖的状况下串行执行。将这一部分中间件bean进行异步加载,是晋升启动速度的一个摸索方向。
二、解决方案
- 主动扫描可批量异步的中间件bean,而后,在bean的初始化阶段利用线程池并行执行其初始化逻辑。
- 容许应用方自行配置耗时bean以享受异步减速能力。(需应用方自行确认依赖关系满足异步条件)
三、原理
3.1 异步初始化原理
3.1.1 如何异步init和afterPropertiesSet?
3.1.1.1 这俩初始化办法在哪里执行的?
在AbstractAutowireCapableBeanFactory#invokeInitMethods办法(以下代码省略异样解决以及日志打印)
protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd) throws Throwable { // 先看bean是不是实现了InitializingBean,如果是则执行afterPropertiesSet办法。 boolean isInitializingBean = (bean instanceof InitializingBean); if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) { if (System.getSecurityManager() != null) { AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> { ((InitializingBean) bean).afterPropertiesSet(); return null; }, getAccessControlContext()); } else { ((InitializingBean) bean).afterPropertiesSet(); } } // xml定义的init办法 if (mbd != null && bean.getClass() != NullBean.class) { String initMethodName = mbd.getInitMethodName(); if (StringUtils.hasLength(initMethodName) && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) && !mbd.isExternallyManagedInitMethod(initMethodName)) { invokeCustomInitMethod(beanName, bean, mbd); } }}
- 调用位置图
3.1.1.2 如何自定义该办法逻辑使其反对异步执行?
- 很简略的想法
有没有可能,我能够替换原有的BeanFactory,换成我自定义的一个BeanFactory,而后我继承他,只是重写invokeInitMethods办法逻辑使其反对异步?像这样:
public class AsyncInitBeanFactory extends DefaultListableBeanFactory { private static final Logger logger = LoggerFactory.getLogger(AsyncInitBeanFactory.class); // 省略 @Override protected void invokeInitMethods(String beanName, Object bean, RootBeanDefinition mbd) throws Throwable { if (AsyncInitBeanNameContainer.MIDDLEWARE_ASYNC_HSF_BEAN_NAME.contains(beanName)) { // hsf异步init this.asyncCallInitMethods(TaskUtil.threadPool4HsfBean, beanName, bean, mbd); } else if (AsyncInitBeanNameContainer.MIDDLEWARE_ASYNC_INIT_BEAN_NAME.contains(beanName)) { // 其余bean异步init this.asyncCallInitMethods(TaskUtil.threadPool4NormalMBean, beanName, bean, mbd); } else { // 同步init call父类原来的invokeInitMethods try { super.invokeInitMethods(beanName, bean, mbd); } catch (Exception e) { logger.error("middleware-bean-accelerator sync-init error: {}", e.getMessage(), e); throw e; } } } // 省略}
那当初曾经有了自定义办法了,只有解决替换就行了呗?
- 怎么替换?
实现ApplicationContextInitializer接口,ApplicationContextInitializer在ApplicationContext做refresh之前能够对ConfigurableApplicationContext的实例做进一步的设置或者解决。在这里能够用反射替换掉原BeanFactory。像这样:
public class AsyncAccelerateInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered { @Override public void initialize(ConfigurableApplicationContext context) { // 是否开启异步初始化 if (ConfigUtil.isEnableAccelerate(context) && context instanceof GenericApplicationContext) { AsyncInitBeanFactory beanFactory = new AsyncInitBeanFactory(context.getBeanFactory()); // 通过反射替换beanFactory try { Field field = GenericApplicationContext.class.getDeclaredField("beanFactory"); field.setAccessible(true); field.set(context, beanFactory); } catch (Throwable e) { throw new RuntimeException(e); } } }}
之后咱们只须要在spring.factories文件将其注册即可。
这样一来就实现了咱们一开始的指标,让init办法和afterPropertiesSet反对异步执行。
3.1.2 如何异步PostConstruct?
3.1.2.1 @PostConstruct在哪执行的?
在CommonAnnotationBeanPostProcessor#postProcessBeforeInitialization办法
- 这是哪里?
CommonAnnotationBeanPostProcessor实现了BeanPostProcessor接口,postProcessBeforeInitialization办法是BeanPostProcessor的办法。BeanPostProcessor在初始化阶段被调用。
- org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization
@Overridepublic Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; // 把BeanPostProcesss都抓进去调用一下 for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessBeforeInitialization(result, beanName); if (current == null) { return result; } result = current; } return result;}
- 调用位置图
3.2.1.2 如何自定义该办法逻辑使其反对异步执行?
- 很简略的想法
有没有可能,我能够去掉原有的CommonAnnotationBeanPostProcessor,换成我自定义的一个BeanPostProcessor,而后我继承他,只是重写postProcessBeforeInitialization办法逻辑使其反对可异步的@PostConstruct 办法?像这样:
public class AsyncCommonAnnotationBeanPostProcessor extends CommonAnnotationBeanPostProcessor { private static final Logger logger = LoggerFactory.getLogger(AsyncCommonAnnotationBeanPostProcessor.class); @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { // 如果是我指定的beanName 那么走异步初始化, 把super.postProcessBeforeInitialization(bean, beanName) 放进线程池里执行 if (AsyncInitBeanNameContainer.MIDDLEWARE_ASYNC_POST_CONSTRUCT_BEAN_NAME.contains(beanName)) { // 异步初始化 this.asyncExecutePostConstruct(bean, beanName); } else { // 同步初始化 return super.postProcessBeforeInitialization(bean, beanName); } return bean; } // 略}
那当初曾经有了自定义办法了,只有解决替换就行了呗?
- 怎么替换?
实现InstantiationAwareBeanPostProcessorAdapter接口,其中有一个办法叫做postProcessBeforeInstantiation。postProcessBeforeInstantiation办法是对象实例化前最先执行的办法,它在指标对象实例化之前调用,该办法的返回值类型是Object,咱们能够返回任何类型的值。因为这个时候指标对象还未实例化,所以这个返回值能够用来代替本来该生成的指标对象的实例(比方代理对象)。
像这样:
public class OverrideAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter { @Override public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { // 替换掉原解决@PostConstruct注解的后置处理器 if ("org.springframework.context.annotation.internalCommonAnnotationProcessor".equals(beanName)) { AsyncCommonAnnotationBeanPostProcessor asyncBeanPostProcessor = new AsyncCommonAnnotationBeanPostProcessor(); // 省略根底的设置 return asyncBeanPostProcessor; } return super.postProcessBeforeInstantiation(beanClass, beanName); }}
这样一来就实现了咱们一开始的指标,让@PostConstruct办法反对异步执行。
3.2 批量扫描&异步加载中间件Bean原理
中间件bean批量异步实现案例以RPC为例RPC是后端日常开发中最常见的中间件之一,HSF是阿里外部常见的RPC中间件,3.2节的讲述咱们以HSF为案例,实现HSFConsumerBean的批量异步初始化。
3.2.1 如何获取待异步的Bean信息?
3.2.1.1 HSF Consumer是怎么样应用的?
与Dubbo类似,对于使用者而言,只需在成员变量上加上@HSFConsumer注解,服务启动过程中HSF就会将实现了近程调用的代理对象注入成员变量。如下:
@Servicepublic class XXXService { @HSFConsumer(serviceVersion = "1.0.0", serviceGroup = "HSF") private OrderService orderService; // 省略}
3.2.1.2 如何通过Consumer的注解获取Bean信息?
如3.2.1.1节所示,被注入代理对象的成员变量字段上带有@HSFConsumer注解,这样,咱们是不是能够利用该注解在启动过程中找到这些Bean,并对其施行异步初始化解决?答案是必定的通过实现BeanFactoryPostProcessor接口,咱们能够在beanDefinition被扫描&记录后,在postProcessBeanFactory办法中获取所有bean的定义信息,并找出其中带有@HSFConsumer注解的bean进行记录,以便在后续调用init办法时(见3.1.1.2节)进行异步初始化。
public class HsfBeanNameCollector implements BeanFactoryPostProcessor, BeanClassLoaderAware { private ClassLoader classLoader; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { // 省略 for (String beanName : beanFactory.getBeanDefinitionNames()) { BeanDefinition definition = beanFactory.getBeanDefinition(beanName); String beanClassName = definition.getBeanClassName(); Class<?> clazz = ClassUtils.resolveClassName(definition.getBeanClassName(), this.classLoader); ReflectionUtils.doWithFields(clazz, field -> { if (AnnotationUtils.getAnnotation(field, HSFConsumer.class) == null) { return; } // 收集HsfConsumerBeanName不便后续异步化 AsyncInitStaticVariables.MIDDLEWARE_ASYNC_HSF_BEAN_NAME.add(field.getName()); }); } } @Override public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; }}
3.3.2 如何平安异步HSFSpringConsumerBean?
3.3.2.1 咱们加@HSFConsumer注解的成员变量是如何被注入动静代理类的?
HSFSpringConsumerBean实现了FactoryBean接口,其中的getObject办法会在属性注入时被调用,获取其返回值,注入成员变量。而真正接口的实现(也就是动静代理类)就是在这里被注入的。
public class HSFSpringConsumerBean implements FactoryBean, InitializingBean, ApplicationListener, ApplicationContextAware { // 省略 @Override public Object getObject() throws Exception { return consumerBean.getObject(); } // 省略}
而该动静代理类是如何生成的呢?答案在HSFApiConsumerBean的init办法中
如下所示:metadata.setTarget(consume(metadata));
public class HSFApiConsumerBean { // 省略 /** * 初始化 * * @throws Exception */ public void init() throws Exception { // 省略 synchronized (metadata) { // 省略 metadata.init(); try { // 动静代理类的设置就在这里 metadata.setTarget(consume(metadata)); // 省略 } catch (Exception e) { // 省略 } catch (Throwable t) { // 省略 } // 省略 } } // 省略}
3.3.2.2 会存在什么问题?
- 动静代理对象的生成在init阶段意味着什么?
意味着bean初始化如果未实现,会为成员变量注入一个null值,导致consumer不可用,这是异步的微小危险。
3.3.2.3 咱们的解决方案
自定义一个NewHsfSpringConsumerBean,继承HSFSpringConsumerBean并重写getObject办法,在父类的getObject办法执行前期待初始化工作实现。
像这样:
public class NewHsfSpringConsumerBean extends HSFSpringConsumerBean { // 省略 private Future<?> initTaskFuture; /** * 重写NewHsfSpringConsumerBean的次要目标 在此退出卡点 避免hsfSpringConsumerBean未初始化实现导致的npe * * @return * @throws Exception */ @Override public Object getObject() throws Exception { this.waitHsfInit(); return super.getObject(); } private void waitHsfInit() { if (this.initTaskFuture == null) { logger.warn("middleware-bean-accelerator, hsf getObject wait future is null."); return; } try { this.initTaskFuture.get(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } } // 省略}
当初的问题就是咱们如何将原有的HSFSpringConsumerBean替换成NewHsfSpringConsumerBean?
答案还是InstantiationAwareBeanPostProcessorAdapter接口
如下所示:
public class OverrideAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter { private final AsyncInitBeanFactory beanFactory; // 省略 @Override public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { // 批改beanDefinition 使容器创立自定义的HsfSpringConsumerBean if (beanClass == HSFSpringConsumerBean.class) { this.reviseBeanDefinition(beanName, NewHsfSpringConsumerBean.class); // 返回null能够让实例化的工作交由spring容器 return null; } return super.postProcessBeforeInstantiation(beanClass, beanName); } @Override public boolean postProcessAfterInstantiation(Object bean, String beanName) { if (bean.getClass() == NewHsfSpringConsumerBean.class) { this.reviseBeanDefinition(beanName, HSFSpringConsumerBean.class); } return super.postProcessAfterInstantiation(bean, beanName); } /** * 批改beanDefinition * 设置NewHsfSpringConsumerBean使容器创立自定义的HsfSpringConsumerBean 实例化后设置回来 * * @param beanName * @return */ private void reviseBeanDefinition(String beanName, Class<?> clazz) { try { Method methodOfRootBeanDefinition = this.beanFactory.getClass(). getSuperclass().getSuperclass().getSuperclass(). getDeclaredMethod("getMergedLocalBeanDefinition", String.class); methodOfRootBeanDefinition.setAccessible(true); RootBeanDefinition beanDefinition = (RootBeanDefinition) methodOfRootBeanDefinition.invoke(this.beanFactory, beanName); // 重点步骤: 批改beanDefinition 使容器创立自定义的HsfSpringConsumerBean, 并在实例化后设置回来 beanDefinition.setBeanClass(clazz); } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) { throw new RuntimeException(e); } }}
咱们在实例化之前,批改beanDefinition,使容器创立自定义的HsfSpringConsumerBean。而后在实例化后的阶段将beanDefinition改回,这样就十分优雅实现了对原有HSFSpringConsumerBean的替换动作!
四、成果
4.1 性能成果
作者|严熠彬(言益)
原文链接
本文为阿里云原创内容,未经容许不得转载。