Spring Boot 3.2:虚拟线程和CRAC

 Spring Framework 6.1.0和Spring Boot 3.2.0已经相继发布,亮点是:从高性能应用程序的角度出发,推出了对两项非常重要的创新的支持:虚拟线程(Virtual Threads)和 CRAC 项目。

虚拟线程
Spring 支持虚拟线程是有条件的,取决于两个因素:

  • 使用 JDK 21 运行应用程序和
  • 激活 spring.threads.virtual.enabled 配置选项。

目前,Spring Boot 中的 Tomcat 和 Jetty 服务器都使用虚拟线程处理查询。这意味着应用代码(如管理网络请求的控制器方法)将在虚拟线程上运行,从而可能提高应用性能。

将虚拟线程纳入 Spring 还带来了一些额外的变化:

Spring 创建了 VirtualThreadTaskExecutor,当激活虚拟线程时,SimpleAsyncTaskExecutor 和 SimpleAsyncTaskScheduler 都会默认使用虚拟线程。

这一变更有很多副作用,包括:

  • @EnableAsync 方法的行为变更、
  • Spring MVC 中的异步请求处理以及
  • Spring WebFlux 中对阻塞执行的支持。

它还会影响单个集成的性能,如 RabbitMQ 或 Kafka、Spring Data Redis 和 Apache Pulsar的监听器。

如果能有更细粒度的选项来决定我们真正想要使用虚拟线程的地方--这将简化大型项目的迁移。也许在未来的版本中会有。

到目前为止,Spring 本身还没有详细分析虚拟线程对Web应用程序的影响。不过,我建议大家看看 Spring 的竞争对手 Quarkus 的优秀系列文章,他们已经对这个问题进行了相当深入的研究。其影响和最佳实践应该不相上下。

CRAC 项目
在介绍第二个重要主题,即 CRaC 项目之前,有必要先解释一下被称为冷启动问题的问题。

您可能已经知道,Java 应用程序在开始以高性能方式处理流量之前通常需要一定的预热时间。这是传统服务器应用程序和无服务器应用程序的共同特点,传统服务器应用程序需要及时编译(JiT)过程和动态剖析才能达到最佳性能,而无服务器应用程序只需要(在某些情况下)从头开始设置整个环境来处理单个请求。

幸运的是,有多种解决方案可以缓解这种 "冷启动 "问题--例如,GraalVM 使我们能够生成 Java 应用程序的 "本地 "版本。此外,还可以利用快速启动或 AppCDS 等技术来缓存单独的 JVM 运行时片段,从而避免在启动时从头开始初始化它们。但是,如果我们能持续重用预热、预编译的应用程序代码呢?或者更好的办法是,重用已加载数据(如 ML 模型)的 Java 进程的整个内存?为了实现这一目标,我们应该考虑一种称为 CRIU 的机制。

用户空间检查点/还原(CRIU)是 Linux 的一项功能,可将运行中的应用进程 "转储 "到磁盘。下一个实例可以从捕获上一个快照的位置启动,从而缩短启动时间。

AppCDS 这样的技术与传统的保存方式类似:我们选择能够让我们稍后恢复应用程序状态的部分,然后简单地保存它们。而保存状态则不涉及这些微妙之处--随着计算机的发展,我们拥有了更多的存储空间,我们只需将整个内存状态转存到磁盘上(以老式游戏机为例,存储空间可能达到惊人的 1Mb),然后在需要时将其原样重放即可。

CRIU 是在 JVM 之外运行的通用操作系统功能,因此在安全性和便利性方面都面临着挑战。每个应用程序都有自己的特点,而且根据应用程序服务器、网络应用程序或批处理作业的不同,写入磁盘的时间也各不相同。

春天选择了一种有竞争力的解决方案--是时候转用 CRaC 了。

CRaC 项目(应用程序延续中的检查点/恢复)是一种工具解决方案,它利用了 Java 应用程序中的检查点/恢复机制。这使得运行中的 JVM 状态(检查点)得以保留并在稍后恢复,从而使应用程序能够跳过初始加载和预热阶段,更迅速地运行。与 "裸 "CRIU 相比,它采用了适当的挂接和改进措施,以方便 JVM 使用整个系统。

要将 Spring Framework 与 Project CRaC 以及 Virtual Threads 整合使用,需要满足某些先决条件:

  • 其中包括启用了检查点/恢复功能的 JVM,目前只有 Azul 或 Liberica 支持 Linux。
  • 不过,只有后者的 21 版本允许并发使用虚拟线程。
  • 此外,classpath 中必须附加 org.crac:crac 库(1.4.0 或更高版本),还需要指定特定的命令行参数,如 -XX:CRaCCheckpointTo=PATH。
  • 检查点过程生成的文件封装了 JVM 内存的完整状态,其中可能包括敏感数据。这就需要对安全影响进行全面评估。
  • 值得注意的是,该进程还会影响 java.util.Random 的随机性,使其随机性 "略微 "降低,因为所有进程都是从同一种子启动的。

