SpringBoot 响应式编程

一、前置核心概念(面试必问开篇)

1.1 传统阻塞编程痛点(SpringMVC)

传统 SpringMVC:一请求一线程(Tomcat 线程池)

  • Tomcat 默认线程池核心数:10-200 有限固定线程
  • 线程一旦发起 DB 查询、RPC 调用、网络等待,线程全程阻塞休眠,CPU 空闲
  • 高并发场景:大量请求堆积等待线程,吞吐量急剧下降,容易线程耗尽雪崩

1.2 响应式编程定义

响应式编程(Reactive Programming):面向数据流、异步非阻塞、事件驱动 核心特质:

  1. 异步非阻塞:等待 IO 时释放线程,线程复用处理其他请求
  2. 数据流:数据源源不断推送(0 个 / 1 个 / 多个元素)
  3. 背压(Backpressure):下游消费慢时,上游主动限流,防止内存溢出
  4. 事件驱动:数据到达、异常、完成都会触发回调事件

1.3 两组关键区分

同步 VS 异步
  • 同步:调用方等待结果返回,线程阻塞
  • 异步:调用发起后立刻返回,结果通过回调 / 订阅通知
阻塞 VS 非阻塞
  • 阻塞:IO 等待期间线程挂起,无法干活
  • 非阻塞:IO 等待时线程释放,去处理别的任务,IO 就绪后再回调处理

1.4 Reactive Streams 官方规范(业界统一标准)

Java 响应式统一规范,解决各框架不兼容问题,4 大核心接口:

plaintext

复制代码
1. Publisher 发布者:产生数据流(上游)
    void subscribe(Subscriber subscriber);
2. Subscriber 订阅者:消费数据流(下游)
    void onSubscribe(Subscription s); //订阅建立
    void onNext(T t);  //收到数据
    void onError(Throwable t); //异常
    void onComplete(); //数据流结束
3. Subscription 订阅令牌:背压控制核心
    void request(long n); //向下游申请n条数据
    void cancel(); //取消订阅
4. Processor:既是Publisher又是Subscriber,数据流中间处理节点

背压核心 :下游通过request(n)告诉上游我能承受多少数据,上游不会疯狂推送压垮下游。

二、SpringBoot 响应式技术栈整体架构(图文结构)

整体架构层级(文本架构图)

plaintext

复制代码
客户端 <-----> 非阻塞容器(Netty)
        ↓
Spring WebFlux(响应式Web框架,替代SpringMVC)
        ↓
Reactor(Spring官方默认响应式实现框架,实现Reactive Streams)
        ↓
底层:NIO + 事件循环 + 线程调度器(Scheduler)
配套组件:
响应式DB:Spring Data R2DBC / Reactive MongoDB / Redis Reactive
响应式RPC:Spring Cloud Gateway、WebClient

两大核心容器区别

  1. SpringMVC:容器 Tomcat(Servlet 阻塞容器,基于 BIO/NIO 阻塞模式)
  2. WebFlux:默认容器 Netty(异步非阻塞 Reactor 模式),也可适配 Jetty、Undertow

三、Reactor 核心:Mono & Flux(面试重中之重)

Spring WebFlux 底层封装 Reactor,只有两个数据流类型:

3.1 Flux

  • 含义:0~N 个元素的数据流(集合、列表、多条数据)
  • 终止事件:onComplete /onError

3.2 Mono

  • 含义:0 或 1 个元素的数据流(单个对象、空、无返回)
  • 场景:单条查询、新增修改返回结果

3. 基础创建代码示例

java

运行

复制代码
//1. Flux 创建多条数据
Flux<String> flux = Flux.just("张三","李四","王五");
//数组、集合创建
Flux<Integer> numFlux = Flux.fromArray(new Integer[]{1,2,3});

//2. Mono 创建单个数据/空数据
Mono<String> mono = Mono.just("单个用户");
Mono<Void> empty = Mono.empty(); //无数据
Mono<String> error = Mono.error(new RuntimeException("异常"));

