
本文讲解如何在非阻塞式 Reactor 编程中,安全地顺序执行两个 Mono(先触发事件、再获取数据并保存),避免因误用 block() 导致线程阻塞异常。
本文讲解如何在非阻塞式 reactor 编程中,安全地顺序执行两个 mono(先触发事件、再获取数据并保存),避免因误用 `block()` 导致线程阻塞异常。
在基于 Spring WebFlux 或 Project Reactor 的响应式应用中,常见的错误是试图在事件循环线程(如 reactor-http-nio-2)中调用 block()、blockFirst() 等阻塞方法——这会直接导致 IllegalStateException: block() is not supported in thread reactor-http-nio-X 异常,破坏整个响应式链的非阻塞性。
你遇到的问题源于以下写法:
spi.messageEvent(message).doOnSuccess(it -> {
spi.getPersonById(evt.getVictimId()).doOnSuccess(victim -> {
repository.save(victim);
}).block(); // ❌ 错误:在非阻塞线程中强行阻塞
}).block(); // ❌ 同样错误
doOnSuccess 是副作用操作,不改变数据流;而 block() 会同步等待 Mono 完成,彻底违背响应式编程原则,且在 Netty/Reactor HTTP 线程中被明确禁止。
✅ 正确做法是使用声明式链式操作符,让整个流程保持异步、非阻塞、可组合:
使用 then() 实现“执行后继续”的语义
Mono.then(Mono
return spi.messageEvent(message)
.then(spi.getPersonById(evt.getVictimId())) // ✅ 顺序执行,无阻塞
.flatMap(victim -> repository.save(victim)); // ✅ save() 通常返回 Mono<victim>,用 flatMap 处理</victim>
⚠️ 注意:若 repository.save(victim) 返回的是 Mono
(例如 Spring Data R2DBC 的 save()),则应使用 .then();若返回 Mono (如某些自定义实现),则用 .flatMap() 更合适。多数现代响应式仓库(如 R2DBC、Mongo Reactive)的 save() 返回 Mono ,因此推荐 flatMap。
补充:增强健壮性的完整写法
实际生产中,还需考虑错误传播与日志追踪:
return spi.messageEvent(message)
.doOnNext(__ -> log.debug("Message event triggered for: {}", message))
.then(spi.getPersonById(evt.getVictimId()))
.doOnNext(victim -> log.debug("Fetched victim: {}", victim.getId()))
.flatMap(repository::save)
.doOnSuccess(saved -> log.info("Victim saved successfully: {}", saved.getId()))
.onErrorResume(e -> {
log.error("Failed to process message event and save victim", e);
return Mono.error(new ServiceException("Failed to persist victim after event", e));
});
关键原则总结
- ❌ 永远不要在 Mono/Flux 链中(尤其在 WebFlux Handler、Filter、Controller 中)调用 block()、toFuture().get() 等同步阻塞方法;
- ✅ 始终使用响应式操作符:then()(忽略前值、接力执行)、flatMap()(转换并扁平化)、concatWith()(严格顺序拼接)等;
- ✅ doOnSuccess / doOnNext 仅用于副作用(如日志、指标),不可用于触发新异步流程(应改用 flatMap 或 then);
- ✅ 所有数据库访问、HTTP 调用必须使用响应式客户端(如 WebClient、R2DBC、ReactiveMongoTemplate),确保整条链路非阻塞。
遵循以上模式,即可构建高性能、可伸缩的响应式服务,彻底规避线程阻塞风险。










