前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >应用启动加速-并发初始化spring bean

应用启动加速-并发初始化spring bean

作者头像
方丈的寺院
发布于 2022-11-08 05:22:48
发布于 2022-11-08 05:22:48
1.3K00
代码可运行
举报
文章被收录于专栏:方丈的寺院方丈的寺院
运行总次数:0
代码可运行

背景

随着需求的不断迭代,服务承载的内容越来越多,依赖越来越多,导致服务启动慢,从最开始的2min以内增长到5min,导致服务发布很慢,严重影响开发效率,以及线上问题的修复速度。所以需要进行启动加速。

方案

应用启动加速的优化方案通常有

  1. 编译阶段的优化,比如无用依赖的优化
  2. dockerfile的优化
  3. 依赖的中间件优化,中间件有大量的网络连接建立,有很大的优化手段
  4. 富客户端的优化
  5. spring bean加载的优化 spring容器加载bean是通过单线程加载的,可以通过并发来提高加载速度。

鉴于1的优化难度比较大,2、3、4则一般与各个公司里的基础组件有很大相关性,所以本篇只介绍spring bean加载的优化。

spring bean 加载耗时分析

分析bean加载耗时

首先需要分析加载耗时高的bean。spring bean 耗时 = timestampOfAfterInit - timestampOfBeforeInit.可以通过扩展BeanPostProcessor来实现,代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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() > 1000) {
                   log.warn("slowSpringbean => :",entry.getKey());
                }
            }
        }
    }
}

这样我们就能得到应用中耗时比较高的spring bean。可以看下这些bean的特点,大部分都是在afterPropertiesSet,postconstruct,init方法中有初始化逻辑

