线程模型与 Netty
传统 Servlet 线程模型
传统 Spring MVC 基于 Servlet 容器,采用每请求一线程模型:
1 2 3
| 请求1 ──→ 线程1 ──→ [接收请求 → 处理业务 → 等待DB → 返回响应] ──→ 释放 请求2 ──→ 线程2 ──→ [接收请求 → 处理业务 → 等待DB → 返回响应] ──→ 释放 请求3 ──→ 线程3 ──→ [接收请求 → 处理业务 → 等待DB → 返回响应] ──→ 释放
|
特点
- 一个请求独占一个线程,从头到尾
- 线程在等待 I/O(数据库、网络调用)时阻塞,什么都不做
- 高并发时需要大量线程(Tomcat 默认 200 个)
- 线程切换开销大,内存占用高(每线程约 1MB 栈空间)
WebFlux 事件循环模型
WebFlux 基于 Netty,采用事件循环(Event Loop) 模型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ┌─────────────────────────────────────┐ │ Event Loop 线程池 │ │ (少量线程,如 CPU 核心数) │ └─────────────────────────────────────┘ │ ┌───────────────────────────┼───────────────────────────┐ ↓ ↓ ↓ 接收请求1 接收请求2 接收请求3 │ │ │ ↓ ↓ ↓ 提交I/O任务 提交I/O任务 提交I/O任务 (非阻塞返回) (非阻塞返回) (非阻塞返回) │ │ │ └───────────────────────────┼───────────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ I/O 完成后触发回调 │ │ Event Loop 继续处理 │ └─────────────────────────────────────┘
|
核心思想
- 线程不等待 I/O 完成,立即去处理其他请求
- I/O 完成后通过回调/事件通知
- 少量线程就能处理大量并发连接
WebFlux 中的线程池
WebFlux 使用多个线程池,各司其职:
1. Event Loop 线程池
- 处理连接建立、读写事件
- 绝对不能阻塞,否则影响所有请求
2. boundedElastic 线程池
1 2
| Mono.fromCallable(() -> blockingCall()) .subscribeOn(Schedulers.boundedElastic())
|
- 处理必须阻塞的操作(如调用阻塞 API)
- 弹性伸缩,有上限保护
- 适合 I/O 密集型阻塞操作
3. parallel 线程池
1 2 3
| Flux.range(1, 100) .parallel() .runOn(Schedulers.parallel())
|
- 线程数 = CPU 核心数
- 适合 CPU 密集型任务
线程模型对比
| 方面 |
传统 MVC |
WebFlux |
| 线程数量 |
大量(默认 200+) |
少量(CPU 核心数) |
| 等待 I/O 时 |
线程阻塞等待 |
线程去处理其他请求 |
| 线程利用率 |
低(大量时间在等待) |
高(几乎不等待) |
| 内存占用 |
高(每线程 ~1MB) |
低 |
| 上下文切换 |
频繁 |
很少 |
| 编程模型 |
同步阻塞 |
异步非阻塞 |
WebFlux 与 Netty 的关系
Netty 是什么
Netty 是一个高性能网络通信框架,提供:
- 非阻塞 I/O(NIO)
- 事件驱动模型
- 高效的内存管理
- 协议编解码支持
直接用 Netty
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class HttpServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { if (msg instanceof HttpRequest) { HttpRequest request = (HttpRequest) msg; String uri = request.uri(); FullHttpResponse response = new DefaultFullHttpResponse( HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer("Hello", CharsetUtil.UTF_8) ); response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); ctx.writeAndFlush(response); } } }
|
需要自己处理:路由、参数解析、JSON 序列化、异常处理、连接管理…
用 WebFlux
1 2 3 4 5 6 7 8
| @RestController public class HelloController { @GetMapping("/hello/{name}") public Mono<User> hello(@PathVariable String name) { return userService.findByName(name); } }
|
WebFlux 在 Netty 之上提供了什么
| 功能 |
Netty |
WebFlux |
| 路由 |
手动解析 URI |
@GetMapping、RouterFunction |
| 参数绑定 |
手动解析 |
@PathVariable、@RequestBody |
| JSON 序列化 |
手动处理 |
自动(Jackson) |
| 异常处理 |
手动 try-catch |
@ExceptionHandler |
| 依赖注入 |
无 |
Spring IoC |
| 配置管理 |
无 |
application.yml |
| 安全认证 |
手动实现 |
Spring Security |
| 数据访问 |
手动实现 |
Spring Data R2DBC |
| 测试支持 |
手动 |
WebTestClient |
类比理解
1 2 3 4 5 6
| Netty ≈ TCP/HTTP 协议引擎(底层) WebFlux ≈ Web 开发框架(上层)
就像: JDBC ≈ 数据库连接(底层) MyBatis ≈ ORM 框架(上层)
|
Netty 是引擎,WebFlux 是汽车。 你可以直接用引擎,但大多数人更愿意开汽车。
什么时候直接用 Netty
- 自定义协议(非 HTTP)
- 极致性能优化
- 不需要 Spring 生态
- 构建基础设施(如网关、代理服务器)
为什么 WebFlux 适合高并发
- 少量线程处理大量连接:不需要为每个连接分配线程
- 线程不阻塞:I/O 等待时去处理其他请求
- 内存效率高:线程少,内存占用低
- 无线程切换开销:事件驱动,不需要频繁切换
相关链接
- Webflux简介
- 响应式编程基础
- Reactor核心API