10个Spring Boot性能最佳实践

在本文中,我们将首先讨论一般意义上的性能,然后讨论 10 个 Spring Boot 性能最佳实践,以使我们的 Spring Boot 快速且资源高效。

什么是性能?
在现代软件开发中,性能有不同的方面,这些方面在某种程度上与这两者(运行时和资源效率)相关:

  • 可扩展性:在更高的工作负载、用户数量或数据量下维持性能和功能的能力。
  • 可靠性:能够保持一致并无中断或错误地执行其预期功能。
  • 吞吐量:在特定时间内顺利管理数据和任务的能力。
通常,软件应用程序的性能就是确保其顺利、快速、有效地运行。它还涉及高效管理资源和满足用户的期望。

1-尽可能使用最新版本的 Spring Boot
在 Spring Framework 和 Spring Boot 的每个版本中,除了引入新特性和功能外,还通过优化 Spring 核心容器和模块、修复错误等方式提高了性能。因此,强烈建议您尽可能多地使用最新版本的 Spring Boot。


2- JVM 版本和调优
与 Spring Boot 类似,每个Java LTS 版本都有很多性能改进。有时,为了利用 Spring Boot 最新版本中的性能改进,我们需要使用最新版本的 Java。

虽然最新版本的 JVM 会提高你的 Spring Boot 应用程序的性能,但 JVM 调优可以进一步提高我们的 Spring Boot 应用程序的性能。JVM 调优超出了本文的讨论范围,但我可以提到一些我们需要注意的领域:

  • 初始和最大堆大小使用-Xms和-Xmx
  • 其他 JVM 内存设置
  • 选择合适的GC实现
最佳 JVM 和 GC 设置因应用程序和环境而异。测试各种配置和监控性能对于确定最佳设置至关重要。

3-在 JDK 21 上的 Web MVC 堆栈中使用虚拟线程
从 3.2 版开始,Spring Boot 开始以不同的方式支持虚拟线程(Project Loom)。

使用此配置,我们可以在 Spring Boot 中轻松启用虚拟线程:

spring.threads.virtual.enabled=true

通过这种配置,Spring Boot Web MVC 将处理网络请求,如控制器中的方法,该请求将在虚拟线程上运行。

4- Spring AOT 和 Spring GraalVM 原生镜像
GraalVM Native Images 提供了一种运行 Java 应用程序的新方法,与 JVM 相比,它减少了内存使用量并显著缩短了启动时间。

GraalVM Native Images 非常适合基于容器的部署和函数即服务平台,它需要提前处理才能通过静态代码分析创建可执行文件。结果是特定于平台的独立可执行文件,运行时不需要 JVM。

由于 GraalVM 不能直接感知 Java 代码中的动态元素,例如反射、资源、序列化和动态代理,因此我们需要为 GraalVM 提供有关这些元素的一些信息。另一方面,Spring Boot 应用程序是动态的,配置在运行时执行。

Spring AOT(Ahead-of-Time)处理是 Spring 框架提供的功能,可以对 Spring 应用程序进行改造,使其更加兼容 GraalVM。

Spring AOT 在构建时执行提前处理,并生成额外资产,如 Java 代码、字节码和 GraalVM JSON 提示文件,帮助 GraalVM 进行提前编译。

为此,我们需要在 Spring Boot 项目中使用 GraalVM 构建工具插件:

<plugin>
 <groupId>org.graalvm.buildtools</groupId>
 <artifactId>native-maven-plugin</artifactId>
</plugin>

然后,为了构建 GraalVM 本地镜像,我们可以在激活本地配置文件的情况下运行 spring-boot:build-image 目标:
mvn -Pnative spring-boot:build-image

它将使用 Buildpacks 为我们创建一个 docker 镜像。

要生成本机可执行文件而不是 Docker 镜,我们可以运行此命令(确保您的机器上安装了 GraalVM 发行版):
mvn -Pnative native:compile

5- JVM 检查点恢复功能(Project CRaC)
该功能从 3.2 版开始添加到 Spring Boot,由 JVM 提供,使运行中的 Java 应用程序能够保存其状态(称为 "检查点"),然后在稍后时间恢复该状态。Spring Boot 将此功能与其 ApplicationContext 生命周期集成,用于自动检查点,只需添加 -Dspring.context.checkpoint=onRefresh 参数即可使用。

该功能可通过以下方式提高 Spring Boot 应用程序的性能:

  • 更快的启动时间:为了提高性能,Spring Boot 应用程序可以通过保存预热的 JVM 状态和跳过耗时的重启初始化来进行优化。
  • 提高稳定性:通过从可靠的检查点恢复 JVM 状态并绕过初始化相关问题,增强 Spring Boot 应用程序的稳定性,从而获得更流畅、更可靠的体验。
  • 减少资源使用:通过重复使用检查点的现有 JVM 资源和减少总体资源消耗,优化 Spring Boot 应用程序的资源使用。

要使用此功能,我们需要安装支持 CRaC 的 JDK 版本,并使用 Spring Boot 3.2 或更高版本。