//3. 订阅消费(响应式必须订阅才会执行,惰性加载)
flux.subscribe(
    data -> System.out.println("收到数据:"+data), //onNext
    err -> err.printStackTrace(), //onError
    () -> System.out.println("数据流结束") //onComplete
);

4. 常用操作符(面试常考)

表格

操作符 作用
map 同步转换元素
flatMap 异步流转(嵌套 Mono/Flux,最常用)
filter 过滤数据
concat/merge 合并数据流
zip 多个数据流配对合并
timeout 设置超时
retry 异常重试
doOnNext/doOnError 回调埋点(日志、监控)

四、线程调度 Scheduler(底层原理必考题)

Reactor 通过 Scheduler 管控线程池,区分不同执行场景:

  1. Schedulers.single():单一线程,顺序执行
  2. Schedulers.parallel():CPU 密集型线程池(默认 CPU 核心数)
  3. Schedulers.boundedElastic():IO 密集型线程池(数据库、网络 IO 首选,弹性扩容)
  4. Schedulers.immediate():当前线程直接执行

切换线程两个核心方法:

  • publishOn()下游线程切换(后续操作运行在指定线程)
  • subscribeOn()上游源头线程切换(整个数据流源头运行线程)

面试考点:两者区别,subscribeOn 全局生效,publishOn 就近生效

五、Spring WebFlux 完整架构图解

5.1 WebFlux 两种编程模式

plaintext

复制代码
WebFlux
├─ 注解模式(和SpringMVC写法几乎一致,上手简单)
└─ 函数式编程模式(RouterFunction + HandlerFunction,纯流式定义路由,更灵活)

5.2 注解模式实战代码(日常开发主流)

1)启动依赖 pom.xml

xml

复制代码
<!-- 引入webflux,排除springmvc-tomcat依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
2)响应式 Controller

java

运行

复制代码
@RestController
@RequestMapping("/user")
public class UserReactiveController {

    // 返回单个用户 Mono
    @GetMapping("/{id}")
    public Mono<User> getUserById(@PathVariable Long id){
        // 模拟响应式数据库查询
        return userReactiveService.getById(id);
    }

    // 返回用户列表 Flux
    @GetMapping("/list")
    public Flux<User> getUserList(){
        return userReactiveService.listAll();
    }

    // 新增
    @PostMapping("/add")
    public Mono<User> addUser(@RequestBody Mono<User> userMono){
        return userMono.flatMap(user -> userReactiveService.save(user));
    }
}

5.3 函数式编程模式(面试偶尔考察)

路由 + 处理器拆分,无注解:

java

运行

复制代码
// 处理器
@Component
public class UserHandler {
    public Mono<ServerResponse> getUser(ServerRequest request){
        Long id = Long.valueOf(request.pathVariable("id"));
        Mono<User> userMono = userService.getById(id);
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(userMono, User.class);
    }
}

// 路由注册
@Configuration
public class RouterConfig {
    @Bean
    public RouterFunction<ServerResponse> userRouter(UserHandler handler){
        return RouterFunctions.route()
                .GET("/user/{id}", handler::getUser)
                .build();
    }
}

六、WebFlux 底层执行流程(口述结构图)

客户端请求 → Netty NIO 事件轮询器 (EventLoop) 接收连接 → 请求封装成 ServerWebExchange(上下文,替代 SpringMVC 的 HttpServletRequest) → 路由匹配处理器 Controller/HandlerFunction → 执行业务逻辑(DB/RPC 非阻塞调用,线程释放) → IO 就绪后回调线程推送 Mono/Flux 数据流 → Netty 异步写回响应给客户端

核心:全程不会长时间占用固定工作线程

七、SpringMVC VS WebFlux 面试高频对比表

表格

对比维度 SpringMVC(阻塞) Spring WebFlux(响应式非阻塞)
底层容器 Tomcat、Jetty(Servlet 阻塞规范) Netty(NIO 异步事件驱动)
线程模型 一请求一线程,线程池固定上限 少量核心线程,IO 等待释放线程,高并发吞吐高
编程模型 同步阻塞,返回实体类 异步流式,返回 Mono/Flux
数据库支持 JDBC 阻塞 R2DBC 响应式驱动
适用场景 业务复杂、CPU 计算多、并发一般 高并发 IO 密集:网关、推送、大量接口调用
学习成本 较高,需要理解流式、背压、异步回调
事务支持 完善 有限,R2DBC 事务复杂度更高