eg. AgentConfig中有个构建bean,并调用init方法初始化。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Bean(initMethod="init')
BeanA initBeanA(){
xxx
}

bean的生命周期

sampleCode

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
@Configuration
public class BeanC implements EnvironmentAware, InitializingBean{
    public BeanC() {
        System.out.println("constructC");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterC"  + Thread.currentThread().getName() + Thread.currentThread().getId());
    }

    @Resource
    public void resource(Environment environment) {
        System.out.println("resourceC");
    }

    @PostConstruct
    public void postConstruct() {
        System.out.println("postConstructC" +Thread.currentThread().getName() + Thread.currentThread().getId());
    }

    @Override
    public void setEnvironment(Environment environment) {
        System.out.println("EnvironmentC");
    }


    public void init(){
        System.out.println("InitC");
    }

}

输出结果

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
constructC
resourceC
EnvironmentC
postConstructC
afterC

看下代码 单个类的加载顺序org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory

image.png

单个类的方法顺序是确定的,但是不同类的加载顺序是不确定的。默认是按照module,package的ascii顺序来加载。但这个类的初始化顺序不是固定的,在不同机器上表现形式不一样。类似于Jvm加载jar包的顺序

控制不同类的加载顺序

可以通过以下方法来控制bean加载顺序

  1. 依赖 @DependOn
  2. bean依赖 构造器,或者@Autowired
  3. @Order 指定顺序

对BeanB添加了BeanC的依赖,输出结果为

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
constructC
resourceC
constructB
resourceB
EnvironmentB
postConstructB
afterB
EnvironmentC
postConstructC
afterC

这时候bean的加载顺序为

  1. 调用对象的构造函数
  2. 为对象注入依赖,执行依赖对象的初始化过程
  3. 执行PostConstruct,afterPropertiesSet等生命周期方法。

这意味着我们可以按照bean的加载的各个阶段进行优化。

并发加载spring bean

全局依赖拓扑

因为spring容器管理bean是单线程加载的,所以耗时慢,我们的解决思路是通过并发来优化,通过并发的前提是相互没有依赖。这个显然是不现实的,一个应用中的spring bean有大量依赖,甚至是有很多循环依赖。

对于循环依赖,可以通过分解拓扑关系来解决。但是按照我们上面分析,spring又提供了大量的扩展能力,让开发者去定义bean的依赖,这样导致我们无法得到一个spring bean的全局依赖图。因此无法通过自动配置的手段来解决spring bean单线程加载的问题。

局部异步加载

既然无法通过全自动配置手段来完成所有bean的全自动并发加载,那我们退而求其次,通过手动配置耗时分析中得到的,耗时比较高的bean。这样特殊处理也能达到我们优化启动时间目的。

同时因为单个bean加载有多个阶段,有些阶段耗时并不高,都是通用的操作,可以继续委托spring 容器去管理,这样就不必去处理复杂的循环依赖的问题。

按照这个思路,解决方案就比较简单

  1. 定义待并发加载的bean
  2. 重写bean的initmethod,如果是在第一步的配置里,就提交到线程池中,如果不在,就调用父类的加载方法

总结

最后通过并发加载原本耗时超过1s的bean,将我们的其中一个微服务启动耗时时间降低了100s,取得了阶段性的成果。

当然这个方案并不是很完善,

  1. 需要依赖人工配置,做不到自动化
  2. 安全得不到保障,需要确保不同bean之间afterPropertiesSet等扩展方法中无依赖。当然这一点不止是并发加载时需要保障,即使是单线程加载时也需要保障,原因是bean的加载顺序得不到保障,可能会引发潜在的bug。

欢迎提出新的优化方案讨论。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-07-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 方丈的寺院 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
不知道吧?Spring Bean初始化/销毁竟然有这么多姿势
日常开发过程有时需要在应用启动之后加载某些资源,或者在应用关闭之前释放资源。Spring 框架提供相关功能,围绕 Spring Bean 生命周期,可以在 Bean 创建过程初始化资源,以及销毁 Bean 过程释放资源。Spring 提供多种不同的方式初始化/销毁 Bean,如果同时使用这几种方式,Spring 如何处理这几者之间的顺序?
Bug开发工程师
2020/03/12
1.8K0
不知道吧?Spring Bean初始化/销毁竟然有这么多姿势
掌握这些 Spring Boot 启动扩展点,已经超过 90% 的人了!
来源:jianshu.com/p/38d834db7413 1.背景 Spring的核心思想就是容器,当容器refresh的时候,外部看上去风平浪静,其实内部则是一片惊涛骇浪,汪洋一片。Springboot更是封装了Spring,遵循约定大于配置,加上自动装配的机制。很多时候我们只要引用了一个依赖,几乎是零配置就能完成一个功能的装配。 我非常喜欢这种自动装配的机制,所以在自己开发中间件和公共依赖工具的时候也会用到这个特性。让使用者以最小的代价接入。想要把自动装配玩的转,就必须要了解spring对于bean的
程序猿DD
2022/09/13
9720
掌握这些 Spring Boot 启动扩展点,已经超过 90% 的人了!
Spring5 - Bean的初始化和销毁的4种方式
概述 针对单实例bean的话,容器启动的时候,bean的对象就创建了,而且容器销毁的时候,也会调用Bean的销毁方法 针对原型bean的话,容器启动的时候,bean是不会被创建的而是在获取bean的
小小工匠
2021/08/17
5690
Spring高手之路6——Bean生命周期的扩展点:BeanPostProcessor
在前一篇讲解生命周期的时候就可以讲解后置处理器了,但是内容比较多,还是分开来讲解。
砖业洋__
2023/06/27
1K0
Spring高手之路6——Bean生命周期的扩展点:BeanPostProcessor
OpenSource - Spring Startup Ananlyzer
Spring Startup Ananlyzer 采集Spring应用启动过程数据,生成交互式分析报告(HTML),用于分析Spring应用启动卡点,支持Spring Bean异步初始化,减少优化Spring应用启动时间。
小小工匠
2023/07/24
5920
OpenSource - Spring Startup Ananlyzer
羞,Spring Bean 初始化/销毁竟然有这么多姿势
日常开发过程有时需要在应用启动之后加载某些资源,或者在应用关闭之前释放资源。Spring 框架提供相关功能,围绕 Spring Bean 生命周期,可以在 Bean 创建过程初始化资源,以及销毁 Bean 过程释放资源。Spring 提供多种不同的方式初始化/销毁 Bean,如果同时使用这几种方式,Spring 如何处理这几者之间的顺序?
andyxh
2019/11/29
1.8K0
Spring组件初始化扩展点:BeanPostProcessor
在Spring框架中,BeanPostProcessor是一个强大的扩展接口,允许开发者在Bean初始化的过程中插入自定义逻辑。它是Spring IoC容器生命周期管理的核心机制之一,广泛应用于属性注入、AOP代理、监控等场景。理解BeanPostProcessor的工作机制,能够帮助开发者更灵活地定制Spring容器的行为。
Java微观世界
2025/03/18
1230
Spring Bean初始化过程
init-method方法,初始化bean的时候执行,可以针对某个具体的bean进行配置。init-method需要在applicationContext.xml配置文档中bean的定义里头写明。例如:
chengcheng222e
2021/11/04
3930
如何在Spring初始化Bean或销毁Bean前执行某些操作
0x01:通过在Bean中定义init-method 和 destory-method方法
BUG弄潮儿
2020/06/12
7130
面试了才知道初始化Bean不仅只有new那么简单
哈喽,大家好,我是Java小面。前面我们讲了面试时实例化Bean的几种方法《以后面试别再说你只会用@Autowired实例化Bean了》,那么对应不可或缺的还有初始化Bean,接下来我们会讨论它的下一步操作Bean的初始化。
灬沙师弟
2023/03/07
2120
面试了才知道初始化Bean不仅只有new那么简单
【Spring 学习系列】Bean 的生命周期之初始化与销毁
本文将结合一个简单案例,学习 Bean 生命周期中的初始化和销毁阶段的具体内容。
明明如月学长
2022/09/21
3210
【Spring 学习系列】Bean 的生命周期之初始化与销毁
Spring之Bean对象的初始化和销毁方法
  在Bean对象的完整的生命周期前我们还需要给大家介绍下Bean对象自身初始化及销毁的相关方法。
用户4919348
2019/04/02
1.2K0
初始化Spring Bean:Bean初始化有哪些方式?
对JDK比较敏感的朋友应该知道@PostConstruct这种标注方法。是从JDK1.6开始引入的
码农架构
2020/10/26
5.8K0
初始化Spring Bean:Bean初始化有哪些方式?
Spring 中 Bean 的生命周期
所谓 Bean 的生命周期,就是一个 Bean 从创建到销毁,所经历的各种方法调用。大致包含下面几个方法(不是全部)
水货程序员
2018/11/13
4.4K0
SpringBoot系列教程之Bean之指定初始化顺序的若干姿势
上一篇博文介绍了@Order注解的常见错误理解,它并不能指定 bean 的加载顺序,那么问题来了,如果我需要指定 bean 的加载顺序,那应该怎么办呢?
一灰灰blog
2019/11/03
1.5K0
spring源码分析:bean的初始化
我们通过spring容器帮我们实例化并且维护bean的时候,有时候我们需要在bean在实例化完成的时候,帮我们做一些事情,这个时候我们就会使用到bean的初始化方法。举个例子,比如我们创建一个电脑,那么我们肯定就需要先安装系统,不然不能使用,此时我们就能把安装系统的过程封装到初始化方法中。我们今天主要来看我们常见的三种初始化的方法,并且分析一下他们的源码。
全栈程序员站长
2022/07/04
6120
spring源码分析:bean的初始化
Spring Bean生命周期大师课:@PostConstruct与@PreDestroy的正确打开方式
摘要: 嘿,各位在Spring世界遨游的小伙伴们,我是默语!在日常开发中,我们天天和Spring Bean打交道,但你是否真正关注过Bean从“出生”到“消亡”的完整历程呢?@PostConstruct和@PreDestroy这两个注解,就像是Bean生命旅程中的“成人礼”和“告别仪式”,帮助我们在合适的时机执行关键的初始化和销毁逻辑。本文将从核心价值讲起,结合大量代码示例和场景分析,带你深入理解这两个注解的正确使用姿势、常见误区、工程实践要点乃至进阶优化策略,助你成为Bean生命周期管理的高手!
默 语
2025/05/12
2210
Spring源码解析bean对象生命周期管理
Spring Bean是Spring应用中最最重要的部分了。所以来看看Spring容器在初始化一个bean的时候会做那些事情,顺序是怎样的,在容器关闭的时候,又会做哪些事情。
JavaEdge
2021/02/22
4010
Spring源码解析bean对象生命周期管理
spring中bean生命周期的初始化和销毁的几种方法详解
最近在重新学习spring优秀的框架,顺便记录一下自己的总结,同时分享给大家。bean的生命周期指的是:bean创建–>初始化–>销毁 的过程,bean的生命周期由容器进行管理,我们可以自定义bean的初始化和销毁方法来满足我们的需求,当容器在bean进行到当前生命周期的时候,来调用自定义的初始化和销毁方法。今天主要讲解如何定义初始化和销毁的4中方法。
全栈程序员站长
2021/12/23
3360
spring中bean生命周期的初始化和销毁的几种方法详解
Spring Boot 启动后的初始化数据加载原理解析与实战应用
Hey小伙伴们,今天要给大家安利一篇技术成长的文章,相信大家通过仔细阅读,一定会有所收货!
不惑
2024/11/18
5290
Spring Boot 启动后的初始化数据加载原理解析与实战应用
推荐阅读
相关推荐
不知道吧?Spring Bean初始化/销毁竟然有这么多姿势
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档