天天看点

springboot 启动速度调优

作者:江沪白

目前我们的微服务启动速度都谈不上快. 在添加了预热之后启动速度会更加的感人. 这个原因通过研究以及行业相关的解决方案.

注: 这里所说的方法,都是以类加载速度瓶颈优化展开的. 这个是已经在使用工具.如 async-profiler此类的工具对启动速度进行了定位分析后的优化方向.

对于SpringBoot 本身

目前可能解决办法有:

  • 升级 springBoot到2.x , 2.x以后 spring-boot-loader的class 加载器,对于类的索引与查找有优化.升级2.x需要先升级到最新版本的 1.5.x. 然后再升级到2.x 这个略麻烦.参考:https://artemisconsultinginc.com/blog/migrating-spring-boot-from-1-x-to-2-x/Spring Boot 2.0 Migration Guidehttps://docs.spring.io/spring-boot/docs/2.0.x/reference/html/appendix-dependency-versions.htmlhttps://docs.spring.io/spring-boot/docs/1.5.x/reference/html/appendix-dependency-versions.html
  • springboot 中classpath.idx 是什么版本引入的.效果怎么样
  • 在 Spring Boot 中,classpath.idx 是一个索引文件,用于优化类路径的扫描和加载。该文件包含了类路径中所有 JAR 文件中的类和资源的索引信息,以及它们在 JAR 文件中的位置。Spring Boot 应用程序在启动时会使用该索引文件来快速地定位和加载所需的类和资源,从而加快应用程序的启动速度。
  • classpath.idx 文件是从 Spring Boot 2.2 版本开始引入的,它是基于 Spring Framework 5.2 中的新特性而开发的。在早期版本的 Spring Boot 中,类路径扫描是通过递归遍历类路径中的每个 JAR 文件来实现的,这种方式效率较低,特别是当应用程序的类路径包含大量 JAR 文件时会更加明显。因此,引入 classpath.idx 索引文件可以显著提高 Spring Boot 应用程序的启动速度。
  • 总的来说,如果你的 Spring Boot 应用程序的类路径包含大量的 JAR 文件,并且启动速度比较慢,那么使用 classpath.idx 索引文件可以有效地提高应用程序的启动速度。
  • —— 来自chatGPT
  • 使用Java的原生技术JarIndex技术. 为SpringBoot FatJar提供一个JarIndex文件. 再重写部分的SpringBoot ClassLoader相关逻辑. 以使其能够在加载类的时候用到自建的JarIndex, 从而起到加速搜索的目的.参考: 一些可以显著提高大型 Java 项目启动速度的尝试 - 知乎 (链接2 - 今日头条)
  • 使用原生的Class加载方法, 不使用SpringBootFatJar的 class 加载方式.这个是由于FatJar的加载方式决定的. 可以认为 Fatjar方式在文件读取上可能会有一些性能损耗. 同时在文件查找效率上, 也可能比原生的低一些. 这个也是为什么在2.x的 spring-boot-loader项目中对此部分进行了相应的索引加速优化.另外也有一些 springBoot官方的issue对此问题进行了讨论:https://github.com/spring-projects/spring-boot/issues/11207

    org.springframework.boot.loader.jar.Handler creates unnecessary garbage during URL normalisation #1120Investigate startup performance between main and jar #7493Startup performance differences between Spring Boot versions and main class vs jar file formats [SPR-14956] #19523https://jira.spring.io/browse/SPR-14956?redirect=false

以上三种方法,各有各的优点. 如果是最正统的做法,肯定是直接升级到2.x. 但是这个有成本不好实施. 如果使用jarindex这个实际就是2.x的官方已经有的实现. 因此也比较折腾最后可能没有官方的好. 现在最省成本的办法就是直接使用main启动了. 这个可以参考 spring-boot-loader的launcher的代码.

参考源码: https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java

大概可以了解到其原理, 就是在spring启动的时候先启动的不是main class .而是启动的 launcher类. 而launcher类会对使用改造过的 URLClassLoader 来完成对类的加载.

这个改造过的ClassLoader会对 SpringBootFatJar进行预处理. 对里面已经存在的嵌套Jar包进行读取与索引处理. 以便后后面在真正进行ClassLoad的时候,能够查找到正确的类进行加载.

具体可以参考:

  • 三分钟带你了解SpringBoot真正的启动引导类

使用MainClass直接启动的原理与可行性:

并不是一开始我们就找到了官方的文档说明. 这个是从我们的实际项目在Idea中调试时发现的时间差异. 最后对比发现是类的加载方式的差异造成的启动速度的差异.

这里有一个官方镜像的相关说明: https://docs.spring.io/spring-boot/docs/current/reference/html/container-images.html

springboot 启动速度调优

我们对比了某solar-xxx 服务在 macbookpro 2020 上的运行时间差异.

  • 使用java -jar xxx.jar 运行的速度大概是 120秒的启动时间.
  • 把xxx.jar 解压后直接使用 laucher类启动. 其启动时间几乎没有变化.
  • 直接启动 MainClass , 此种启动方法的启动时间大概能提升到 60秒左右. 几乎是一倍时间的差异.

继续阅读