6- 类数据共享(CDS)
类数据共享(CDS)是 Java 虚拟机(JVM)的一项功能,有助于减少 Java 应用程序的启动时间和内存占用。从 3.3 版开始,它被集成到了 Spring Boot 中。

CDS 功能包括两个主要步骤:1-创建 CDS 归档文件,在应用程序退出时创建包含应用程序类的归档文件(.jsa 格式) 2-使用 CDS 归档文件,将 .jsa 文件加载到内存中。

CDS 可缩短 Spring Boot 应用程序的启动时间,因为访问共享归档文件比在 JVM 启动时加载类更快。这也是一种节省内存的解决方案,因为

同一主机上的共享存档有一部分被映射为只读,由多个 JVM 进程共享,共享存档还包含 Java Hotspot VM 所用形式的类数据。

7- 为 Spring MVC 和以数据库为中心的应用程序配置线程
Spring Boot 应用程序可以并行处理多个请求,而实现高吞吐量的关键因素是要有足够的线程来处理请求。控制器层和数据库访问层是可能导致瓶颈并需要仔细配置的两个重要层。

控制器层线程配置:
如果由于任何原因无法在 Spring MVC 应用程序中使用虚拟线程功能,那么为控制器层正确配置线程池就非常重要。

由于 Spring MVC 应用程序是在 Tomcat、Jetty 或 Undertow 等 servlet 容器上运行的,因此我们需要知道 servlet 容器的特定配置键,以配置线程池。例如,对于 Tomcat,我们有两个重要的线程配置键:

  • server.tomcat.threads.max:处理请求的最大工作线程数。
  • server.tomcat.threads.min-spare:保持存活的工作线程的最小数量,也等于启动时创建的线程数量。

server:
  tomcat:
    connection-timeout: 2s
    keep-alive-timeout: 10s
    threads:
      max: 500
      min-spare: 100


根据您使用的 servlet 容器,还有更多配置可以帮助我们配置它以获得更好的性能。例如,对于 Tomcat,有两个超时相关配置(server.tomcat.connection-timeout 和 server.tomcat.keep-alive-timeout)可能需要调整,以提高性能。


数据库访问层线程配置:
由于 JDBC 是与数据库通信的面向连接的标准,因此使用连接池非常重要。Spring Boot 默认使用 HikariCP 作为连接池。与 servlet 容器类似,每个连接池库都有自己的配置键来配置它,对于 HikariCP,连接池最重要的配置是

  • spring.datasource.hikari.maximum-pool-size:池中数据库连接的最大数量。
  • spring.datasource.hikari.minimum-idle:池中空闲数据库连接的最小数量。

spring:
  datasource:
    username: deli
    password: p@ssword
    url: jdbc:postgresql://localhost:5432/sample_db
    hikari:
      maximum-pool-size: 400
      minimum-idle: 100
      connection-timeout: 2000 
      

8- 使用缓存策略
为 Spring Boot 应用程序选择适当的缓存策略可以大大提高其性能。如今,我们有许多微服务,数据分布在它们之间。利用适当的缓存策略,我们可以提高性能并减少多次往返。

根据应用程序的规模、数据访问模式和性能要求,我们需要决定缓存的两个重要方面:

  • 1- 我们要缓存什么?我们想在缓存中保留应用程序中的哪些信息?
  • 2- 如何缓存?我们使用哪种缓存机制或方法?是使用本地缓存、分布式缓存,还是两者兼用?我们想在缓存中保留一段数据多长时间?

9- 采用弹性模式和最佳实践
我认为这是一个非常重要的话题,尤其是现在,因为基于微服务架构的应用程序越来越多。

遵循断路器、超时、回退和重试等弹性模式有助于 Spring Boot 应用程序更好地处理故障、有效分配资源并提供一致的性能,最终提高应用程序的整体性能。

Spring Boot 通过若干内置功能以及与 Resilience4j 等流行库的集成来支持弹性模式。Spring Boot 通过自动配置所需的 Bean,并提供一种通过属性或注解配置弹性模式的便捷方法,简化了 Resilience4j 的集成。

Spring Cloud Circuit Breaker 为断路器提供了一个抽象层,Resilience4j 可配置为在引擎盖下使用。

@Service
public static class DemoControllerService {
 private RestTemplate rest;
 private CircuitBreakerFactory cbFactory;

 public DemoControllerService(RestTemplate rest, CircuitBreakerFactory cbFactory) {
  this.rest = rest;
  this.cbFactory = cbFactory;
 }

 public String slow() {
  return cbFactory.create("slow").run(
      () -> rest.getForObject(
"/slow", String.class), throwable -> "fallback"
    );
 }

}

10- 监控和剖析
通过将监控和剖析相结合,您可以深入了解 Spring Boot 应用程序的性能,并根据数据做出改进决策。

Spring Boot 自带的许多库和工具可用于此目的。Spring Boot Actuator 可以监控应用程序的运行状况、收集指标并识别性能瓶颈。另一方面,从 Spring Boot 3.x 开始,使用 Spring 自动配置与 Micrometer 进行了很好的集成,这有助于我们获得更好的指标和分布式跟踪,最终实现更好的应用监控。