☰
Spring WebFlux响应式编程实战:线程模型、Mono/Flux与背压机制
2026/10/2 3:52:08 网站建设 项目流程

最开始接触 Spring WebFlux 时,我和不少人的想法一样:换了这个响应式框架,接口是不是就能快一倍?后来真正上手压测才发现,这个预期在多数场景下是错的——在单接口、低并发的常规负载里,WebFlux 反而因为多了一层响应式数据处理链路,单次请求耗时往往还略高于 Spring MVC。那它到底凭什么被称为下一代 Web 框架?这个问题我花了不少时间才想明白:WebFlux 真正解决的从来不是"单个请求有多快",而是"同样一份机器资源,能扛住多少个并发连接"。

这篇文章我会围绕 Spring WebFlux 的原理和落地展开,从它解决的线程模型问题讲起,到 Mono/Flux 的核心抽象、Netty 的运转机制,再给出可直接复制的注解式与函数式两种写法,最后聊聊数据库集成、压测数据和我踩过的一堆坑。适合正在做技术选型评估的同学,也适合已经上了 WebFlux 但被各种异步问题卡住的开发者。

1. 传统 Web 容器的线程困境:为什么每个请求都要占一个线程

1.1 一个 "看似够用" 的 IO 阻塞现场

先看一个非常典型的服务:Spring MVC + Tomcat + JDBC。Tomcat 默认的最大工作线程数是 200,也就是说它同一时刻最多处理 200 个请求。这个数字听起来不少,但你要知道,线程在绝大多数时间里并不是在"干活",而是在"等待"。

假设一个接口内部做了三件事:查一次数据库(平均 40ms)、调一次下游 HTTP 接口(平均 60ms)、再做一些内存计算(10ms)。总耗时 110ms,但真正占用 CPU 的时间可能只有 10ms,剩下 100ms 全是 IO 等待。Tomcat 的工作线程在等数据库返回、等下游响应时,它就是干坐着占着线程不释放。于是 200 个线程实际能支撑的同时处理能力,和"200 个并发请求正在执行"是对等的,而性能估算就变成了:

单线程每秒可处理请求数 = 1000ms / 110ms ≈ 9 QPS
200 线程理想吞吐 ≈ 9 × 200 ≈ 1800 QPS

但在真实环境里,一旦并发超过 200,多余请求直接排队,等待时间肉眼可见地暴涨。而线程等待时消耗的栈内存(默认 1MB/线程)、上下文切换的开销,都是实实在在的系统成本。你可能会想,那把 maxThreads 调到 1000 不就行了?实际效果有限:线程太多时 CPU 大量时间花在线程切换上,而不是业务处理上;另外每线程 1MB 栈,1000 个线程光栈内存就接近 1GB,8G 内存的机器扛起来很吃力。

1.2 WebFlux 的思路:用极少数线程服务海量连接

Spring WebFlux 换了一套完全不同的思路,它默认跑在 Netty 上,核心是事件循环模型。Netty 的 event loop 线程数量默认是 CPU 核数 × 2,比如一台 4 核机器就是 8 个线程。这 8 个线程不负责"一个请求跟到底",而是负责监听所有连接上是否有 IO 事件发生——数据可读了就处理可读事件,数据可写就处理可写事件,一个线程可以在极短时间内切换服务成千上万个连接。

这个模型能不能成立,有一个核心前提:事件循环线程上不能有阻塞操作。这句话我再强调一遍——在 WebFlux 里,事件循环线程是绝对不能执行阻塞调用的。原因很简单:这 8 个线程是你整个 Web 服务的"心脏",任何一个线程被一次阻塞查询卡住 100ms,这 100ms 内该线程负责的所有连接都得不到响应,服务整体延迟会瞬间劣化。

作为对比,4 核 8G 的机器上,Tomcat 模式(200 线程)和 Netty 模式(8 事件循环线程)处理 2000 并发长连接时,前者的线程资源已经排队打到极限,后者的事件循环依然游刃有余。这就是 WebFlux 的核心价值:不是"快",而是"省",省下了线程,也就省下了内存、上下文切换,换来的是高并发下的稳定承载能力。

1.3 直面取舍:它不是银弹