面试官追问标准答案:

不是 WebFlux 性能一定碾压 MVC:CPU 密集型业务 WebFlux 没有优势;海量 IO 等待场景(网关、千万级并发接口)WebFlux 吞吐量优势巨大。

八、配套响应式中间件实战

8.1 响应式数据库 R2DBC(替代 JDBC)

JDBC 是阻塞 API,无法适配响应式,WebFlux 必须使用 R2DBC 非阻塞驱动 核心依赖:

xml

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>

Repository 写法:

java

运行

复制代码
public interface UserR2dbcRepository extends R2dbcRepository<User,Long> {
    // 返回Mono/Flux
    Mono<User> findById(Long id);
    Flux<User> findAll();
}

8.2 WebClient(响应式 HTTP 调用,替代 RestTemplate)

RestTemplate 阻塞,WebFlux 内部推荐 WebClient 异步调用第三方接口

java

运行

复制代码
// 构建客户端
WebClient webClient = WebClient.create("https://xxx.api.com");

// 异步调用
Mono<String> resultMono = webClient.get()
        .uri("/info")
        .retrieve()
        .bodyToMono(String.class);

九、大厂高频面试题详细解析(含答题话术、踩坑点)

面试题 1:什么是响应式编程?解决了什么问题?

标准答案回答: 响应式编程是一套基于异步非阻塞、事件驱动、数据流的编程范式,遵循 Reactive Streams 规范,核心引入背压机制。 传统阻塞编程中线程遇到 IO 会被挂起,CPU 资源浪费,并发上限受线程池限制;响应式在线程等待 IO 时释放工作线程,线程可以复用处理其他请求,大幅提升 IO 密集场景吞吐量;同时背压可以解决上下游数据速率不一致导致的内存溢出问题。

面试题 2:Mono 和 Flux 区别?各自使用场景?

  1. Flux:承载 0~N 个元素数据流,多用于列表查询、批量数据推送;
  2. Mono:承载 0 或 1 个元素,多用于单条查询、新增修改、无返回接口; 底层两者都实现 Publisher 接口,Mono 可以转 Flux,Flux 也可聚合为 Mono。

面试题 3:publishOn 和 subscribeOn 区别?

  1. subscribeOn:修改数据源上游的执行线程,无论放在代码哪个位置,都会影响整个数据流源头线程,全局生效;
  2. publishOn:修改后续下游代码的执行线程,只对该方法之后的操作生效,就近生效; IO 密集场景一般用 boundedElastic 线程池。

面试题 4:背压(Backpressure)是什么?WebFlux 如何实现?

  1. 定义:上游生产数据速度远大于下游消费速度,若无限推送会堆积队列、内存溢出,背压就是下游向上游反馈自身消费能力,上游按需推送数据的限流机制;
  2. 实现:基于 Reactive Streams 的 Subscription 接口,下游调用 request (n) 申请指定条数数据,上游严格按照申请量推送。

面试题 5:SpringMVC 和 WebFlux 该如何技术选型?

答题分层:

  1. 选用 SpringMVC:
    • 业务逻辑复杂、大量 CPU 运算、事务依赖重
    • 团队没有响应式技术储备、迭代效率优先
    • 并发量不高,传统架构完全够用
  2. 选用 WebFlux:
    • IO 密集型场景:微服务网关(Spring Cloud Gateway 基于 WebFlux)、海量并发接口、消息推送、爬虫聚合
    • 需要极致吞吐,服务器资源有限需要压榨性能 补充:CPU 密集型 WebFlux 不会提升性能,反而因为异步调度有微小损耗。

面试题 6:WebFlux 有什么缺点?

  1. 调试难度大:异步流式代码栈追踪困难,日志排查问题繁琐;
  2. 生态适配有限:传统 JDBC、大部分老工具都是阻塞 API,需要替换 R2DBC、Redis Reactive;
  3. 事务管控复杂:R2DBC 事务相较于 JDBC 事务易用性差;
  4. 学习门槛高:需要理解异步、回调、线程调度、流式操作符,新手容易写出阻塞 bug;
  5. 第三方阻塞代码一旦嵌入 WebFlux,会直接破坏非阻塞模型,性能退化。

