Spring框架是广泛使用的Java开发框架之一,它提供了强大的功能和灵活性,但在大型应用中,由于Spring框架的复杂性和依赖关系,应用的启动时间和性能可能会受到影响。这可能导致开发过程中的迟缓和开发效率低下。优化Spring应用程序的启动速度和性能是一个重要的任务,通过分析和优化应用的初始化过程、减少不必要的依赖和组件加载、并利用异步初始化、懒加载等技术,可以显著改善应用的启动性能。这将帮助开发者提高开发效率、减少调试时间,并提供更好的用户体验。
线上的业务 jar 包基本上普遍比较庞大,动不动一个 jar 包几百 M,启动时间在10分钟级,拖慢了我们在故障时快速扩容的响应、以及本地开发调试效率。于是做了一些分析,看看 Spring 程序启动慢到底慢在哪里,如何去优化,目前的效果是大部分大型应用启动时间可以缩短 70%~80%。
◦ 开发效率提高:较快的应用启动速度可以显著减少开发和调试的时间。开发人员能够更快地启动应用程序,进行功能测试和调试,从而提高开发效率和迭代速度。
◦ 部署和扩展效率提升:优化启动速度可以减少部署和扩展应用程序的时间和成本。快速启动的应用程序可以更快地响应负载变化,提高系统的可伸缩性和弹性。
◦ 资源利用率优化:通过减少初始化时间和优化资源加载,可以降低应用程序的内存和CPU占用率。这有助于提高服务器的利用率,并降低运行应用程序的成本。
借助Spring startup analyzer的能力,我们以业务线的ARK项目为例,深入研究如何优化提效Spring项目的启动过程。下面我们先观察下ARK的基本启动情况:
这个观察项可以一直下探,直到Bean引用的最末级,可以看出每一级的加载时长
y 轴表示调用栈,每一层都是一个函数。调用栈越深,火焰就越高,顶部就是正在执行的函数,下方都是它的父函数。
x 轴表示抽样数,如果一个函数在 x 轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长。注意,x 轴不代表时间,而是所有的调用栈合并后,按字母顺序排列的。
火焰图就是看顶层的哪个函数占据的宽度最大。只要有"平顶"(plateaus),就表示该函数可能存在性能问题。
颜色没有特殊含义,因为火焰图表示的是 CPU 的繁忙程度,所以一般选择暖色调
从总览图中可以看出,有三个入口函数占用百分比较大,下面分别看一下
这部分火焰图可以看出,springfox在启动过程做了很多初始化,占了大量时间,对于不需要该功能的项目,可以直接下掉
了解下spring bean 的初始化过程
从这个图中可以看出,bean的创建过程也占了很多时间
从这个图中可以看出,注册BeanPostProcessor也耗费了大量时间
这一个观察项可以搜集到项目启动完之后,没有用到的Jar包
手动安装
点击realease下载最新版tar.gz包
新建文件夹,并解压
linux/mac系统可以考虑使用以下命令:
mkdir -p ${HOME}/spring-startup-analyzercd 下载路径
tar -zxvf spring-startup-analyzer.tar.gz -C 安装路径/spring-startup-analyzer
脚本安装(linux/mac)
curl -sS https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/main/bin/install.sh | sh
脚本默认安装路径:$HOME/spring-startup-analyzer
spring-startup-analyzer是以agent的方式启动的,所以在启动命令中添加参数-javaagent:安装路径/spring-startup-analyzer/lib/spring-profiler-agent.jar
即可。
java -javaagent:/Users/runner/spring-startup-analyzer/lib/spring-profiler-agent.jar \
-Dproject.name=mac-demo \
-Dspring-startup-analyzer.admin.http.server.port=8066 \
-jar /Users/runner/spring-startup-analyzer/ARK.jar
日志文件路径:安装路径/spring-startup-analyzer/logs
应用启动完成后会在console和startup.log文件中输出======= spring-startup-analyzer finished, click http://localhost:xxxx to visit details. ======
,可以通过此输出来判断采集是否完成。
预发平均启动10分钟,本地无法启动,每次需求需要提交到预发环境验证,开发和发版周期比较长,且预发环境连接的生产库,不能随便造数。项目引用585个jar,其中有337个jar没用到。
分析可以看到,耗时排名前面的接口都是jsf相关的加载,还有一个es相关的bean。
功能路径:Details of Method Invoke --> AbstractAutowireCapableBeanFactory.createBean
注:index=“注册中心地址”中的“注册中心地址“做了匿名,在具体场景查看自己代码中的配置
jsf的生产者的注册中心在启动的时候,会拉取一批ip,不断尝试注册jsf,在办公环境这些ip无法访问,导致启动过程一直重试
在本机host里面增加jsf发布地址的host配置,下面*...* 在使用的时候替换成自己的,可以 ping test.注册中心地址 获取。“注册中心地址” 替换成上面index后面的地址
*.*.*.* 注册中心地址
再次启动项目,时长来到185s
将ES的Bean初始化进行懒加载,以及开启全局懒加载,时长来到131s;
全局懒加载:
1、根据spring版本的不同,开启全局懒加载的方式可能会不相同
2、不建议生产环境开启全局懒加载,因为基本上我们的服务都是部署在k8s上的,有可能服务在伸缩的时候,在访问量大的时候,由于懒加载的配置,服务快速启动成功了,会返回给docker容器服务已经准备就绪状态,导致k8s把流量分给该服务,导致预想不到的问题。
对于应用未使用的jar包,可以谨慎剔除,在剔除的时候一个一个下,每下一个都要重复编译和启动验证是否会对项目造成影响,这是一个持续和长期的过程,Jar瘦身不仅对启动时长有收益,而且对编译提效很明显,减少了大量的Jar复制过程
做完上述优化之后:
https://www.oracle.com/java/technologies/downloads/#java8-mac
请联系作者提供免费账号
https://redis.io/docs/install/install-redis/install-redis-on-windows/
https://github.com/linyimin0812/spring-startup-analyzer/blob/main/README_ZH.md
作者:京东健康 梁灿
来源:京东云开发者社区 转载请注明来源