如果你看完上面觉得 WebFlux 无敌了,那我得泼一盆冷水。WebFlux 的适用前提是"请求处理链路中的 IO 都是非阻塞的"。如果你项目里的数据访问层还是 MyBatis/JDBC、RPC 客户端还是同步阻塞模型,那么 WebFlux 带来的收益会在第一层数据库调用时就全部抵消——因为一个慢查询照样会阻塞住线程,而且阻塞的是宝贵的事件循环线程,后果比 Spring MVC 更严重。

所以在选型之前先盘点一下你的依赖清单:Redis 有没有响应式客户端?数据库驱动是否支持 R2DBC?下游 HTTP 调用是否能切换到 WebClient?如果答案大部分是"否",那我建议不要硬上 WebFlux。它适合的场景是网关、消息推送、实时数据聚合这类 IO 密集且并发连接数高的系统,而不是什么业务都往里塞。

2. Mono 与 Flux:响应式世界里的两种 "数据管道"

2.1 声明式编程的思维转变

从 Spring MVC 切到 WebFlux,最别扭的不是 API,而是思维方式。传统写法是命令式:你调用一个方法,方法执行完毕把结果返回给你,你拿到结果再去做下一步。响应式写法是声明式:你定义一条数据处理管道,描述"数据来了之后要经历哪些变换",但管道本身不会自动运行,必须有人订阅,数据才开始流动。

打个比方,命令式编程像你去餐厅点菜,厨师做好端到你面前,你吃一口再决定下一步;响应式编程像你定了一份外卖订单,你描述清楚"要什么菜、送到哪、加辣不加辣",商家收到订单后才开始备餐,中间每一步都是异步通知。你写的代码其实是一份"处理说明书",真正的执行发生在订阅那一刻。

入门的时候最容易犯的错,就是把 Mono 和 Flux 当普通集合用,以为代码一执行就能拿到里面的值。如果写mono.block(),或者调用flux.collectList().block(),在 Spring MVC 项目里似乎也能跑通,但一旦放到 WebFlux 的事件循环线程上,这种阻塞获取值的方式会把整个服务的并发能力打回原形,而且比直接报错更隐蔽——它不一定会挂,只是延迟奇高、偶发超时,非常难排查。

2.2 Mono:最多只有一个结果的异步容器

Mono<T>表示异步产生 0 或 1 个数据项。它对应的是传统写法里的"返回单个对象",比如查询一条用户记录、调用一次远程接口、读取一条配置。常见构造方式有Mono.just()和Mono.fromCallable(),这里有个细节很关键:Mono.just(value)里的 value 在声明时就计算好了,而Mono.fromCallable(() -> service.query(id))里的查询逻辑会延迟到订阅时才执行。

如果你的业务逻辑里有真实的远程调用或数据库查询,一定用fromCallable,或者用Mono.defer包裹。否则你会发现,方法还没被请求触发,逻辑就已经执行了,这在流式接口里会产生诡异的行为,比如启动时报错,或者多个订阅者拿到同一个过期结果。

Mono还自带一套完整的错误处理机制,onErrorReturn可以在出错时返回默认值,onErrorResume可以切换到备用调用逻辑,retryWhen可以实现带退避的重试。这些能力在写稳健接口时非常有用,尤其是做聚合层服务时,下游某个服务抖动,不至于把错误直接抛给前端。

2.3 Flux:0 到 N 个元素的异步序列

Flux<T>表示异步产生 0 到 N 个数据项。凡是"返回列表"或者"流式输出"的场景,都是 Flux 的地盘。比如查询用户列表、订阅消息队列、生成实时指标数据流。

Flux 最让人眼前一亮的能力是时间维度上的编排。像Flux.range(1, 10).delayElements(Duration.ofSeconds(1)),就会每隔一秒吐出一个数字,接口返回的媒体类型设为text/event-stream后,前端浏览器可以直接用原生 EventSource 订阅,连 WebSocket 都不需要。这在传统 Spring MVC 里要实现 SSE,得手工维护响应流和调度线程池,代码量完全不是一个量级。

操作符是 Flux 的另一个核心优势。map做同步映射、flatMap做异步并发映射、filter过滤、zip合并多个数据源、buffer攒批、window按窗口切分。比如你要并发调用三个下游接口再聚合结果,用Mono.zip(a, b, c)一步到位,不需要像传统写法那样搞三个线程池和 CountDownLatch。