面试题 7:WebFlux 中不小心调用了阻塞代码会怎么样?怎么解决?

问题:阻塞代码会占用 Netty 核心 EventLoop 线程,事件循环被卡住,所有请求处理停滞,性能暴跌。 解决方案:

  1. 将阻塞代码丢入Schedulers.boundedElastic()线程池执行,隔离阻塞操作;

java

运行

复制代码
Mono.fromCallable(() -> 阻塞业务方法())
    .subscribeOn(Schedulers.boundedElastic());
  1. 尽量替换为原生响应式非阻塞 SDK。

面试题 8:Spring Cloud Gateway 为什么基于 WebFlux 而不是 SpringMVC?

网关核心诉求:海量请求转发、大量网络 IO 等待、高吞吐、低资源占用

  1. MVC 固定线程池无法支撑网关高并发转发场景,线程极易打满;
  2. WebFlux 非阻塞模型,少量线程即可处理上万并发连接,内存占用更低;
  3. 内置 WebClient 异步转发请求,适配上下游异步调用场景。

面试题 9:Flux 的 flatMap 和 concatMap 区别?

  • flatMap:异步并发处理元素,返回数据顺序无序,吞吐更高;
  • concatMap:串行有序处理,严格按照上游数据顺序返回,性能偏低; 业务有序需求选 concatMap,追求并发吞吐选 flatMap。

面试题 10:WebFlux 异常处理三种方式?

  1. 操作符内置:onErrorReturn() 异常返回默认值、onErrorResume()异常降级返回数据流、retry()重试;
  2. 全局统一异常处理器:@ControllerAdvice + @ExceptionHandler(注解模式通用);
  3. 函数式模式:onError 回调统一捕获。

十、面试口述加分总结(收尾话术)

响应式编程核心价值是优化 IO 密集型并发吞吐,Spring WebFlux 基于 Reactor+Netty 实现非阻塞异步 Web 服务,依靠 Mono/Flux 流式编程、Scheduler 线程调度、背压机制解决传统 Servlet 阻塞线程瓶颈;工作中不会全盘替换 MVC,一般网关、高并发接口使用 WebFlux,常规业务系统依旧使用 SpringMVC 做平衡选型,同时开发中严格规避阻塞代码保证非阻塞特性。

十一、面试避雷坑

  1. 不要说 WebFlux 性能全面碾压 MVC,区分 IO/CPU 场景;
  2. 牢记 JDBC 不能用于 WebFlux 响应式场景,必须 R2DBC;
  3. 区分 publishOn/subscribeOn 作用范围;
  4. 背压是 Reactive Streams 核心,不能遗忘。
相关推荐
Python私教1 小时前
请求超时不等于失败:把写操作恢复设计成事实回读
后端·python
goldbin_zhang_xu1 小时前
我用 Go 重写了一个 OpenClaw 框架:这就是 GoClaw
开发语言·后端·golang·agent框架·goclaw·双循环机制
IT_陈寒1 小时前
Redis的KEYS命令差点把我的生产环境拖垮,改用SCAN吧
前端·人工智能·后端
冰夏之夜影1 小时前
【解决方案】SpringBoot项目添加ssl证书后不生效问题
spring boot·后端·ssl
Bonnie_12151 小时前
10-深入理解ConcurrentHashMap(JDK1.8)
java·开发语言
莫得感情 o1 小时前
设计模式 22 · 三个冷门模式:中介者、访问者、解释器
java·设计模式
2601_963870211 小时前
【计算机毕业设计】基于Spring boot+Vue系统的健身俱乐部管理系统的设计与实现
spring boot·后端·课程设计
倔强的石头_1 小时前
TextIn xParse + WorkBuddy实战,零门槛轻松打造财报解析助手
后端
andongni2032 小时前
Spring Boot基础应用开发与部署
java·spring boot·后端