Skip to content

WebFlux 响应式框架与性能优势

Tomcat 处理 10000 并发请求,需要多少线程?

10000 个?还是 5000 个?

如果你回答的是这些数字,说明你还没理解 WebFlux 的价值。

在传统 servlet 模型下,每个请求占用一个线程,10000 并发确实需要接近 10000 个线程。但 WebFlux 用事件循环 + 响应式编程,可以用几十个线程处理上百万并发。

这不是魔法,是架构的革新。

WebFlux 是什么

WebFlux 是 Spring 5 引入的响应式 Web 框架,基于 Reactor 和 Netty,提供非阻塞式 Web 服务。

┌─────────────────────────────────────────────────────┐
│                    WebFlux                          │
├─────────────────────────────────────────────────────┤
│  Handler (响应式 Controller)                        │
│    ↓ 返回 Flux/Mono                                 │
│  HandlerFilterFunction (函数式过滤器)               │
│    ↓                                               │
│  WebHandler                                        │
│    ↓                                               │
│  DispatcherHandler                                 │
├─────────────────────────────────────────────────────┤
│  Reactor Core (响应式编程库)                       │
├─────────────────────────────────────────────────────┤
│  Netty (事件循环,非阻塞 IO)                        │
└─────────────────────────────────────────────────────┘

关键区别

特性Spring MVC (Servlet)WebFlux
线程模型每请求一线程事件循环 + 少量线程
阻塞类型同步阻塞非阻塞
返回类型Object / ResponseEntityMono<T> / Flux<T>
数据流Pull 模式Push + Pull (背压)
适用场景CPU 密集型、简单业务IO 密集型、高并发

第一个 WebFlux 应用

依赖配置

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

函数式编程风格(推荐)

java
@Configuration
public class RouterConfig {
    
    @Bean
    public RouterFunction<ServerResponse> userRoutes(UserHandler handler) {
        return route(GET("/users/{id}"), handler::getUser)
            .andRoute(GET("/users"), handler::listUsers)
            .andRoute(POST("/users"), handler::createUser);
    }
}

@Component
public class UserHandler {
    
    private final UserRepository userRepository;
    
    public UserHandler(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    // 返回 Mono<ServerResponse>,非阻塞
    public Mono<ServerResponse> getUser(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        
        return userRepository.findById(id)
            .flatMap(user -> ServerResponse.ok().bodyValue(user))
            .switchIfEmpty(ServerResponse.notFound().build());
    }
    
    public Mono<ServerResponse> listUsers(ServerRequest request) {
        return userRepository.findAll()
            .collectList()
            .flatMap(users -> ServerResponse.ok().bodyValue(users));
    }
    
    public Mono<ServerResponse> createUser(ServerRequest request) {
        return request.bodyToMono(User.class)
            .flatMap(userRepository::save)
            .flatMap(saved -> ServerResponse.status(HttpStatus.CREATED).bodyValue(saved));
    }
}

注解式编程风格(与 MVC 类似)

java
@RestController
@RequestMapping("/users")
public class UserController {
    
    private final UserRepository userRepository;
    
    public UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @GetMapping("/{id}")
    public Mono<User> getUser(@PathVariable Long id) {
        return userRepository.findById(id);
    }
    
    @GetMapping
    public Flux<User> listUsers() {
        return userRepository.findAll();
    }
    