2.4 背压:消费者能消化多少,生产者就给多少

背压(Backpressure)是响应式流规范里极其重要、却被很多人忽略的概念。简单说,消费者向生产者声明"我一次能处理 N 条",生产者就按 N 条分批推送,而不是哗啦啦把数据全倒给消费者。这避免了生产速度快于消费速度时,内存被无界缓冲挤爆的问题。

Reactor 内部大多数操作符都天然支持背压。Flux.interval每固定时间发一条,属于自带背压的节奏;而limitRate(100)则会告诉上游"每次最多给我 100 条",避免一次性拉取万条数据。在真实项目里,处理数据库流式读取或 Kafka 消费时,背压是保护内存的最后防线。如果你发现系统在数据量突增时 OOM,先检查是不是有某个操作符把数据全量攒在内存里了——这在collectList()之后做全量计算时经常发生。

3. 线程模型拆解:Netty 事件循环里的请求生命周期

3.1 Reactor Netty 的线程分工

Spring WebFlux 默认内置 Reactor Netty 服务器,线程模型分为两组:boss 线程组和 worker 线程组。boss 线程负责监听端口、接受新连接,数量通常相当少(默认 1 个);接受连接后,连接会注册到某个 worker 线程(也就是 event loop),之后的读、写、编解码都由该 worker 负责。

worker 线程数量默认是 CPU 核数 × 2,这就是事件循环线程。每个 worker 内部维护一个 NIO Selector,循环执行"选择事件 → 分发处理"的过程。如果事件处理是在线程池里的其他线程做的,worker 就能迅速回到 selector 继续处理下一个事件,这是支撑高并发连接的核心机制。

注意:Reactor Netty 将业务代码的执行放在事件循环线程上(除非显式切换调度器),这意味着你的 Controller 方法、WebClient的回调、数据库响应回调,默认都跑在 event loop 上。所以任何Thread.sleep、block()、同步 JDBC 调用,都是对事件循环线程的"非法占用"。

3.2 一个 HTTP 请求从进入到返回的完整链路

一个请求进来,链路是这样的:Reactor Netty 的 worker 线程读取请求字节流,解码成HttpServerRequest,交给 WebFlux 的ServerWebExchange封装,然后经过DispatcherHandler匹配HandlerMapping(注解式路由或函数式路由),调用对应的Handler(也就是你的 Controller 方法或函数式 Handler),得到Mono<ServerResponse>或Flux<ServerResponse>,再经过编码器写出响应。

这中间每一步都返回响应式类型,目的就是让 worker 线程在等待下游时可以被释放。例如你的 handler 内部调用了 WebClient 调用下游服务:webClient.get().uri(...).retrieve().bodyToMono(Order.class),这一行不是立刻发起阻塞调用,它会向 Reactor Netty 注册一个 IO 事件回调,然后方法立即返回Mono,worker 线程立刻空闲出来去处理其他连接。等下游响应到达后,再通过回调继续执行后续逻辑。整个过程一个线程服务大量并发请求,就是这么做到的。

3.3 调度器切换:publishOn 与 subscribeOn 的定位

Reactor 的调度器(Scheduler)决定了代码在哪个线程池执行。要记住两个操作符:subscribeOn改变的是订阅起点线程,影响的是上游Mono/Flux从源头产生数据时所在线程;publishOn改变的是下游操作符执行时的线程。

最常用的是publishOn(Schedulers.boundedElastic())把一段"绕不开的阻塞操作"放到弹性线程池,比如对接一个没有响应式客户端的遗留系统。有一个真实的教训:我在聚合服务里为了省事,在响应式链路上直接调了一个旧版客户端的同步方法,压测到 300 并发时服务假死,日志里全是连接超时。后来排查才发现,事件循环线程被同步调用占满,根本来不及处理新的连接事件。改成publishOn切到 boundedElastic 线程池后,问题立刻消失。

4. 实战搭建:注解式 Controller 与函数式 Router 两种姿势

4.1 依赖选择与启动配置

先把依赖写清楚。Spring Boot 3.x 的项目里,引入一个spring-boot-starter-webflux就够了:

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