CRaC 与 Spring 的集成相当全面,因为它能无缝集成到应用程序的自然生命周期中。

  • 可使用 jcmd application.jar JDK.checkpoint 命令触发检查点。该命令可有效停止 Spring 运行的所有 Bean,允许它们关闭资源。
  • 一旦恢复,这些相同的 Bean 将重新启动,并根据需要重新打开资源。

除了手动触发外,该集成的一个重要功能是能够设置在应用程序启动时自动启动检查点/恢复。
  • 通过设置 Java 系统属性-Dspring.context.checkpoint=onRefresh,
  • 启动时会在 LifecycleProcessor.onRefresh 阶段自动创建检查点。

实际上,这意味着所有(非lazy的)单子都已实例化,InitializingBean.afterPropertiesSet 回调已执行,但 Lifecycle.startmethods 尚未运行,ContextRefreshedEvent 尚未发出。

Spring 框架 6.1
Spring Framework 6.1 与 JDK 21 广泛兼容。新版本增强了 "应用容器 "本身,尤其是生命周期管理功能。它现在包含了暂停和恢复任务的功能,并改进了 ThreadPoolTaskExecutor 和 ThreadPoolTaskScheduler 中对任务关闭的控制。

最新版本还为 @Scheduled 方法提供了更好的可观察性,并改进了创建验证器的 "工厂"。
它还为 Spring MVC 和 WebFlux 中的控制器方法参数提供了内置方法验证支持。这消除了对控制器类级别 @Validated 注解的要求。说到注解,@PropertySource 现在扩展了对 SpEL(Spring 表达式语言)表达式中符号的支持。

此外还引入了 RestClient,这是一种新的同步 HTTP 客户端,与 WebClient 类似,但已预先配置并专为 REST 查询量身定制。

数据库访问方面的变化也很有趣--该版本的一个有趣之处是引入了 JdbcClient,它是 JDBC 的统一界面。此外,还增加了对 R2DBC 的支持。在数据层的变更列表中,还包括了对应用程序生命周期中事务活动的改进。

Spring Boot 3.2.0
除了 Spring Framework 6.1 的新功能外,Spring Boot 3.2.0 还引入了几个新的集成和功能。

值得注意的是:

  • Spring Boot 3.2.0 扩展了对 Apache Pulsar 的支持。
  • Spring Framework 6.1 中的 RestClient 接口为开发人员提供了功能性阻塞 HTTP API。
  • 此外还支持 JdbcClient。
  • 一个重要的新增功能是在使用 Micrometer 时自动记录 Correlation Id,这可能会让许多开发人员免于在崩溃时因缺乏足够信息而陷入不愉快的境地。
  • 还为 Micrometer 注释提供了自动配置支持:Timed、@Counted、@NewSpan、@ContinueSpan 和 @Observed。

3.2.0 版增强了按照云原生计算基金会的云原生构建包(Cloud-Native Buildpacks)标准构建 Docker 映像的功能。默认情况下,构建 Docker 映像的任务现在使用主机的配置。

有趣的支持项目
1、Apache Pulsar 的 Spring
Apache Pulsar 采用存储空间与计算能力分离的架构,与 Kafka 竞争。这提供了独立扩展这些组件的能力,实现了不同程度的资源隔离。这与 Kafka 的统一架构形成鲜明对比,后者的计算能力和存储空间紧密相连,因此必须同时扩展资源。

Spring for Apache Pulsar 1.0.0 是一款帮助创建基于 Apache Pulsar 应用程序的工具。它主要依赖于 spring-pulsar-spring-boot-starter 模块,该模块可简化应用程序的配置和开发。该工具可自动配置 PulsarClient 等关键组件,消息发送方和接收方都可使用这些组件。它还能通过 PulsarTemplate 方便地生成和消费消息,并通过 @PulsarListener 注释简单地订阅和响应消息。此外,该工具还支持 TLS 和各种身份验证方法。

简单提一下Spring Integration 6.2 :和往常一样,它包含了很多内容,但没有什么能明显改变任何人的生活。不过,如果你使用 Kafka、MongoDB 或 Debezium,它还是值得一看的。

2、Spring Security 6.2 and Spring Session 3.2.0
Spring Security 6.2 中的重大修改包括:当存在 CorsConfigurationSource Bean 时自动添加 .cors();轻松创建各种配置:全新的 AbstractConfiguredSecurityBuilder.with(...) 方法;简化 OAuth2 客户端组件配置。此外,还引入了对 OIDC Back-channel 注销的支持,并增强了 SecurityContext 传播、可调整的重定向策略(RedirectStrategy)和 HTTP Basic 请求解析。

Spring Session 3.2.0 与安全一直紧密联系在一起,它的实体主要有两大增强功能。首先是引入了 SessionIdGenerator,允许创建自定义会话 ID。后者则是增强了安全反序列化 Redis 会话的能力。

Spring 授权服务器Spring Authorization Server 1.2 也已发布。不过,该版本中的大部分更新都只是补丁注释,因此意义不大。