运维让我优化SpringBoot启动速度,我是这么干的!
Spring Boot毫无疑问是 Java 后端开发的第一大框架,基于Spring Boot有着一套完整的工具链,各种各样的starter。对于日常业务开发而言,可以说是轮子很全。
但随着微服务和云原生时代的流行,Spring Boot应用却暴露出了一些问题,其中比较突出的有:
启动慢应用内存占用多云原生应用对启动速度的要求比较高。当需要进行水平扩展时,要求这些新的实例必须在足够短的时间内完成启动,从而尽快的处理新增的请求。云原生应用要求在运行时占用尽可能少的资源。尽可能的减少单个实例占用的资源,就意味着可以用同样的成本,支持更多的访问请求。云原生应用要求更小的打包体积。云原生应用以容器镜像的形式打包。应用镜像的尺寸越大,所需要的存储空间也会越大,推送和拉取镜像所耗费的时间也会更长。其实我们都比较清楚大部分的启动时间是由于 Spring 需要加载各种 Bean 导致启动速度下降的
(资料图片仅供参考)
一、延迟初始化Bean一般在 SpringBoot 中都拥有很多的耗时任务,比如数据库建立连接、初始线程池的创建等等,我们可以延迟这些操作的初始化,来达到优化启动速度的目的。Spring Boot 2.2 版本后引入spring.main.lazy-initialization属性,配置为 true 会将所有 Bean 延迟初始化。
spring: main: lazy-initialization: true
个人本地开启延迟初始化之后,启动能快了1~2秒。
环境 | 配置 | (十次平均值)启动速度 |
springboot2+jdk1.8 | ≈10.3s | |
延迟初始化Bean | ≈8.63s |
Spring5 之后提供了spring-context-indexer功能,可以通过在编译时创建一个静态候选列表来提高大型应用程序的启动性能。
先看官方的解释:
在项目中使用了@Indexed之后,编译打包的时候会在项目中自动生成META-INT/spring.components文件。
当Spring应用上下文执行ComponentScan扫描时,META-INT/spring.components将会被CandidateComponentsIndexLoader 读取并加载,转换为CandidateComponentsIndex对象,这样的话@ComponentScan不在扫描指定的package,而是读取CandidateComponentsIndex对象,从而达到提升性能的目的.
我们只需要将依赖引入,然后在启动类上使用@Indexed注解即可。这样在程序编译打包之后会生成META-INT/spring.components文件,当执行@ComponentScan扫描类时,会读取索引文件,提高扫描速度。
org.springframework spring-context-indexer true
@Indexed@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}
环境 | 配置 | (十次平均值)启动速度 |
springboot2+jdk1.8 | ≈10.3s | |
+延迟初始化Bean | ≈8.63s | |
+创建扫描索引 | ≈7.7s |
其他技巧:
1、减少@ComponentScan @SpringBootApplication扫描类时候的范围
2、关闭 Spring Boot 的 JMX监控,设置spring.jmx.enabled=false
3、设置JVM参数 -noverify ,不对类进行验证
4、对非必要启动时加载的Bean,延迟加载5、使用Spring Boot的全局懒加载一
6、AOPQ切面尽量不使用注解方式,这会导致启动时扫描全部方法7、关闭endpoint的一些监控功能
8、排除项目多余的依赖jar
9、swagger扫描接口时,指定只扫描某个路径下的类10、Feign 客户端接口的扫描缩小包扫描范围
到这启动速度应该算是优化的比较极致了, 但是内存占用大依然是问题
三、 升级jdk17当然jdk也在这方面做了很大的努力:
内存占用多主要是内存占用后不会归还操作系统,这个正在逐步改善:
G1 JDK12及之后 已支持ZGC JDK13及之后 已支持由于Java语言的特性及Spring Boot的一些实现方式,决定了即便是开启了G1/ZGC的未使用内存及时归还操作系统,Spring Boot的内存占用,仍然远大于Golang这种编译型语言。
所以,Java想要解决云原生时代的问题,目前的方案基本都是基于GraalVM来的,不管是Quarkus(夸克)还是Micronaut都是。
那么,Spring Boot有没有类似的方案呢?:spring-graalvm-native
四、升级SpringBoot3spring-graalvm-native是springBoo6/SpringBoot3非常重大的一个特性,支持使用 GraalVM 将 SpringBoot 的应用程序编译成本地可执行的镜像文件,可以显著提升启动速度、峰值性能以及减少内存使用。