这里有个容易掉进去的坑:不要在引入 WebFlux starter 的同时再保留spring-boot-starter-web(Spring MVC 的 starter)。一旦两者共存,Spring Boot 默认优先自动配置 Spring MVC,你的代码跑在 Tomcat+Filter+Servlet 容器上,写出来的响应式代码无法发挥作用,行为也变得很怪。实际开发中经常有项目为了迁移 WebFlux,忘了剔除原来的 web starter,结果压测一直没效果,查了半天才发现问题。

还有一种排查方式:启动日志里看看是 Netty 还是 Tomcat。如果是 Netty,会打印Netty started on port 8080;如果看到 Tomcat 的初始化日志,说明 MVC 在起作用。

4.2 注解式 Controller:从 Spring MVC 迁移成本最低

如果你习惯了 Spring MVC 的@RestController,WebFlux 的注解式开发会非常亲切。核心变化只有返回值类型:原来返回User,现在返回Mono<User>;原来返回List<User>,现在返回Flux<User>。

@RestController @RequestMapping("/users") public class UserController { private final WebClient userServiceClient; private final WebClient orderServiceClient; public UserController(WebClient.Builder builder) { this.userServiceClient = builder.baseUrl("http://user-service").build(); this.orderServiceClient = builder.baseUrl("http://order-service").build(); } @GetMapping("/{id}") public Mono<UserDetail> getUserDetail(@PathVariable Long id) { Mono<User> userMono = userServiceClient.get() .uri("/users/{id}", id) .retrieve() .bodyToMono(User.class); Mono<List<Order>> orderMono = orderServiceClient.get() .uri("/orders?userId={id}", id) .retrieve() .bodyToFlux(Order.class) .collectList(); return Mono.zip(userMono, orderMono) .map(tuple -> new UserDetail(tuple.getT1(), tuple.getT2())); } }

注意Mono.zip的用法:两个互不依赖的远程调用通过 zip 并发执行,而不是写成userMono.flatMap(user -> orderMono.map(...))那种串行方式。串行会让总耗时变成两次调用的耗时之和,并发则是两者取最大值。这是响应式编程里最容易提升性能的地方,也是初学阶段最容易写串的地方。

4.3 函数式路由:RouterFunction 的显式组合

函数式路由提供另一种风格:路由定义与处理逻辑用代码显式构建,不依赖注解扫描。

@Configuration public class RouterConfig { @Bean public RouterFunction<ServerResponse> userRoutes(UserHandler handler) { return RouterFunctions.route() .GET("/users/{id}", handler::getUser) .GET("/users/{id}/orders", handler::getUserOrders) .POST("/users", handler::createUser) .build(); } } @Component public class UserHandler { public Mono<ServerResponse> getUser(ServerRequest request) { Long id = Long.valueOf(request.pathVariable("id")); Mono<User> userMono = userService.findById(id); return ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) .body(userMono, User.class); } }

这种方式比较适合两类场景:一是希望路由规则显式可见,不喜欢注解散落在各个 Controller 里;二是希望多个服务之间复用同一套路由定义,比如公共注册模块的 base 路由,通过函数式组合能方便地合并。函数式路由与注解式在 WebFlux 中是可以共存的,我在实际项目里通常把请求入口都用注解,只有公共路由和网关层用函数式,这样每个团队的成员上手都会比较快。

4.4 流式响应与 SSE:WebFlux 的独门绝技

WebFlux 的流式能力是 Spring MVC 很难替代的。下面这个接口每隔一秒输出一个递增数字:

