












请关注微信公众号:阿呆-bot
先来说说最根本的区别。WebMVC 和 WebFlux 在编程模型上完全是两个不同的世界:
| 特性 | WebMVC | WebFlux |
|---|---|---|
| 编程模型 | 命令式、阻塞式 | 响应式、非阻塞式 |
| I/O 模型 | 阻塞 I/O | 非阻塞 I/O |
| 线程模型 | 每个请求一个线程 | 事件循环 + 少量工作线程 |
| API 风格 | Servlet API | Reactive Streams API |
简单理解:
举个例子,假设你要调用下游服务获取用户信息:
WebMVC 方式(阻塞式):
// 线程在这里阻塞,等待响应,啥也干不了
User user = userService.getUser(userId); // 阻塞等待
return ServerResponse.ok().body(user);
WebFlux 方式(非阻塞式):
// 线程不会阻塞,可以继续处理其他请求
return userService.getUser(userId) // 返回 Mono<User>
.map(user -> ServerResponse.ok().body(user));
路由匹配是 Gateway 的核心功能,两种模式的实现方式完全不同。
WebMVC 方式:
使用同步的 RequestPredicate,匹配过程是阻塞的。比如匹配路径 /api/users/**:
// 同步匹配,立即返回结果
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/api/users/**", handler) // 同步匹配
.build();
// 实际匹配逻辑是这样的
RequestPredicate predicate = RequestPredicates.path("/api/users/**");
if (predicate.test(request)) { // 同步判断,立即返回 true/false
return Optional.of(handler);
}
WebFlux 方式:
使用异步的 AsyncPredicate,匹配过程是非阻塞的,返回 Mono<Boolean>:
// 异步匹配,返回 Mono
AsyncPredicate<ServerWebExchange> predicate =
exchange -> {
String path = exchange.getRequest().getPath().value();
// 可以在这里做异步操作,比如查询数据库
return checkPathAsync(path) // 返回 Mono<Boolean>
.map(matches -> path.startsWith("/api/users/"));
};
// 实际使用
return routeLocator.getRoutes()
.filterWhen(route -> route.getPredicate().test(exchange)) // 异步过滤
.next(); // 返回第一个匹配的路由
区别说明:
请求处理是 Gateway 最核心的部分,两种模式的处理方式差异很大。
WebMVC 方式(阻塞式处理):
处理请求时,线程会一直占用,直到处理完成:
// 这是一个同步的处理方法,线程会一直占用
public ServerResponse handle(ServerRequest request) {
// 1. 解析请求(线程占用)
String userId = request.pathVariable("id");
// 2. 调用下游服务(线程阻塞等待)
ResponseEntity<User> response = restClient.get()
.uri("http://user-service/users/" + userId)
.retrieve()
.toEntity(User.class); // 这里线程会阻塞,等待响应
// 3. 处理响应(线程占用)
User user = response.getBody();
// 4. 返回结果(线程占用)
return ServerResponse.ok().body(user);
}
实际执行流程:
线程1: 接收请求 → 处理请求 → [阻塞等待下游服务] → 处理响应 → 返回结果
↑___________________________|
这段时间线程被占用,不能处理其他请求
WebFlux 方式(非阻塞式处理):
处理请求时,线程不会阻塞,可以处理其他请求:
// 这是一个异步的处理方法,返回 Mono,线程不会阻塞
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 解析请求(线程占用,但很快)
String userId = exchange.getRequest().getPath().subPath(7);
// 2. 调用下游服务(非阻塞,线程可以处理其他请求)
return httpClient.get()
.uri("http://user-service/users/" + userId)
.retrieve()
.bodyToMono(User.class) // 返回 Mono,不阻塞线程
.flatMap(user -> {
// 3. 处理响应(在响应到达后执行)
exchange.getAttributes().put("user", user);
// 4. 继续过滤器链
return chain.filter(exchange);
});
}
实际执行流程:
线程1: 接收请求 → 发起异步调用 → [线程释放,可以处理其他请求]
↓
线程2: [处理其他请求] ↓
↓
线程1: [下游服务响应到达] → 处理响应 → 返回结果
关键区别:
WebMVC:
// 使用 RestClient(阻塞式)
RestClient restClient = RestClient.create();
ResponseEntity<String> response = restClient.get()
.uri("http://backend-service")
.retrieve()
.toEntity(String.class);
WebFlux:
// 使用 Reactor Netty HttpClient(非阻塞式)
HttpClient httpClient = HttpClient.create();
Mono<HttpClientResponse> response = httpClient.get()
.uri("http://backend-service")
.response();
这是两种模式最核心的性能差异。让我们用具体例子来说明:
| 指标 | WebMVC | WebFlux |
|---|---|---|
| 线程模型 | 每请求一线程 | 事件循环 + 工作线程池 |
| 最大并发 | 受线程池大小限制(通常几百到几千) | 可处理数万并发连接 |
| 资源消耗 | 每个线程占用 ~1MB 内存 | 少量线程,内存占用低 |
| 上下文切换 | 频繁的线程上下文切换 | 事件驱动,上下文切换少 |
举个例子:
假设你的服务器有 8 核 CPU,配置了 200 个线程的线程池(WebMVC 模式):
# WebMVC 配置
server:
tomcat:
threads:
max: 200 # 最多 200 个线程
WebMVC 场景:
WebFlux 场景:
实际测试数据(仅供参考):
场景:1000 并发请求,每个请求调用下游服务(延迟 50ms)
WebMVC(200 线程):
- 处理时间:约 250ms(需要多轮处理)
- 吞吐量:约 4000 QPS
- 内存占用:约 200MB
WebFlux(16 线程):
- 处理时间:约 50ms(几乎同时处理)
- 吞吐量:约 20000 QPS
- 内存占用:约 50MB
为什么 WebFlux 能处理更多并发?
想象一下:
吞吐量(QPS - Queries Per Second)是衡量 Gateway 性能的重要指标。两种模式的吞吐量差异很大。
WebMVC 模式:
吞吐量主要受限于线程池大小。举个例子:
# 典型配置
server:
tomcat:
threads:
max: 200 # 最大 200 个线程
min-spare: 10 # 最小 10 个线程
实际场景:
瓶颈在哪里?
// 瓶颈:线程在等待下游服务响应时被阻塞
ResponseEntity<String> response = restClient.get()
.uri("http://backend-service/api/data")
.retrieve()
.toEntity(String.class); // 线程在这里阻塞 50ms
// 这 50ms 内,线程不能处理其他请求
WebFlux 模式:
吞吐量主要受限于 CPU 和网络 I/O,而不是线程数。
实际场景:
为什么吞吐量高?
// 非阻塞:线程不会等待,可以处理其他请求
return httpClient.get()
.uri("http://backend-service/api/data")
.retrieve()
.bodyToMono(String.class) // 返回 Mono,不阻塞线程
.flatMap(data -> {
// 响应到达后才执行这里
return processData(data);
});
// 在等待响应的这段时间,线程可以处理其他请求
实际对比数据(8 核 CPU,16GB 内存):
| 场景 | WebMVC (200线程) | WebFlux (16线程) |
|---|---|---|
| 简单转发(无下游调用) | ~5000 QPS | ~30000 QPS |
| 调用下游服务(50ms延迟) | ~2000 QPS | ~15000 QPS |
| 调用下游服务(200ms延迟) | ~800 QPS | ~6000 QPS |
结论:
| 场景 | WebMVC | WebFlux |
|---|---|---|
| 低延迟请求 | 线程切换开销 | 事件驱动,延迟更低 |
| 高并发请求 | 线程等待,延迟增加 | 非阻塞,延迟稳定 |
| 长连接 | 线程占用时间长 | 事件驱动,资源占用少 |
✅ 推荐使用 WebMVC 的场景:
传统 Spring MVC 应用迁移
如果你的团队已经在用 Spring MVC,迁移到 Gateway 的 WebMVC 模式会非常顺滑。
实际例子:
// 你现有的 Spring MVC Controller
@RestController
public class UserController {
@GetMapping("/users/{id}")
public User getUser(@PathVariable String id) {
return userService.findById(id);
}
}
// Gateway WebMVC 的路由配置,语法几乎一样
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/api/users/{id}", handler) // 熟悉的语法
.build();
优势:
低到中等并发场景
如果你的业务量不大,WebMVC 完全够用。
实际例子:
什么时候不够用?
需要阻塞式操作
如果你的业务逻辑中必须使用阻塞式 API,WebMVC 更适合。
实际例子:
// 必须使用阻塞式 API 的场景
public ServerResponse handle(ServerRequest request) {
// 调用传统的 JDBC(阻塞式)
User user = jdbcTemplate.queryForObject(
"SELECT * FROM users WHERE id = ?",
userRowMapper,
userId
);
// 调用同步的文件操作(阻塞式)
String content = Files.readString(Paths.get("config.txt"));
// 调用第三方库(只支持阻塞式)
String result = legacyLibrary.process(user);
return ServerResponse.ok().body(result);
}
为什么不用 WebFlux?
简单应用、快速开发
如果项目比较简单,或者需要快速出原型,WebMVC 更合适。
实际例子:
# 简单的路由配置,几分钟就能搞定
spring:
cloud:
gateway:
server:
mvc:
routes:
- id: simple-route
uri: http://localhost:8081
predicates:
- path=/api/**
优势:
✅ 推荐使用 WebFlux 的场景:
高并发、高吞吐量场景
这是 WebFlux 的主战场。如果你的业务需要处理大量并发请求,WebFlux 是不二之选。
实际例子:
性能对比:
# 同样的硬件配置(8核16G)
场景:10000 并发请求
WebMVC:
- 需要约 500 个线程
- 内存占用:~500MB
- 处理时间:~2秒
WebFlux:
- 只需要 16 个线程
- 内存占用:~100MB
- 处理时间:~0.5秒
流式处理
WebFlux 天生支持流式处理,这是 WebMVC 很难做到的。
实际例子:
Server-Sent Events (SSE) - 实时推送数据:
// WebFlux 可以轻松实现 SSE
@GetMapping(value = "/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamEvents() {
return Flux.interval(Duration.ofSeconds(1))
.map(seq -> ServerSentEvent.<String>builder()
.id(String.valueOf(seq))
.event("message")
.data("Event " + seq)
.build());
}
WebSocket 长连接 - 实时通信:
// WebFlux 原生支持 WebSocket
@Bean
public RouterFunction<ServerResponse> websocketRoute() {
return RouterFunctions.route()
.GET("/ws", request -> {
// 处理 WebSocket 连接
return ServerResponse.ok().build();
})
.build();
}
流式数据传输 - 大文件上传/下载:
// 流式处理大文件,不会占用大量内存
public Mono<Void> uploadLargeFile(ServerWebExchange exchange) {
return exchange.getRequest().getBody()
.flatMap(dataBuffer -> {
// 流式处理,不会一次性加载到内存
return processDataBuffer(dataBuffer);
})
.then();
}
为什么 WebMVC 不适合?
微服务网关
作为微服务架构的统一入口,Gateway 需要处理大量微服务调用,WebFlux 非常适合。
实际例子:
# 典型的微服务架构
网关需要路由到:
- 用户服务(10000 QPS)
- 订单服务(8000 QPS)
- 商品服务(15000 QPS)
- 支付服务(5000 QPS)
总计:~38000 QPS
WebFlux 的优势:
实际代码示例:
// WebFlux 可以轻松聚合多个服务
public Mono<AggregatedResponse> aggregateServices(ServerWebExchange exchange) {
Mono<User> user = getUserService(exchange);
Mono<Order> order = getOrderService(exchange);
Mono<Product> product = getProductService(exchange);
// 并行调用,非阻塞
return Mono.zip(user, order, product)
.map(tuple -> {
// 聚合结果
return new AggregatedResponse(tuple.getT1(), tuple.getT2(), tuple.getT3());
});
}
资源受限环境
如果你的服务器资源有限,WebFlux 可以让你用更少的资源处理更多的请求。
实际例子:
资源对比:
场景:处理 10000 QPS
WebMVC:
- 需要:8核 CPU,16GB 内存,500 线程
- 成本:~$200/月
WebFlux:
- 需要:4核 CPU,8GB 内存,16 线程
- 成本:~$100/月
响应式生态系统
如果你的整个技术栈都是响应式的,使用 WebFlux 可以实现端到端的响应式处理。
实际例子:
// 前端:React + WebSocket(响应式)
// 网关:Spring Cloud Gateway WebFlux(响应式)
// 后端:Spring WebFlux(响应式)
// 数据库:R2DBC(响应式数据库驱动)
// 端到端的响应式处理
@GetMapping("/users/{id}")
public Mono<User> getUser(@PathVariable String id) {
return r2dbcRepository.findById(id) // 响应式数据库查询
.flatMap(user -> {
return enrichUserData(user); // 响应式数据增强
});
}
优势:
易于理解和调试
这是 WebMVC 最大的优势。代码写起来就像写普通的 Java 代码,逻辑清晰,容易理解。
实际例子:
// WebMVC 的代码,一看就懂
public ServerResponse handle(ServerRequest request) {
String userId = request.pathVariable("id");
User user = userService.getUser(userId); // 同步调用,逻辑清晰
if (user == null) {
return ServerResponse.notFound().build(); // 错误处理直观
}
return ServerResponse.ok().body(user);
}
调试体验:
对比 WebFlux:
// WebFlux 的代码,需要理解 Mono/Flux
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return Mono.defer(() -> {
String userId = exchange.getRequest().getPath().subPath(7);
return userService.getUser(userId) // 返回 Mono
.switchIfEmpty(Mono.error(new NotFoundException()))
.flatMap(user -> {
// 嵌套的 flatMap,理解起来需要时间
return chain.filter(exchange);
});
});
}
生态成熟
Spring MVC 已经存在很多年了,生态非常成熟,几乎什么功能都有现成的库。
实际例子:
第三方库支持:
// 几乎所有 Java 库都支持同步调用
// 数据库操作
User user = jdbcTemplate.queryForObject(...);
// HTTP 调用
ResponseEntity<String> response = restTemplate.getForEntity(...);
// 文件操作
String content = Files.readString(...);
// Redis 操作
String value = redisTemplate.opsForValue().get(...);
文档和示例:
兼容性好
如果你已经有 Spring MVC 应用,迁移到 Gateway WebMVC 模式几乎零成本。
实际例子:
// 你现有的 Spring MVC Controller
@RestController
public class ApiController {
@GetMapping("/api/users")
public List<User> getUsers() {
return userService.findAll();
}
}
// Gateway WebMVC 的路由配置,语法几乎一样
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/api/users", handler) // 熟悉的语法,迁移成本低
.build();
支持传统 Servlet 容器:
开发效率高
同步编程让开发变得简单直接,不需要考虑异步、背压等复杂概念。
实际例子:
// 开发一个简单的过滤器,几分钟就能搞定
@Component
public class SimpleFilter implements FilterFunction {
@Override
public ServerRequest filter(ServerRequest request) {
// 添加请求头,逻辑简单
return ServerRequest.from(request)
.header("X-Request-Id", UUID.randomUUID().toString())
.build();
}
}
错误处理直观:
// 错误处理就是普通的 try-catch
try {
User user = userService.getUser(userId);
return ServerResponse.ok().body(user);
} catch (UserNotFoundException e) {
return ServerResponse.notFound().build();
} catch (Exception e) {
return ServerResponse.status(500).build();
}
性能限制
这是 WebMVC 最大的短板。受限于线程池大小,高并发场景下性能不足。
实际例子:
# 典型配置
server:
tomcat:
threads:
max: 200 # 最多 200 个线程
性能瓶颈:
资源利用率低:
// 线程在等待下游服务响应时被阻塞,资源浪费
ResponseEntity<String> response = restClient.get()
.uri("http://backend-service/api/data")
.retrieve()
.toEntity(String.class);
// 假设下游服务响应需要 100ms
// 这 100ms 内,线程被占用,不能处理其他请求
// 200 个线程 × 100ms = 20000ms 的线程时间被浪费
阻塞式 I/O
线程在等待 I/O 操作(网络请求、数据库查询等)时会被阻塞,无法充分利用系统资源。
实际例子:
// 调用下游服务,线程阻塞等待
ResponseEntity<User> response = restClient.get()
.uri("http://user-service/users/123")
.retrieve()
.toEntity(User.class);
// 线程在这里阻塞,假设等待 50ms
// 这 50ms 内,线程不能做任何事情
资源浪费:
扩展性差
当需要处理更多请求时,扩展成本高。
垂直扩展(增加服务器配置):
水平扩展(增加服务器数量):
实际例子:
场景:需要处理 10000 QPS
WebMVC 方案:
- 需要:5 台服务器(每台 200 线程,2000 QPS)
- 成本:5 × $200/月 = $1000/月
WebFlux 方案:
- 需要:1 台服务器(16 线程,10000 QPS)
- 成本:1 × $200/月 = $200/月
高性能
这是 WebFlux 最大的优势。非阻塞 I/O 让它可以处理大量请求,吞吐量远超 WebMVC。
实际例子:
// WebFlux 的非阻塞处理
return httpClient.get()
.uri("http://backend-service/api/data")
.retrieve()
.bodyToMono(String.class) // 非阻塞,线程可以处理其他请求
.flatMap(data -> processData(data));
性能数据(8核16G服务器):
为什么性能高?
高并发
WebFlux 可以轻松处理数万并发连接,这是 WebMVC 做不到的。
实际例子:
# WebFlux 配置
# 只需要 16 个线程(CPU 核心数 × 2)
# 可以处理数万并发连接
并发能力对比:
实际场景:
资源高效
用更少的资源处理更多的请求,这是 WebFlux 的核心优势。
实际例子:
场景:处理 10000 QPS
WebMVC:
- 线程:500 个
- 内存:~500MB(每个线程 ~1MB)
- CPU:利用率低(线程被 I/O 阻塞)
WebFlux:
- 线程:16 个
- 内存:~100MB
- CPU:利用率高(事件驱动,充分利用 CPU)
成本对比:
功能丰富
WebFlux 支持很多 WebMVC 难以实现的功能。
HTTP/2 支持:
# WebFlux 原生支持 HTTP/2
server:
http2:
enabled: true
WebSocket 支持:
// WebFlux 原生支持 WebSocket,性能优秀
@Bean
public RouterFunction<ServerResponse> websocketRoute() {
return RouterFunctions.route()
.GET("/ws", request -> {
// 处理 WebSocket 连接
return ServerResponse.ok().build();
})
.build();
}
流式处理:
// 流式处理大文件,不会占用大量内存
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Data> streamData() {
return dataService.getDataStream() // 返回数据流
.delayElements(Duration.ofSeconds(1));
}
学习曲线陡峭
这是 WebFlux 最大的门槛。响应式编程和传统的命令式编程完全不同,需要时间学习。
实际例子:
// 传统编程(WebMVC),一看就懂
User user = userService.getUser(userId);
Order order = orderService.getOrder(orderId);
return new UserOrder(user, order);
// 响应式编程(WebFlux),需要理解 Mono/Flux
return userService.getUser(userId) // 返回 Mono<User>
.flatMap(user ->
orderService.getOrder(orderId) // 返回 Mono<Order>
.map(order -> new UserOrder(user, order)) // 组合结果
);
需要学习的概念:
学习时间:
生态相对较小
响应式编程的生态相比传统编程要小很多,很多库不支持响应式。
实际例子:
// 传统库(JDBC),不支持响应式
User user = jdbcTemplate.queryForObject(...); // 阻塞式
// 响应式库(R2DBC),生态较小
Mono<User> user = r2dbcTemplate.query(...) // 响应式,但库较少
.one();
生态对比:
文档和示例:
阻塞风险
如果在响应式链中执行阻塞操作,会严重影响性能,甚至导致系统崩溃。
错误示例:
// ❌ 错误:在响应式链中执行阻塞操作
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 阻塞操作会阻塞事件循环线程!
String result = blockingService.call(); // 阻塞 100ms
return chain.filter(exchange);
}
问题:
正确做法:
// ✅ 正确:使用专门的线程池执行阻塞操作
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return Mono.fromCallable(() -> blockingService.call())
.subscribeOn(Schedulers.boundedElastic()) // 使用专门的线程池
.flatMap(result -> chain.filter(exchange));
}
背压机制:
调试困难
异步调用栈让调试变得困难,错误追踪也不容易。
实际例子:
// 同步调用栈(WebMVC),调试容易
handleRequest()
-> getUser()
-> queryDatabase()
-> [错误在这里,调用栈清晰]
// 异步调用栈(WebFlux),调试困难
filter()
-> flatMap()
-> getUser()
-> [错误在这里,但调用栈复杂,难以追踪]
调试工具:
实际体验:
是否需要高并发/高吞吐量?
├─ 是 → 是否已有响应式经验?
│ ├─ 是 → 选择 WebFlux
│ └─ 否 → 评估学习成本,优先考虑 WebFlux
└─ 否 → 是否已有 Spring MVC 应用?
├─ 是 → 选择 WebMVC(迁移成本低)
└─ 否 → 根据团队技能选择
在实际项目中,可以考虑混合使用:
如果从 WebMVC 迁移到 WebFlux:
| 维度 | WebMVC | WebFlux | 推荐 |
|---|---|---|---|
| 学习成本 | 低 | 高 | WebMVC |
| 开发效率 | 高 | 中 | WebMVC |
| 性能 | 中 | 高 | WebFlux |
| 并发能力 | 低 | 高 | WebFlux |
| 资源效率 | 低 | 高 | WebFlux |
| 生态成熟度 | 高 | 中 | WebMVC |
| 调试难度 | 低 | 高 | WebMVC |
最终建议:
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。