    @PostMapping
    public Mono<User> createUser(@RequestBody User user) {
        return userRepository.save(user);
    }
}

WebFlux 性能优势详解

1. 事件循环:少线程高吞吐

Tomcat 线程模型(阻塞):

请求1  [========线程1========]
请求2  [========线程2========]
请求3  [========线程3========]
...
请求N  [========线程N========]

线程数量 = 并发数(受限)

Netty 事件循环(事件驱动):

线程池(少量线程):
  线程1: [处理事件1] [处理事件3] [处理事件5] ...
  线程2: [处理事件2] [处理事件4] [处理事件6] ...

线程数量 = CPU 核心数(通常 4~16)

代码示例

java
// Spring MVC:每个请求一个线程
@GetMapping("/slow")
public String slowEndpoint() {
    // 假设这里有 1 秒的 IO 等待
    // 这 1 秒内,线程被阻塞,什么都做不了
    return restTemplate.getForObject("http://slow-service/api", String.class);
}

// WebFlux:事件驱动,不阻塞线程
@GetMapping("/slow")
public Mono<String> slowEndpoint() {
    // IO 操作交给 Netty 事件循环处理
    // 这个方法立即返回 Mono,不阻塞任何线程
    return webClient.get().uri("http://slow-service/api").retrieve().bodyToMono(String.class);
}

2. 背压支持:自动流量控制

当后端处理速度跟不上前端请求速度时,WebFlux 自动进行背压控制:

java
// 如果数据库查询太慢,Flux 会自动控制消费速度
userRepository.findAll()           // 可能很慢
    .take(1000)                     // 取前 1000 个
    .map(this::transform)           // 转换
    .subscribe(result -> {
        // 即使后端很慢,前端也不会崩溃
        // 因为中间有背压机制
    });

3. 内存占用低

线程类型栈大小10000 并发所需内存
阻塞线程~1MB~10GB
事件循环线程无栈(协程)~100MB
java
// Tomcat 内存消耗
// 10000 并发 = 10000 线程 × 1MB = ~10GB 栈内存

// WebFlux 内存消耗
// 少量事件循环线程 + 数据流对象
// 10000 并发 ≈ 几十 MB

4. 连接复用:HTTP/2 多路复用

WebFlux + Netty 原生支持 HTTP/2:

yaml
server:
  http2: true
java
// WebClient:自动复用连接
WebClient webClient = WebClient.builder()
    .baseUrl("http://api.example.com")
    .build();

// 1000 个请求复用少量连接
Flux.range(1, 1000)
    .flatMap(i -> webClient.get().uri("/user/{id}", i).retrieve().bodyToMono(User.class))
    .subscribe();

WebFlux 调优参数

Netty 线程配置

yaml
spring:
  reactor:
    Netty:
      ioWorkerCount: 16  # IO 线程数,默认 CPU 核心数 * 2
  netty:
    connection-timeout: 10000

连接池配置

java
@Bean
public HttpClient httpClient() {
    return HttpClient.create()
        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
        .responseTimeout(Duration.ofSeconds(2))
        .doOnConnected(conn -> conn
            .addHandlerLast(new ReadTimeoutHandler(10))
            .addHandlerLast(new WriteTimeoutHandler(10)))
        .pool(
            PoolConfig.builder()
                .maxConnections(500)           // 最大连接数
                .maxIdleTime(Duration.ofSeconds(20))  // 空闲超时
                .maxLifeTime(Duration.ofMinutes(5))   // 连接生命周期
                .build()
        );
}

背压配置

java
// 配置全局背压参数
@Bean
public ReactorCoreServerProperties reactorCoreProperties() {
    ReactorCoreServerProperties props = new ReactorCoreServerProperties();
    props.getHttpServer().getRequest().maxInMemorySize(1 * 1024 * 1024);
    return props;
}

响应式数据访问

WebFlux 需要配合响应式数据源使用。

R2DBC(推荐)

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
java
@Repository
public interface UserRepository extends R2dbcRepository<User, Long> {
    Flux<User> findByStatus(String status);
    Mono<User> findByUsername(String username);
}

@Service
public class UserService {
    private final UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    public Flux<User> getActiveUsers() {
        return userRepository.findByStatus("ACTIVE");
    }
}

Redis 响应式客户端

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
java
@Service
public class CacheService {
    
    private final ReactiveStringRedisTemplate redisTemplate;
    
    public CacheService(ReactiveStringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    public Mono<String> getUserCache(Long userId) {
        return redisTemplate.opsForValue().get("user:" + userId);
    }
    
    public Mono<Boolean> setUserCache(Long userId, String data) {
        return redisTemplate.opsForValue().set("user:" + userId, data);
    }
}

WebFlux 的局限性

不是所有场景都适合 WebFlux

场景推荐原因
IO 密集型(API 网关、实时数据)WebFlux高并发、低资源
CPU 密集型(复杂计算)Spring MVC计算能力更强
团队不熟悉响应式Spring MVC学习成本低
混合同步/异步代码Spring MVC响应式需要全链路响应式

禁止行为

java
// 绝对禁止:阻塞操作
@GetMapping("/bad")
public Mono<String> badExample() {
    // 这是阻塞操作,会杀死事件循环!
    Object result = jdbcTemplate.queryForObject("SELECT ...", ...);
    return Mono.just(result.toString());
}

// 正确做法:使用 R2DBC
@GetMapping("/good")
public Mono<String> goodExample() {
    return r2dbcTemplate.queryForObject("SELECT ...", String.class);
}

性能对比测试

java
@RestController
public class BenchmarkController {
    
    @Autowired
    private WebClient webClient;
    
    // 模拟 100ms 延迟的外部调用
    @GetMapping("/test-blocking")
    public String testBlocking() throws Exception {
        Thread.sleep(100);
        return "done";
    }
    
    @GetMapping("/test-reactive")
    public Mono<String> testReactive() {
        return Mono.delay(Duration.ofMillis(100)).thenReturn("done");
    }
}

JMeter 测试结果(8 核机器,2000 并发):

指标Spring MVCWebFlux
吞吐量1500 req/s8000 req/s
平均响应时间180ms35ms
99% 响应时间500ms80ms
CPU 利用率40%70%
线程数2008

总结

WebFlux 的核心优势:

优势说明
高并发低资源事件循环用少量线程处理大量并发
内存占用低无需为每个请求分配线程栈
背压支持自动流量控制,防止崩溃
HTTP/2 支持原生多路复用,连接复用

但 WebFlux 不是银弹,它需要:

  1. 全链路响应式(数据库、缓存、远程调用都要响应式)
  2. 团队掌握响应式编程
  3. 合适的使用场景(IO 密集型)

留给你的问题

假设你有一个订单查询接口,需要:

  1. 查询 MySQL 获取订单信息(10ms)
  2. 查询 Redis 获取缓存的物流信息(5ms)
  3. 调用物流服务 API 获取实时状态(50ms)

订单量:每天 1000 万单

当前方案:Spring MVC + Tomcat 200 线程

  1. 如果改成 WebFlux + 响应式数据访问,线程数可以降到多少?
  2. 如果物流服务 API 不支持响应式调用,只能用同步 RestTemplate 怎么办?
  3. 什么情况下,WebFlux 的性能可能不如 Spring MVC?

思考这些问题,能帮助你判断何时选择 WebFlux。

基于 VitePress 构建