随着 Spring 7.x 和 Spring Boot 4.x 的发布,Spring Cloud 也迎来了其新版本 Spring Cloud 2025.1。这一版本的发布不仅带来了常规的依赖升级和对新 JDK 版本(如 JDK 25)的支持,还迎来了对虚拟线程的全面拥抱。可以说,Spring Cloud 2025.1 是一场关于架构选择和技术取舍的革命,特别是在 Spring Cloud Gateway 组件上的“返璞归真”,为我们带来了前所未有的惊喜。
从“加法”到“减法”:Spring Cloud Gateway的拆分
Spring Cloud Gateway,一直以来作为响应式编程的代表,基于 WebFlux 和 Netty 实现,强制要求开发者使用 Mono/Flux 等响应式编程模式来获得高性能。在过去,WebFlux + Netty 被认为是实现高并发的唯一选择,但随着虚拟线程的引入,开发者的选择空间迎来了转折点。
在 Spring Cloud 2025.1 中,Spring Cloud Gateway 不再是单一的响应式网关,而是被拆分成了两种技术栈,提供了 WebFlux 和 WebMVC 两种选择。这样一来,开发者可以根据具体的需求选择适合的架构模式,甚至可以在相同的微服务架构中同时使用两种技术栈。
旧时代(已废弃):
dependencies:
spring-cloud-starter-gateway # 强制使用 WebFlux 和响应式编程
新时代:Spring Cloud Gateway 5.0.0 拆分后
在新的版本中,Spring Cloud Gateway 被拆分为两个独立的构件,分别适应不同的使用场景。开发者必须明确选择技术栈来启用:
方案 A:继续使用 Reactive(老项目继续苟)
spring:
threads:
virtual:
enabled: false # 禁用虚拟线程,继续使用响应式编程
dependencies:
spring-cloud-starter-gateway-server-webflux # WebFlux + Netty(保持响应式架构)
方案 B:拥抱虚拟线程(新项目必须上)
spring:
threads:
virtual:
enabled: true # 启用虚拟线程
dependencies:
spring-cloud-starter-gateway-server-webmvc # WebMVC + 虚拟线程(提高性能)
为什么要拆分?虚拟线程时代的到来
要理解 Spring Cloud Gateway 的拆分决策,必须理解 Java 21 引入的虚拟线程(Virtual Threads)如何彻底改变了传统架构设计。过去,开发者在面对高并发时,面临的主要选择是两种模式:
阻塞模型(传统模式):每个请求一个线程,线程资源昂贵且容易耗尽。
非阻塞模型(响应式编程):通过
Callback或Mono/Flux等方式实现非阻塞操作,虽然能节省线程,但编程复杂度较高。
虚拟线程的出现改变了这一局面。Java 21 中,虚拟线程与操作系统的内核线程解耦,阻塞操作不再消耗宝贵的内核线程资源。虚拟线程能够在处理阻塞操作时,极大地减少线程上下文切换的开销,提供类似于 非阻塞 的高并发性能。
WebFlux VS WebMVC:虚拟线程的优势
WebFlux + Netty 模式(基于响应式编程)在处理高并发时,性能非常优秀,但却有较高的编程复杂度,且调试与监控较为困难。相比之下,传统的 WebMVC + 虚拟线程 模式在性能上几乎不逊色,但代码复杂度大大降低,开发者可以回归到传统的同步编程模型。
为了展示两种技术栈的区别,我们来实现一个 GoodsController。这个控制器将分别使用 WebMVC + 虚拟线程 和 WebFlux 两种方式来实现。
示例 1:WebMVC + 虚拟线程(传统阻塞模型)
@RestController
@RequestMapping("/goods")
public class GoodsController {
private final GoodsService goodsService;
public GoodsController(GoodsService goodsService) {
this.goodsService = goodsService;
}
@GetMapping("/{id}")
public Goods getGoods(@PathVariable Long id) {
// 这是一个阻塞调用,但虚拟线程允许我们在高并发下不消耗内核线程
Goods goods = goodsService.findById(id); // 阻塞调用
return goods;
}
@GetMapping
public List<Goods> getAllGoods() {
// 批量获取商品,仍然是阻塞调用,虚拟线程将确保高并发下线程不会被耗尽
return goodsService.findAll();
}
}
示例 2:WebFlux + Netty(响应式编程)
@RestController
@RequestMapping("/goods")
public class GoodsController {
private final GoodsService goodsService;
public GoodsController(GoodsService goodsService) {
this.goodsService = goodsService;
}
@GetMapping("/{id}")
public Mono<Goods> getGoods(@PathVariable Long id) {
// 使用响应式编程模式,返回 Mono 对象,表示异步操作
return goodsService.findById(id);
}
@GetMapping
public Flux<Goods> getAllGoods() {
// 使用响应式编程返回 Flux 对象,表示多个商品的异步操作
return goodsService.findAll();
}
}
性能对比:WebFlux + Netty vs WebMVC + 虚拟线程
基于官方基准测试,以下是两者的性能对比:
WebFlux + Netty:30000 RPS,95% 延迟 45ms
WebMVC + 虚拟线程:28500 RPS,95% 延迟 48ms
可以看到,WebMVC + 虚拟线程 的性能与 WebFlux + Netty 差距不足 5%,而且在开发效率和代码简洁性上,后者的优势更加明显。
虚拟线程对其他 Spring Cloud 组件的影响
虚拟线程的普及不仅影响了 Spring Cloud Gateway,还直接影响了 Spring Cloud Stream 的架构。例如,曾经作为高性能方案的 Reactive Kafka Binder 被移除。过去,Reactive Kafka 因为支持非阻塞背压机制,曾被认为是“终极方案”,但其实现复杂,错误处理和调试困难。现在,虚拟线程使得阻塞模式重新焕发活力,开发者可以在高并发的环境中轻松使用传统的阻塞模式,而无需依赖复杂的响应式编程。
启用虚拟线程后的最佳实践
当使用启用了虚拟线程的 WebMVC 版本的 Spring Cloud Gateway 时,可以通过以下配置来支持百万级并发:
spring:
threads:
virtual:
enabled: true # 启用
executor:
max-thread-count: 1000000 # 线程数量配置
tomcat:
threads:
max: 1000 # Servlet 线程池仅负责调度,设置较小值即可
总结:虚拟线程时代的回归
Spring Cloud 2025.1 的发布标志着一个重要的转折点:响应式编程不再是获取高性能的唯一方式,传统的 WebMVC 配合 虚拟线程 也能达到接近的性能,并且简化了开发过程。这不仅是性能上的技术选择,更是开发哲学的回归:简单胜过复杂,实用胜过炫技,生态兼容胜过极致性能。
随着虚拟线程的推广,Spring Cloud 的微服务架构将迎来新的发展机遇,而开发者也将能够享受到更加高效、简洁的开发体验。