响应式编程:Reactive Streams、Project Reactor、Flow与Reactive的对比

响应式编程,说白了就是处理数据流的一种编程范式。你想想看,传统编程里我们写list.map{...},数据是现成的,一次性处理完。但现实世界的数据是源源不断来的——用户点击、网络请求、传感器数据……这些东西什么时候来、来多少,你根本控制不了。

我刚开始接触响应式编程时,也觉得这东西有点玄乎。直到有一次做一个实时股票行情系统,传统方式下数据一多就OOM,线程池炸了,服务直接挂掉。嗯,从那以后我才真正理解了响应式的价值。

Reactive Streams:一切的基础

Reactive Streams 不是一个框架,而是一套规范。它定义了四个核心接口:PublisherSubscriberSubscriptionProcessor。说白了,就是规定了「生产者-消费者」之间怎么协作。

它的核心思想是背压(Backpressure)。什么意思?就是消费者告诉生产者:「你慢点发,我处理不过来」。这在传统回调式编程里很难做到——数据一股脑推过来,你接不住就丢包。

核心接口一览:

  • Publisher:发布数据的人
  • Subscriber:订阅数据的人
  • Subscription:连接两者的「管道」,控制流量
  • Processor:既是发布者又是订阅者,可以做数据转换

我在项目中遇到过一个问题:用传统回调方式做消息队列消费,消费者一重启,积压的消息瞬间涌入,直接打爆内存。后来改用 Reactive Streams 规范实现,通过 request(n) 控制每次拉取数量,问题就解决了。

Project Reactor:JVM 上的响应式王者

Project Reactor 是 Reactive Streams 规范在 JVM 上的实现,也是 Spring WebFlux 的底层依赖。它提供了两个核心类型:Mono(0-1 个元素)和 Flux(0-N 个元素)。

我个人习惯把 Mono 理解为「异步的 Optional」,把 Flux 理解为「异步的 Stream」。它们都支持丰富的操作符——mapflatMapfilterzip 等等。

// 一个简单的 Reactor 示例
Flux<String> flux = Flux.just("A", "B", "C", "D")
    .map(String::toLowerCase)
    .filter(s -> !s.equals("b"))
    .doOnNext(System.out::println);

flux.subscribe();

你可能会问:这和 Kotlin 的 Sequence 有什么区别?区别大了。Reactor 的操作符是异步非阻塞的,而 Sequence 是同步阻塞的。说白了,Reactor 不会因为一个操作慢就卡住整个线程。

避坑指南:我曾经在 Reactor 的 flatMap 里做数据库查询,结果并发数没控制好,直接把连接池打满了。后来加了 flatMap(service::query, 5) 限制并发数,才稳定下来。

Kotlin Flow:协程时代的响应式

Kotlin Flow 是 Kotlin 协程生态的一部分。它和 Reactor 最大的区别在于:Flow 是挂起函数驱动的,而 Reactor 是基于回调的。

说白了,Flow 让你用同步的写法写异步代码。你不需要理解什么 subscribeonNext,直接用 collect 就能拿到数据。

// Kotlin Flow 示例
fun getData(): Flow<String> = flow {
    emit("A")
    emit("B")
    emit("C")
}

// 消费端
scope.launch {
    getData().collect { value ->
        println(value)
    }
}

Flow 也支持背压,但它的实现方式更优雅——通过 bufferconflatecollectLatest 等操作符来控制。我记得有一次做搜索框的自动补全,用户输入太快,请求发出去结果还没回来。用 debounce(300) + flatMapLatest 就完美解决了。

注意:Flow 的 collect 是一个挂起函数,必须在协程作用域内调用。如果你在非协程环境里用 Flow,需要先启动一个协程。

三者的对比:一张图说清楚

我画了一张对比图,帮你快速理解三者的关系:

响应式编程三剑客对比 Reactive Streams(规范层) 定义 Publisher / Subscriber / Subscription / Processor 四大接口 核心:背压(Backpressure)机制 Project Reactor JVM 原生实现 Mono / Flux 两大类型 Spring WebFlux 底层依赖 Kotlin Flow Kotlin 协程生态 挂起函数驱动 Flow / StateFlow / SharedFlow 应用场景 实时数据流 · 消息队列 · WebFlux API · 搜索自动补全 · 传感器数据

如何选择?我的建议

这个问题没有标准答案,但我可以分享一些经验:

  • 如果你在用 Spring Boot,尤其是 WebFlux,那 Reactor 是自然选择。它和 Spring 生态集成得最好。
  • 如果你在用 Kotlin + 协程,Flow 更香。代码更简洁,学习成本更低。
  • 如果你需要跨平台(比如 KMP),Flow 是唯一选择。Reactor 只支持 JVM。
  • 如果你在做底层框架,建议直接基于 Reactive Streams 规范实现,保持兼容性。

我的个人经验:在一个混合项目中,我同时用了 Reactor 和 Flow。后端服务用 Reactor 做 WebFlux,Android 客户端用 Flow 做 UI 状态管理。两者通过 gRPC 通信,各司其职,互不干扰。

性能对比:别被数字迷惑

很多人喜欢比性能,但说实话,在大多数业务场景下,两者的性能差异可以忽略不计。真正影响性能的是你的操作符使用方式线程调度策略

维度 Project Reactor Kotlin Flow
调度模型 Scheduler(线程池) CoroutineDispatcher(协程调度器)
背压方式 request(n) 显式控制 buffer / conflate / collectLatest
错误处理 onErrorResume / onErrorReturn catch / retry
学习曲线 中等(需要理解回调) 较低(挂起函数更直观)
调试难度 较高(回调栈深) 较低(协程栈更清晰)

你想想看,如果你的业务逻辑里就几个 mapfilter,用哪个都一样。但如果你要做复杂的流式处理——比如窗口聚合、时间序列分析——那 Reactor 的操作符更丰富一些。

避坑指南:我曾经在 Flow 里用 flowOn(Dispatchers.IO) 切换线程,结果忘了 collect 还是在主线程,导致 UI 卡顿。记住:flowOn 只影响上游,不影响下游。

总结

响应式编程不是银弹,但它确实解决了很多实际问题。Reactive Streams 是规范,Reactor 和 Flow 是两种优秀的实现。选哪个,取决于你的技术栈和团队习惯。

我个人更倾向于 Kotlin Flow,因为它和协程配合得天衣无缝。但如果你在维护一个 Spring 项目,Reactor 也是很好的选择。最重要的是理解背后的思想——数据流、背压、异步非阻塞——这些才是真正有价值的东西。


公众号:蓝海资料掘金营,微信deep3321