让 SpringBoot 应用启动更快
“春天有多快?”
这是 Spring One Platform 2018 的一个会议。我看了视频,也亲自尝试了一下。所以在这里介绍一下我做了哪些工作以及结果。
如果你还没看过这个会议视频,我推荐你看一下。它非常有趣。
https://springoneplatform.io/2018/sessions/how-fast-is-spring-
今天的源代码
https://github.com/bufferings/spring-boot-startup-mybench
↓我使用了 OpenJDK 11。
❯ java --version
openjdk 11.0.1 2018-10-16
OpenJDK Runtime Environment 18.9 (build 11.0.1+13)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.1+13, mixed mode)
↓你可以像这样运行所有基准测试。由于要运行所有基准测试,所以需要一些时间。
❯ ./mvnw clean package
❯ (cd benchmarks/; java -jar target/benchmarks.jar)
1. FluxBaseline
↓我使用 SpringInitializr 创建了一个 Reactive Web 项目。然后,我编写了一个 WebMVC 风格的小型控制器。
@SpringBootApplication
@RestController
public class DemoApplication {
@GetMapping("/")
public String home() {
return "Hello";
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
↓Spring Boot 版本为 2.1.0.RELEASE。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
↓启动耗时 2.938 ± 0.287 s/op。
Benchmark Mode Cnt Score Error Units
MyBenchmark.case01_FluxBaseline ss 10 2.938 ± 0.287 s/op
现在我得到了检查启动时间的基准。让我们从这里开始吧。
2. WebMVC
↓我好奇 WebMVC 而不是 WebFlux 有什么区别?所以我尝试了一下。也许这只是 Tomcat 和 Netty 的比较?
Benchmark Mode Cnt Score Error Units
MyBenchmark.case01_FluxBaseline ss 10 2.938 ± 0.287 s/op
MyBenchmark.case02_Web ss 10 3.281 ± 0.342 s/op
WebFlux 更快一点,不是吗?
3. spring-context-indexer
接下来,我尝试了 spring-context-indexer,它似乎创建了组件索引。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<optional>true</optional>
</dependency>
↓嗯...慢一点?
Benchmark Mode Cnt Score Error Units
MyBenchmark.case01_FluxBaseline ss 10 2.938 ± 0.287 s/op
MyBenchmark.case03_WithContextIndexer ss 10 3.063 ± 0.102 s/op
↓我检查了一下spring.components
,发现它只包含一个组件。原来如此……我应该用一个更大的项目试试效果。
#
#Sun Nov 04 18:42:59 JST 2018
com.example.DemoApplication=org.springframework.stereotype.Component
4.延迟初始化
尝试了 Lazy Init。
@Configuration
public class LazyInitBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (String beanName : beanFactory.getBeanDefinitionNames()) {
beanFactory.getBeanDefinition(beanName).setLazyInit(true);
}
}
}
↓结果如下。速度稍微快了一点。
Benchmark Mode Cnt Score Error Units
MyBenchmark.case01_FluxBaseline ss 10 2.938 ± 0.287 s/op
MyBenchmark.case04_WithLazyInit ss 10 2.844 ± 0.129 s/op
5. NoVerify
运行方式-noverify
:
Benchmark Mode Cnt Score Error Units
MyBenchmark.case01_FluxBaseline ss 10 2.938 ± 0.287 s/op
MyBenchmark.case05_WithNoVerifyOption ss 10 2.582 ± 0.060 s/op
它变得有点快了。我不知道这意味着什么,所以我需要稍后再检查一下。
6. 分层停止
运行方式-XX:TieredStopAtLevel=1
:
Benchmark Mode Cnt Score Error Units
MyBenchmark.case01_FluxBaseline ss 10 2.938 ± 0.287 s/op
MyBenchmark.case06_WithTieredStopAtLevel1Option ss 10 1.980 ± 0.037 s/op
呃,快多了!不到两秒就搞定了。不过我也不知道这个标志。我以后再看看。
7.明确指定SpringConfigLocation
运行方式-Dspring.config.location=classpath:/application.properties
:
Benchmark Mode Cnt Score Error Units
MyBenchmark.case01_FluxBaseline ss 10 2.938 ± 0.287 s/op
MyBenchmark.case07_WithSpringConfigLocationOption ss 10 3.026 ± 0.139 s/op
嗯,变慢了。
8.关闭JMX
运行方式-Dspring.jmx.enabled=false
:
Benchmark Mode Cnt Score Error Units
MyBenchmark.case01_FluxBaseline ss 10 2.938 ± 0.287 s/op
MyBenchmark.case08_WithJmxDisabledOption ss 10 2.877 ± 0.097 s/op
它变得快了一点。
9. 排除 Logback
从这里开始,我尝试排除一些库。首先,排除 Logback:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
</dependency>
结果如下:
Benchmark Mode Cnt Score Error Units
MyBenchmark.case01_FluxBaseline ss 10 2.938 ± 0.287 s/op
MyBenchmark.case09_WithoutLogback ss 10 2.904 ± 0.096 s/op
嗯……稍微好一点?
10. 排除杰克逊
接下来是杰克逊
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-json</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
结果:
Benchmark Mode Cnt Score Error Units
MyBenchmark.case01_FluxBaseline ss 10 2.938 ± 0.287 s/op
MyBenchmark.case10_WithoutJackson ss 10 2.789 ± 0.093 s/op
它变得快了一点。
11. 排除 HibernateValidator
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<exclusions>
<exclusion>
<artifactId>hibernate-validator</artifactId>
<groupId>org.hibernate.validator</groupId>
</exclusion>
</exclusions>
</dependency>
结果如下:
Benchmark Mode Cnt Score Error Units
MyBenchmark.case01_FluxBaseline ss 10 2.938 ± 0.287 s/op
MyBenchmark.case11_WithoutHibernateValidator ss 10 2.857 ± 0.084 s/op
也略有改善。
图书馆排除的时代就此结束。
12. AppCDS
AppCDS(应用程序类数据共享)最初作为一项商业功能包含在 Oracle JDK 中。但它从 OpenJDK 10 开始可用。
AppCDS 似乎将信息转储到共享档案中,因此启动时间变短。
Benchmark Mode Cnt Score Error Units
MyBenchmark.case01_FluxBaseline ss 10 2.938 ± 0.287 s/op
MyBenchmark.case12_WithAppCds ss 10 2.957 ± 0.079 s/op
嗯... 没有更快... 然后我查看了有关CDS的文章,找到了原因。
使用 SpringBoot FatJAR,这些库超出了 CDS 的范围。
13. Flux 与 Thin Launcher
呃,抱歉,基准测试名称“Exploded”错了。我曾经尝试过解压 FatJAR,但解压后的 JAR 无法使用 CDS。所以我改用 Thin Launcher。请将基准测试名称“Exploded”改为“Thin Launcher”。
在使用CDS之前,我想检查一下用Thin Launcher打包的JAR文件的速度。
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot.experimental</groupId>
<artifactId>spring-boot-thin-layout</artifactId>
<version>1.0.15.RELEASE</version>
</dependency>
</dependencies>
</plugin>
</plugins>
虽然我使用了Thin Launcher来打包应用程序,但是我并没有使用Thin Launcher的启动类,而是指定了Main类,以使启动时间尽可能快。
Benchmark Mode Cnt Score Error Units
MyBenchmark.case01_FluxBaseline ss 10 2.938 ± 0.287 s/op
MyBenchmark.case13_Exploded ss 10 2.476 ± 0.091 s/op
嗯,是不是快了一点?
14. Thin Launcher + CDS
现在我想将 AppCDS 应用于它。
Benchmark Mode Cnt Score Error Units
MyBenchmark.case01_FluxBaseline ss 10 2.938 ± 0.287 s/op
MyBenchmark.case14_ExplodedWithAppCds ss 10 1.535 ± 0.036 s/op
哇!速度快多了!
15. 全部适用
最后,我应用了所有方法。
Benchmark Mode Cnt Score Error Units
MyBenchmark.case01_FluxBaseline ss 10 2.938 ± 0.287 s/op
MyBenchmark.case15_AllApplied ss 10 0.801 ± 0.037 s/op
不到1秒!(∩´∀`)∩yay
再一步
在 Dave 的讲座中,他提到了“函数式 Bean 定义”,并尝试使用 Spring 进行改进(无需 SpringBoot),应用程序速度明显加快。我需要学习更多才能理解这些改进。
结果列表
Benchmark Mode Cnt Score Error Units
MyBenchmark.case01_FluxBaseline ss 10 2.938 ± 0.287 s/op
MyBenchmark.case02_Web ss 10 3.281 ± 0.342 s/op
MyBenchmark.case03_WithContextIndexer ss 10 3.063 ± 0.102 s/op
MyBenchmark.case04_WithLazyInit ss 10 2.844 ± 0.129 s/op
MyBenchmark.case05_WithNoVerifyOption ss 10 2.582 ± 0.060 s/op
MyBenchmark.case06_WithTieredStopAtLevel1Option ss 10 1.980 ± 0.037 s/op
MyBenchmark.case07_WithSpringConfigLocationOption ss 10 3.026 ± 0.139 s/op
MyBenchmark.case08_WithJmxDisabledOption ss 10 2.877 ± 0.097 s/op
MyBenchmark.case09_WithoutLogback ss 10 2.904 ± 0.096 s/op
MyBenchmark.case10_WithoutJackson ss 10 2.789 ± 0.093 s/op
MyBenchmark.case11_WithoutHibernateValidator ss 10 2.857 ± 0.084 s/op
MyBenchmark.case12_WithAppCds ss 10 2.957 ± 0.079 s/op
MyBenchmark.case13_Exploded ss 10 2.476 ± 0.091 s/op
MyBenchmark.case14_ExplodedWithAppCds ss 10 1.535 ± 0.036 s/op
MyBenchmark.case15_AllApplied ss 10 0.801 ± 0.037 s/op
真的很有趣。谢谢!
鏂囩珷鏉ユ簮锛�https://dev.to/bufferings/lets-make-springboot-app-start-faster-k9m