@GetMapping(value = "/sse/numbers", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<ServerSentEvent<Integer>> streamNumbers() { return Flux.interval(Duration.ofSeconds(1)) .map(seq -> ServerSentEvent.<Integer>builder() .id(String.valueOf(seq)) .event("number") .data(seq.intValue()) .build()); }

浏览器端只需要const es = new EventSource('/sse/numbers')就能订阅,服务端推送在长连接场景下非常方便。这里有几个实际要注意的点:

客户端断开后,服务端要能感知并取消订阅,否则interval会一直跑,造成后台线程泄漏。理想的做法是配合takeUntilOther或者用onCancel回调来清理资源;我见过有的项目上线后内部日志量莫名翻倍,排查发现就是 SSE 连接断开后流没被取消,定时任务一直在执行。

另外,跨网关部署时要注意代理的 readTimeout。很多 Nginx 默认 60 秒无响应就断开连接,SSE 是持续有数据的,但如果某一时间段内没有新事件,连接会被错误切断。解决方案是服务端定期发送 comment 行(ServerSentEvent里 comment 字段)保活,或者在网关层把 readTimeout 调大。

5. 数据库与外部服务集成的现实边界

5.1 关系型数据库:R2DBC 的真实成熟度

JDBC 本身是阻塞模型,想在 WebFlux 的链路上查关系型数据库,标准方案是 R2DBC(Reactive Relational Database Connectivity)。

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-r2dbc</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>r2dbc-postgresql</artifactId> </dependency>

用 R2DBC 之后,Repository 接口可以返回Flux<T>或Mono<T>:

public interface UserRepository extends ReactiveCrudRepository<User, Long> { Mono<User> findByUsername(String username); Flux<User> findByStatus(String status); }

但你必须清楚,R2DBC 生态远没有 JDBC 成熟。复杂动态 SQL、存储过程、多表关联查询、数据库方言兼容性,都还是坑。我自己在一个项目中就遇到过大分页查询在 PostgreSQL 下生成的 SQL 效率明显不如原生 JDBC 的情况,最后被迫在响应式链路上用publishOn切到弹性线程池,内部改回普通 JDBC 查询,收益虽然打折扣,但至少功能稳定。

如果你的业务高度依赖复杂 SQL 和强事务,我的建议是:保持 Spring MVC + JDBC,别硬上 WebFlux。全链路响应式听起来美好,但代价是开发效率显著下降。只有在并发量确实打到了传统方案天花板,且业务模型简单、以单表 CRUD 为主时,R2DBC 才是个合理选择。

NoSQL 方面成熟度高很多:MongoDB 官方驱动原生支持响应式,Redis 的 Lettuce 客户端也是非阻塞的,Spring Data 都有对应的响应式封装,这些配合 WebFlux 比较顺。

5.2 外部 HTTP 调用:WebClient 的正确用法

在 WebFlux 项目里,RestTemplate基本是废的,必须用WebClient。它底层基于 Reactor Netty,天然支持非阻塞 IO。我当时把服务里十几个 RestTemplate 调用全部替换成 WebClient 后,压测数据立竿见影。

WebClient client = WebClient.builder() .baseUrl("http://order-service") .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .build(); Mono<Order> order = client.get() .uri("/orders/{id}", 1001) .retrieve() .bodyToMono(Order.class);

WebClient 的配置藏在细节里。默认连接池上限、响应超时、重试策略都需要显式调优。我踩过的一个坑是默认没有设置连接超时,下游服务假死时,上游请求会一直挂到 TCP 层超时,用户端表现为偶发白屏;后来在连接层加了reactor.netty.http.client的连接超时配置,同时配合retryWhen指定重试窗口,接口的可用性才有了明显改观。

5.3 生态盘点:哪些组件能响应式,哪些不能

决定 WebFlux 能不能落地,关键在于贯穿业务链路的每个 IO 组件是否有非阻塞客户端。我通常会先给团队列一张清单:

  • Redis 缓存:Lettuce 响应式客户端,可用
  • MongoDB:官方的响应式驱动,可用
  • MySQL/PostgreSQL:R2DBC 驱动,基本可用,复杂 SQL 场景要测试
  • Kafka 消息:Reactive Kafka 客户端,可用
  • Elasticsearch:响应式驱动,可用
  • 自研 RPC、老版本 MQ SDK:绝大多数没有响应式版本,需要包一层隔离或者放弃

自研 RPC 这一项其实是很多项目硬上 WebFlux 失败的主因。业务方把入口切成了响应式,结果远程调用内部同步等接口返回,非但没有提升并发能力,反而因为事件循环被阻塞引入了更多问题。我参与过的一个项目最终选择了折中方案:WebFlux 只做网关和聚合层,所有自研 RPC 调用通过boundedElastic调度器隔离,至少保证核心链路不被阻塞。

6. 压测对比与避坑指南:从真实案例说起

6.1 一组典型的对比数据

下面这组数据来自我当时在同一台 4 核 8G 服务器上做的压测,接口逻辑模拟了一次 50ms 的下游 IO 等待,用 wrk 分别压 Spring MVC 和 WebFlux,不同环境结果会有差异,不代表标准 benchmark,但趋势非常有参考价值。

压测并发Spring MVC P99 延迟WebFlux P99 延迟Spring MVC 线程池状态
100180ms210ms空闲,稳定
500950ms240ms线程池接近饱和,排队明显
10002500ms+280ms线程池已满,大量超时
2000大量连接超时320ms完全不可用,错误率飙升

低并发下 Spring MVC 反而略微领先,因为响应式链路多了一层调度开销,这和前面讲的"不是快、是省"完全一致。并发一旦上来,线程池排队带来的延迟飙升在 MVC 里是灾难性的,而 WebFlux 的事件循环模型让延迟分布非常平稳。

但注意,如果接口内部是 20ms CPU 计算且没有外部 IO,两者压测结果差异很小,WebFlux 甚至在简单计算类型接口上会有轻微劣势。所以压测一定要用贴近真实业务的场景,不能拿一个return Mono.just("ok")的接口得出结论。

6.2 那些我踩过又填平的坑

第一个大坑是.block()。初学响应式时,总会觉得"反正不是事件循环线程,block 一下也无妨"。在 WebFlux 里,block()一旦发生在事件循环线程上,整个服务直接卡死,恢复过来也是半分钟后的事。更可怕的是它不一定会百分百复现,而是高并发偶发出现,非常难排查。最好在写代码时就立下规范:响应式链路中严禁出现block(),所有取值都通过操作符完成。

第二个坑是链路中混用 JDBC。这个在上面讲过,核心问题是你写的代码不会报错,但所有响应式优势都被静默吃掉,而且事件循环线程被阻塞时整个服务的症状非常诡异——CPU 不高、内存充足,但请求就是超时。排查手段是先看线程栈,如果大量epollWait和block交替出现,基本可以断定是阻塞调用污染了事件循环。

第三个坑是异常处理。响应式链路的异常不会像传统 try-catch 那样直接抛出,而是onError信号沿着流向下传播。如果你在map里抛了一个异常,却只在subscribe的 lambda 里处理,很有可能根本不会被捕获。开发时可以在关键链路上打checkpoint("调用订单服务"),排错时从异常栈里能直接定位是哪一段链路出的问题。

第四个坑是调试信息断层。异步执行让调用栈在 Reactor 操作符之间被截断,日志里经常只有"哪里异常",没有"哪条链路异常"。开启 Reactor 的调试模式(Hooks.onOperatorDebug())可以拿到相对完整的操作链,但注意它在生产环境有性能开销,不能常开。更实用的做法是给每个关键操作符链加上log()或者自定义 cabinetry 日志。

6.3 工作量评估:什么时候该果断放弃 WebFlux

并不是所有项目都适合 WebFlux。我个人的经验,下面几个条件命中两条以上,建议谨慎选择或直接放弃:

团队没有现成的响应式编程经验。响应式的思维模型和排错方式与传统编程差异很大,新人上手周期长;如果团队平时工作节奏紧张,还是用熟知的 Spring MVC 更稳妥。

业务链路中仍有大量无法替换的阻塞依赖,比如自研 RPC、老版 MQ、复杂 JDBC SQL。这些依赖一旦存在,WebFlux 的收益就会被反复抵消,引入的复杂度却不会减少。

强事务、复杂 SQL 是核心需求。响应式事务在 R2DBC 里虽然能用,但实际体验和生态远不如声明式事务在 JDBC 下顺手,跨表、跨库、分布式事务更是难上加难。

接口以 CPU 密集计算为主,没有多少 IO 等待。这种方式想拿 WebFlux 提高吞吐,发挥不了优势,还徒增调度开销。

我个人到现在都保留了一部分常规 CRUD 服务在 Spring MVC 上,只用 WebFlux 承接网关、消息推送和实时聚合这类真正适合它的业务。这种混搭不"纯粹",但运维和同事的反馈都还不错。如果你所在团队打算第一次上 WebFlux,我的建议是先从非核心的小服务试水,跑通全链路响应式,包括 R2DBC 查询、WebClient 调用、流式响应,整体压测达标后再扩大范围,否则排错和回退的成本会非常大。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询