Reactor 深度解析:响应式编程的「核反应堆」是如何工作的?

267 阅读4分钟

Reactor 深度解析:响应式编程的「核反应堆」是如何工作的?


一、Reactor 是谁?为什么 Spring 选它?

Reactor 是一个基于 Reactive Streams 规范 的 JVM 响应式库,相当于响应式世界的「涡轮增压引擎」。Spring WebFlux 默认集成 Reactor,而不是 RxJava,因为:

  • 专为 Java 8+ 优化:深度整合 CompletableFutureStream 的语法糖。
  • 背压原生支持:从设计之初就遵循 Reactive Streams 标准。
  • Spring 亲儿子:Pivotal(Spring 母公司)亲自维护,生态无缝兼容。

💡 类比

  • RxJava 是「瑞士军刀」,功能多但复杂;
  • Reactor 是「精工匕首」,专注高频核心场景。

二、核心模型:Mono 与 Flux 的「量子纠缠」

1. Mono:单身贵族的哲学
  • 定义:0 或 1 个元素的异步序列。
  • 经典场景:查询单个数据库记录、HTTP 请求响应。
Mono<User> userMono = userRepository.findById(1);
// 相当于 Optional<User>,但带异步超能力
2. Flux:后宫团的流量密码
  • 定义:0 到 N 个元素的异步序列。
  • 经典场景:消息流、批量查询、SSE(Server-Sent Events)。
Flux<Order> orders = orderRepository.findAll();
// 相当于 List<Order> 的异步动态版

量子纠缠现象
通过 flatMapconcatMap 等操作符,Mono 和 Flux 可以互相转换:

Flux<User> users = Flux.just(1, 2, 3)
    .flatMap(id -> userRepository.findById(id)); // Mono → Flux

三、线程模型:Schedulers 的「影分身之术」

Reactor 的异步魔法依赖于 Scheduler(调度器),关键角色:

调度器类型作用适用场景
Schedulers.immediate()当前线程执行(无切换)快速无阻塞操作
Schedulers.single()全局单线程池低并发轻任务
Schedulers.parallel()固定大小线程池(CPU 核数)CPU 密集型计算
Schedulers.boundedElastic()弹性线程池(带队列限制)阻塞操作(如 JDBC、旧式 API)
Schedulers.fromExecutor()自定义线程池集成现有基础设施

操作符搭配使用

Flux.range(1, 10)
    .publishOn(Schedulers.parallel()) // 下游操作切换到并行线程池
    .map(i -> i * 2)                 // 在 parallel 线程执行
    .subscribeOn(Schedulers.boundedElastic()) // 源头执行线程
    .subscribe();

⚠️ 陷阱
subscribeOn 只影响源头,publishOn 影响下游——顺序错了可能白切换!


四、核心操作符:Reactor 的「忍术大全」

1. 创建流(Creation Operators)
Mono.just("Hello")                    // 静态值
Mono.fromFuture(CompletableFuture.supplyAsync(() -> "Hi")) // 兼容 Future
Flux.interval(Duration.ofSeconds(1))  // 定时发射(0,1,2...)
Flux.fromStream(IntStream.range(1,5).boxed()) // 懒加载 Stream
2. 转换流(Transformation Operators)
Flux.just("apple", "banana")
    .map(String::toUpperCase)        // 同步映射
    .flatMap(s -> Mono.just(s + "!")) // 异步展开(Mono→Flux)
    .concatMap(s -> externalApiCall(s)) // 保证顺序的异步展开
3. 过滤控制(Filtering & Control)
Flux.range(1, 100)
    .filter(i -> i % 2 == 0)         // 过滤偶数
    .take(5)                         // 只取前5个
    .timeout(Duration.ofSeconds(3))  // 超时熔断
4. 错误处理(Error Handling)
Flux.just(1, 0, 2)
    .map(i -> 10 / i)                // 会除零异常
    .onErrorResume(e -> Flux.just(-1)) // 捕获后返回备用值
    .retryWhen(Retry.backoff(3, Duration.ofSeconds(1))) // 指数退避重试

五、高级特性:Reactor 的「奥义技」

1. 热发布 vs 冷发布
  • 冷发布(Cold Publisher):每次订阅重新生成数据(如数据库查询)。
    Flux<String> cold = Flux.defer(() -> Flux.fromIterable(fetchData()));
    
  • 热发布(Hot Publisher):数据共享给所有订阅者(如股票行情)。
    ConnectableFlux<Integer> hot = Flux.range(1, 10).publish();
    hot.connect(); // 手动启动数据流
    
2. Context:跨阶段的「暗黑数据」

Reactor 的 Context 可以隐式传递数据(替代 ThreadLocal):

Mono<String> result = Mono.deferContextual(ctx -> {
    String traceId = ctx.get("traceId");
    return Mono.just("Trace: " + traceId);
}).contextWrite(Context.of("traceId", "123")); // 注入上下文
3. 测试:Reactor Test 工具包
StepVerifier.create(Flux.just(1, 2, 3).expectNext(1, 2, 3).verifyComplete();
StepVerifier.withVirtualTime(() -> Flux.interval(Duration.ofDays(1)))
            .thenAwait(Duration.ofDays(1)) // 时间快进
            .expectNext(0L)
            .verifyComplete();

六、性能调优:从「拖拉机」到「超跑」

1. 避免阻塞操作
// 错误示范:阻塞破坏响应式链
Flux.range(1, 10)
    .map(i -> {
        Thread.sleep(1000); // 阻塞炸弹!
        return i;
    });

// 正确姿势:用 boundedElastic 隔离阻塞
Flux.range(1, 10)
    .publishOn(Schedulers.boundedElastic())
    .map(i -> blockingIoCall(i));
2. 合理配置线程池
// 自定义弹性线程池(限制最大线程数)
Scheduler customScheduler = Schedulers.newBoundedElastic(
    10,              // 最大线程数
    100,             // 任务队列容量
    "custom-pool");

// 使用后记得关闭(防止内存泄漏)
customScheduler.dispose();
3. 监控与诊断
  • 启用调试模式
    Hooks.onOperatorDebug(); // 打印详细操作符日志
    
  • Micrometer 集成
    Flux.just(1, 2, 3)
        .name("myFlux")      // 命名指标
        .metrics()          // 暴露给 Prometheus
        .subscribe();
    

七、面试直通车:Reactor 高频考题

Q1:mapflatMap 的区别?

  • map:同步转换,输入 T 输出 R(1:1)。
  • flatMap:异步转换,输入 T 输出 Publisher<R>(1:N,可能乱序)。

Q2:subscribeOnpublishOn 的区别?

  • subscribeOn:控制整个链的订阅执行线程(源头生效)。
  • publishOn:控制下游操作的执行线程(位置敏感)。

Q3:如何实现响应式超时重试?

Flux.just("data")
    .timeout(Duration.ofSeconds(3))
    .retryWhen(Retry.fixedDelay(2, Duration.ofSeconds(1)));

八、总结:Reactor 的「终极奥义」

  • 核心原则:异步非阻塞 + 背压控制 = 高吞吐低延迟。
  • 黄金搭配
    • Mono/Flux + Schedulers + WebClient = 服务间调用
    • R2DBC + Reactive Repository = 数据库交互
  • 避坑箴言
    • 不要阻塞响应式链(否则性能反降)。
    • 理解冷热发布区别(避免数据意外共享)。
    • 永远考虑背压(除非你想半夜被告警电话叫醒)。

🚀 Reactor 不是银弹,但用好了就是「分布式系统的光剑」!