目录
[🎯 我踩过的坑](#🎯 我踩过的坑)
[✨ 摘要](#✨ 摘要)
[1. 别急着用WebFlux,先搞清楚这两个问题](#1. 别急着用WebFlux,先搞清楚这两个问题)
[1.1 什么时候该用WebFlux?](#1.1 什么时候该用WebFlux?)
[1.2 性能神话 vs 现实](#1.2 性能神话 vs 现实)
[2. 线程模型:一个线程 vs 少量线程](#2. 线程模型:一个线程 vs 少量线程)
[2.1 Servlet的"一个请求一个线程"](#2.1 Servlet的"一个请求一个线程")
[2.2 WebFlux的"事件循环"](#2.2 WebFlux的"事件循环")
[3. 请求处理流程对比](#3. 请求处理流程对比)
[3.1 Spring MVC的请求处理链](#3.1 Spring MVC的请求处理链)
[3.2 WebFlux的请求处理链](#3.2 WebFlux的请求处理链)
[4. 内存管理:堆内 vs 堆外](#4. 内存管理:堆内 vs 堆外)
[4.1 Servlet的内存模型](#4.1 Servlet的内存模型)
[4.2 WebFlux的零拷贝与堆外内存](#4.2 WebFlux的零拷贝与堆外内存)
[5. 连接处理:短连接 vs 长连接](#5. 连接处理:短连接 vs 长连接)
[5.1 Servlet的短连接瓶颈](#5.1 Servlet的短连接瓶颈)
[5.2 WebFlux的长连接优势](#5.2 WebFlux的长连接优势)
[6. 错误处理:同步 vs 异步](#6. 错误处理:同步 vs 异步)
[6.1 Servlet的错误处理](#6.1 Servlet的错误处理)
[6.2 WebFlux的错误处理](#6.2 WebFlux的错误处理)
[7. 实战:构建API网关](#7. 实战:构建API网关)
[7.1 需求分析](#7.1 需求分析)
[7.2 Spring Cloud Gateway实现](#7.2 Spring Cloud Gateway实现)
[7.3 性能压测结果](#7.3 性能压测结果)
[8. 常见问题与解决方案](#8. 常见问题与解决方案)
[8.1 阻塞代码问题](#8.1 阻塞代码问题)
[8.2 内存泄漏排查](#8.2 内存泄漏排查)
[8.3 调试技巧](#8.3 调试技巧)
[9. 性能优化指南](#9. 性能优化指南)
[9.1 线程池优化](#9.1 线程池优化)
[9.2 内存优化](#9.2 内存优化)
[9.3 背压处理](#9.3 背压处理)
[10. 迁移指南:从Servlet到WebFlux](#10. 迁移指南:从Servlet到WebFlux)
[10.1 渐进式迁移策略](#10.1 渐进式迁移策略)
[10.2 兼容性处理](#10.2 兼容性处理)
[11. 最后的话](#11. 最后的话)
[📚 推荐阅读](#📚 推荐阅读)
🎯 我踩过的坑
去年我们团队要做一个实时消息推送系统,要求支持10万并发长连接。开始用的Spring MVC + Tomcat,测试时1000连接就崩了,Tomcat线程池直接打满。
后来换成WebFlux + Netty,同样的机器配置,10万连接稳稳的。但高兴得太早,上线后发现CPU使用率比原来高了30%。排查发现是有个同事在响应式链里写了阻塞代码。
还有个更坑的:我们有个批量查询接口,用WebFlux重写后,响应时间反而变长了。查了三天,发现是因为没处理好背压,数据量大的时候内存暴涨。
这些经历让我明白:不懂原理的技术选型就是耍流氓,WebFlux不是银弹,用错了比不用还惨。
✨ 摘要
Spring WebFlux是响应式Web框架,基于Reactor和Netty实现。本文深度对比Servlet阻塞模型与响应式非阻塞模型的差异,从线程模型、IO处理、内存管理三个维度解析性能关键。通过源码分析揭示WebFlux的请求处理流程、编解码机制、错误处理策略。结合压测数据和实战案例,提供WebFlux的正确使用姿势和性能优化指南。
1. 别急着用WebFlux,先搞清楚这两个问题
1.1 什么时候该用WebFlux?
很多人盲目追新技术,我总结了几个判断标准:
适合WebFlux的场景:
-
✅ 高并发IO密集型应用(API网关、消息推送)
-
✅ 实时数据流处理(股票行情、实时监控)
-
✅ 需要大量长连接的场景(WebSocket、SSE)
-
✅ 微服务间的非阻塞调用
不适合WebFlux的场景:
-
❌ CPU密集型计算(大数据分析、复杂算法)
-
❌ 已有大量阻塞代码的遗留系统
-
❌ 团队对响应式编程不熟悉
-
❌ 简单CRUD应用,并发不高
1.2 性能神话 vs 现实
先看个真实的性能对比,这是我们在4核8G机器上的测试结果:
| 场景 | Spring MVC (Tomcat) | WebFlux (Netty) | 提升 |
|---|---|---|---|
| 100并发,简单查询 | 3200 QPS | 3500 QPS | 9.4% |
| 1000并发,简单查询 | 2800 QPS | 5200 QPS | 85.7% |
| 100并发,IO密集 | 1200 QPS | 3800 QPS | 216.7% |
| 内存占用(1000连接) | 450MB | 320MB | 28.9% |
看到没?只有在高并发和IO密集型场景下,WebFlux才有明显优势。
2. 线程模型:一个线程 vs 少量线程
2.1 Servlet的"一个请求一个线程"
这是Servlet最经典的模型,也是最大的瓶颈:
java
// Tomcat的线程池处理请求
public class Http11Processor implements Runnable {
public void run() {
try {
// 1. 解析请求
parseRequest();
// 2. 调用Servlet(线程在这里阻塞!)
servlet.service(request, response);
// 3. 返回响应
writeResponse();
} catch (Exception e) {
handleError(e);
}
}
}
// Spring MVC的Controller
@RestController
public class UserController {
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
// 线程在这里阻塞,等待数据库查询
return userRepository.findById(id).orElse(null);
}
}
代码清单1:Servlet线程模型
用图表示更清楚:

图1:Servlet阻塞线程模型
问题:线程在等待IO时被阻塞,什么也干不了,浪费资源。
2.2 WebFlux的"事件循环"
WebFlux用了完全不同的思路:
java
// Netty的事件循环
public class NioEventLoop extends SingleThreadEventLoop {
protected void run() {
for (;;) {
try {
// 1. 选择就绪的IO事件
int readyChannels = selector.select();
if (readyChannels > 0) {
// 2. 处理IO事件
processSelectedKeys();
}
// 3. 处理任务队列
runAllTasks();
} catch (Throwable t) {
handleLoopException(t);
}
}
}
}
// WebFlux的Controller
@RestController
public class UserController {
@GetMapping("/user/{id}")
public Mono<User> getUser(@PathVariable Long id) {
// 立即返回Publisher,不阻塞
return userRepository.findById(id)
.subscribeOn(Schedulers.boundedElastic());
}
}
代码清单2:WebFlux事件循环模型
响应式模型的工作方式:

图2:WebFlux事件驱动模型
关键点:事件循环线程只做IO调度,耗时操作交给工作线程池,处理完回调。
3. 请求处理流程对比
3.1 Spring MVC的请求处理链
看Spring MVC是怎么处理请求的:
java
// DispatcherServlet的核心流程
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request,
HttpServletResponse response) throws Exception {
// 1. 获取Handler
HandlerExecutionChain mappedHandler = getHandler(request);
// 2. 获取HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3. 执行拦截器前置处理
if (!mappedHandler.applyPreHandle(request, response)) {
return;
}
// 4. 实际处理(这里线程被阻塞!)
ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler());
// 5. 渲染视图
mappedHandler.applyPostHandle(request, response, mv);
processDispatchResult(request, response, mappedHandler, mv, null);
}
}
代码清单3:Spring MVC请求处理
处理流程的时间线:

图3:Spring MVC线程阻塞时间线
看到没?线程大部分时间在等待,啥也干不了。
3.2 WebFlux的请求处理链
WebFlux的处理方式完全不同:
java
// DispatcherHandler的核心流程
public class DispatcherHandler implements WebHandler {
public Mono<Void> handle(ServerWebExchange exchange) {
return Flux.fromIterable(this.handlerMappings)
.concatMap(mapping -> mapping.getHandler(exchange))
.next()
.switchIfEmpty(createNotFoundError())
.flatMap(handler -> invokeHandler(exchange, handler))
.flatMap(result -> handleResult(exchange, result));
}
private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange,
Object handler) {
// 找到合适的HandlerAdapter
HandlerAdapter adapter = getHandlerAdapter(handler);
// 执行业务逻辑(返回Mono,不阻塞)
return adapter.handle(exchange, handler);
}
}
代码清单4:WebFlux请求处理
用序列图表示更清楚:

图4:WebFlux非阻塞处理序列图
关键优势:事件循环线程永不阻塞,一个线程能处理成千上万的连接。
4. 内存管理:堆内 vs 堆外
4.1 Servlet的内存模型
Servlet使用的是传统的堆内存:
java
// 典型的Servlet内存使用
public class UserController {
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) {
// 整个文件被读到堆内存!
byte[] bytes = file.getBytes(); // 内存峰值
// 处理文件
processFile(bytes);
return "success";
}
}
内存分配情况:
-
每个请求独立的内存空间
-
大文件上传时内存暴涨
-
GC压力大,容易Full GC
4.2 WebFlux的零拷贝与堆外内存
WebFlux利用Netty的零拷贝和堆外内存:
java
@RestController
public class FileController {
@PostMapping(value = "/upload",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<String> upload(FilePart file) {
// 使用数据流,不一次性加载到内存
return file.content()
.map(dataBuffer -> {
// 直接操作数据缓冲区
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return bytes;
})
.collectList()
.flatMap(chunks -> {
// 合并处理
return processChunks(chunks);
});
}
// 文件下载:零拷贝
@GetMapping(value = "/download/{id}",
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public Mono<Resource> download(@PathVariable String id) {
return Mono.fromCallable(() ->
new FileSystemResource("/data/files/" + id));
}
}
代码清单5:WebFlux文件处理
内存对比图:

图5:内存模型对比
性能测试(上传100MB文件,100并发):
| 指标 | Spring MVC | WebFlux | 对比 |
|---|---|---|---|
| 内存峰值 | 2.5GB | 800MB | 减少68% |
| GC时间 | 4.2s | 0.8s | 减少81% |
| 平均耗时 | 3.4s | 1.8s | 减少47% |
5. 连接处理:短连接 vs 长连接
5.1 Servlet的短连接瓶颈
Tomcat默认使用短连接,每个请求创建新连接:
java
// Tomcat连接处理
public class Http11Processor {
public void action(ActionCode actionCode, Object param) {
switch (actionCode) {
case CLOSE:
// 关闭连接
endRequest();
break;
// ...
}
}
private void endRequest() {
// 释放连接资源
inputBuffer.recycle();
outputBuffer.recycle();
// 如果是短连接,关闭Socket
if (!isKeepAlive()) {
closeSocket();
}
}
}
问题:
-
高并发时大量连接创建/销毁开销
-
端口资源有限
-
握手开销大(SSL握手更甚)
5.2 WebFlux的长连接优势
Netty天生支持长连接:
java
// WebFlux的HTTP/2支持
@Configuration
public class Http2Config {
@Bean
public NettyServerCustomizer nettyServerCustomizer() {
return httpServer -> httpServer
.protocol(HttpProtocol.H2, HttpProtocol.HTTP11)
.compress(true)
.http2Settings(settings ->
settings.maxConcurrentStreams(1000L));
}
}
// WebSocket支持
@RestController
public class ChatController {
@MessageMapping("chat")
public Flux<Message> chat(Flux<Message> inbound) {
return inbound
.doOnNext(message ->
log.info("收到消息: {}", message))
.doOnComplete(() ->
log.info("连接关闭"));
}
}
连接数对比测试(10万并发连接):

图6:连接处理能力对比
现实案例:我们做的消息推送系统,用Tomcat最多支撑5000长连接,用Netty轻松10万连接。
6. 错误处理:同步 vs 异步
6.1 Servlet的错误处理
Servlet的错误处理是同步的:
java
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception e) {
// 同步处理错误
return ResponseEntity.status(500)
.body("错误: " + e.getMessage());
}
}
// 问题:如果异常处理器本身抛异常?
@ExceptionHandler(NullPointerException.class)
public ResponseEntity<String> handleNPE(NullPointerException e) {
String msg = e.getMessage(); // 可能NPE!
return ResponseEntity.status(500).body(msg);
}
6.2 WebFlux的错误处理
WebFlux的错误处理是响应式的:
java
@ControllerAdvice
public class GlobalErrorWebExceptionHandler implements ErrorWebExceptionHandler {
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
return Mono.fromRunnable(() -> {
// 可以在这里记录日志
log.error("处理请求异常", ex);
})
.then(Mono.defer(() -> {
// 构建错误响应
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
byte[] bytes = ("错误: " + ex.getMessage()).getBytes(UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(buffer));
}))
.onErrorResume(e -> {
// 错误处理器本身出错时的fallback
log.error("错误处理器异常", e);
return Mono.empty();
});
}
}
代码清单6:WebFlux错误处理
错误处理链对比:

图7:错误处理机制对比
7. 实战:构建API网关
7.1 需求分析
我们需要一个高性能API网关,要求:
-
支持5万QPS
-
动态路由
-
熔断降级
-
限流鉴权
-
实时监控
7.2 Spring Cloud Gateway实现
java
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user_service", r -> r
.path("/api/users/**")
.filters(f -> f
.addRequestHeader("X-Request-ID", UUID.randomUUID().toString())
.circuitBreaker(config -> config
.setName("userService")
.setFallbackUri("forward:/fallback/user"))
.requestRateLimiter(config -> config
.setRateLimiter(redisRateLimiter()))
.retry(config -> config
.setRetries(3)
.setStatuses(HttpStatus.SERVICE_UNAVAILABLE)))
.uri("lb://user-service"))
.build();
}
@Bean
public RedisRateLimiter redisRateLimiter() {
return new RedisRateLimiter(100, 200, 1);
}
}
// 全局过滤器
@Component
public class AuthFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
String token = exchange.getRequest()
.getHeaders().getFirst("Authorization");
if (token == null) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return validateToken(token)
.flatMap(valid -> {
if (valid) {
return chain.filter(exchange);
} else {
exchange.getResponse()
.setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
});
}
private Mono<Boolean> validateToken(String token) {
return webClient.post()
.uri("http://auth-service/validate")
.bodyValue(new TokenRequest(token))
.retrieve()
.bodyToMono(TokenResponse.class)
.map(TokenResponse::isValid)
.timeout(Duration.ofSeconds(3))
.onErrorReturn(false);
}
}
代码清单7:Spring Cloud Gateway配置
7.3 性能压测结果
测试环境:
-
4核8GB * 3节点
-
后端服务:Spring Boot 2.7
-
压测工具:wrk
测试结果:
| 场景 | Spring Cloud Zuul | Spring Cloud Gateway | 提升 |
|---|---|---|---|
| 100并发,简单路由 | 4200 QPS | 18500 QPS | 340% |
| 1000并发,复杂过滤 | 1200 QPS | 8500 QPS | 608% |
| 长连接支持 | 差 | 优秀 | - |
| 内存占用 | 650MB | 320MB | 51% |
8. 常见问题与解决方案
8.1 阻塞代码问题
这是WebFlux最常见的问题:
java
// 错误:在响应式链中阻塞
@GetMapping("/blocking")
public Mono<String> blocking() {
return Mono.fromCallable(() -> {
Thread.sleep(1000); // 阻塞!
return "done";
});
}
// 正确:使用调度器
@GetMapping("/non-blocking")
public Mono<String> nonBlocking() {
return Mono.fromCallable(() -> {
Thread.sleep(1000);
return "done";
})
.subscribeOn(Schedulers.boundedElastic());
}
// 使用BlockHound检测阻塞调用
@PostConstruct
public void init() {
BlockHound.install();
}
8.2 内存泄漏排查
响应式编程容易内存泄漏:
java
// 监控内存使用
@RestController
public class MetricsController {
@GetMapping("/metrics/memory")
public Mono<Map<String, Object>> memoryMetrics() {
return Mono.fromCallable(() -> {
Runtime runtime = Runtime.getRuntime();
Map<String, Object> metrics = new HashMap<>();
metrics.put("totalMemory", runtime.totalMemory());
metrics.put("freeMemory", runtime.freeMemory());
metrics.put("maxMemory", runtime.maxMemory());
metrics.put("directMemory", PlatformDependent.usedDirectMemory());
return metrics;
});
}
}
// 使用JVM参数开启详细GC日志
// -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log
8.3 调试技巧
java
// 1. 开启操作符堆栈跟踪
Hooks.onOperatorDebug();
// 2. 添加检查点
flux.checkpoint("source")
.map(...)
.checkpoint("afterMap")
.subscribe();
// 3. 使用Micrometer监控
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config()
.commonTags("application", "webflux-demo");
}
// 4. 记录慢请求
@Bean
public WebFilter slowRequestFilter() {
return (exchange, chain) -> {
long start = System.currentTimeMillis();
return chain.filter(exchange)
.doFinally(signal -> {
long cost = System.currentTimeMillis() - start;
if (cost > 1000) {
log.warn("慢请求: {} {}, 耗时: {}ms",
exchange.getRequest().getMethod(),
exchange.getRequest().getPath(),
cost);
}
});
};
}
9. 性能优化指南
9.1 线程池优化
# application.yml
spring:
webflux:
client:
max-connections: 1000
max-memory-size: 10MB
server:
netty:
connection-timeout: 30s
idle-timeout: 60s
reactor:
schedulers:
default-pool-size: 4
bounded-elastic:
max-threads: 200
queue-size: 10000
ttl: 60
9.2 内存优化
java
// 使用直接内存池
@Bean
public NettyServerCustomizer nettyServerCustomizer() {
return server -> server
.metrics(true, () ->
new MicrometerChannelMetricsRecorder("server", "http"))
.accessLog(true)
.runOn(LoopResources.create("webflux", 4, true))
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.childOption(ChannelOption.ALLOCATOR,
PooledByteBufAllocator.DEFAULT);
}
// 监控直接内存
@Scheduled(fixedRate = 5000)
public void monitorDirectMemory() {
long used = PlatformDependent.usedDirectMemory();
long max = PlatformDependent.maxDirectMemory();
if (used > max * 0.8) {
log.warn("直接内存使用过高: {}/{}", used, max);
}
}
9.3 背压处理
java
@GetMapping("/stream")
public Flux<Data> streamData() {
return dataService.getDataStream()
.onBackpressureBuffer(1000,
BufferOverflowStrategy.DROP_OLDEST)
.doOnNext(data ->
metrics.recordDataProcessed())
.delayElements(Duration.ofMillis(10)) // 控制速率
.timeout(Duration.ofSeconds(30))
.doOnError(TimeoutException.class, e ->
log.error("数据流超时"));
}
10. 迁移指南:从Servlet到WebFlux
10.1 渐进式迁移策略
不要一次性重写整个系统:

图8:渐进式迁移策略
10.2 兼容性处理
java
// 1. 混合使用
@Configuration
public class HybridConfig {
// WebFlux路由
@Bean
public RouterFunction<ServerResponse> webFluxRoutes() {
return RouterFunctions.route()
.GET("/reactive/users", this::getUsers)
.build();
}
// Spring MVC Controller
@RestController
@RequestMapping("/mvc")
public class MvcController {
@GetMapping("/users")
public List<User> getUsers() {
return userService.getUsers();
}
}
}
// 2. 响应式调用阻塞服务
@Service
public class HybridService {
public Mono<List<User>> getUsersReactive() {
return Mono.fromCallable(() ->
// 调用现有的阻塞服务
blockingUserService.getUsers()
)
.subscribeOn(Schedulers.boundedElastic())
.flatMapMany(Flux::fromIterable)
.collectList();
}
}
11. 最后的话
WebFlux不是Spring MVC的替代品,它们是解决不同问题的工具。就像螺丝刀和锤子,用对了事半功倍,用错了事倍功半。
我见过太多团队在技术选型上犯错:有的在简单CRUD系统上用WebFlux,复杂度增加但收益不大;有的在高并发场景下死守Servlet,性能上不去还怪硬件不行。
记住:技术是为业务服务的,选型要结合业务场景、团队能力、运维成本综合考虑。
📚 推荐阅读
官方文档
-
**Spring WebFlux官方文档** - 最权威的参考
-
**Project Reactor文档** - 响应式编程基础
源码学习
-
**Spring WebFlux源码** - 直接看源码
-
**Netty源码** - 网络层实现
最佳实践
-
**Spring Cloud Gateway指南** - API网关实战
-
**响应式系统设计** - 响应式宣言
性能工具
-
**Micrometer监控** - 响应式应用监控
-
**BlockHound** - 阻塞调用检测
最后建议 :不要因为WebFlux"时髦"就用它。先分析你的业务场景,做个小规模POC测试,用数据说话。记住:先验证,后落地;先小范围,后推广。