Reactive Streams 出现的原因

了解异步的出现,可以先看 什么叫 IO就绪

  1. 异步、异步库为什么会出现?(你现在想问的:根源,在 Reactive Streams 之前很久就存在)
  2. Reactive Streams 规范为什么出现?(刚才我讲的:异步库多了、各自一套,缺统一标准、缺标准化背压)

一句话前置:异步不是为了背压而生;背压,是异步普及之后,暴露出来的一个棘手副作用问题。

下面从头梳理:为什么人类会搞异步、异步库。

一、最原始:同步模型的瓶颈(同步阻塞)

传统 Java 服务模型:一个请求绑定一个线程(Tomcat BIO 模型)

复制代码
线程1:接收请求 → 调用JDBC(阻塞,等DB返回)→ 返回响应

当线程卡在 DB、RPC、文件读写这种IO等待的时候:

✅ CPU 是空闲的

❌ 线程被占死,不能处理别的请求

线程是昂贵资源:

  • JVM 线程占用栈内存(默认 1MB 左右)
  • 操作系统内核调度线程有开销
  • 线程数量不能无限开,开到几千之后,上下文切换爆炸、OS 负载飙升

核心矛盾:IO 等待浪费线程资源。

同步模型里,等待IO=占着线程摸鱼。

在流量上涨的时候,很快线程池耗尽,新请求排队、超时,服务扛不住。

👉 解决方案思路:不要让线程卡在IO等待上。IO等待的时候,线程释放,去干别的活。

这个思路,就是异步。

二、异步的底层基础:操作系统提供的能力

异步不是Java发明的,是操作系统内核能力:

  • Linux:select / poll / epoll
  • Windows:IOCP
    也就是常说的 非阻塞IO、IO多路复用

原理:

线程不再死等IO结果,而是告诉内核:帮我盯着这个socket,数据来了通知我。

线程立刻返回,可以去处理别的连接、别的任务。

等内核检测到数据就绪,再回调回来处理。

这是异步最底层来源:榨干IO等待间隙,用少量线程支撑大量IO连接。

典型场景:网关、IM、长连接、高并发HTTP服务,成千上万连接,但大部分时间都在等待。

同步:线程等IO(线程绑定IO)

异步:IO就绪了再通知线程(IO就绪才占用线程)

三、早期异步编程形态,以及痛点(异步库诞生的原因)

有了操作系统非阻塞IO之后,语言/框架要封装它,于是出现各种异步方案:

1. 原生回调(Callback)

伪代码:

java 复制代码
readFile(path, result -> {
    // IO完成之后才进来
    sendHttp(result, resp -> {
        saveDb(resp, () -> {
            // 嵌套,回调地狱
        })
    })
})

优点:线程不阻塞,资源利用率高。

缺点:

  • 回调地狱,嵌套很深,可读性崩盘
  • 异常传递很难:内层异常怎么传到最外层?
  • 取消、超时、重试、组合多个IO,代码极难写
  • 顺序、并发、分支编排非常麻烦

2. 然后就出现了第一代异步库:为了简化回调

不同团队各自封装:

  • Guava ListenableFuture
  • Java 8 CompletableFuture(JDK官方异步)
  • Netty(底层epoll,回调式异步网络库)
  • 早期RxJava 1.x(为了流式事件编排)

这些异步库诞生的目的:

把零散的回调封装起来,提供链式编排、异常传递、组合能力,解决回调地狱。

⚠️ 注意:这一批异步库,目标是解决「回调难写、IO线程资源浪费」,根本没有考虑背压。

早期异步推送模型,大多是纯 Push:上游有数据就拼命往下发。

当遇到上游生产 > 下游消费,数据无处安放,只能堆在无界队列 → OOM。

这个坑,是异步流行之后,才慢慢大面积暴露出来的。

举个例子:Netty 读一个大文件流,上游源源不断读出字节,下游处理慢。如果没有限流,缓冲区无限涨,直接OOM。

四、时间线重新排一遍(非常关键,纠正之前顺序)

  1. 硬件 & OS 层面:存在IO等待问题
    网络、磁盘IO远慢于CPU,同步线程模型浪费资源。
  2. 操作系统提供 epoll / IOCP 非阻塞多路复用 → 异步能力底座
  3. 出现原生回调写法,但是难写、难维护。
  4. 各种异步库雨后春笋出现(Guava、Netty、RxJava1)
    ✅ 目标:简化异步回调、高效利用少量线程承载大量IO
    ❌ 此时:没有统一标准,很多库没有背压机制
  5. 大规模落地后,暴露出新问题:异步推送容易OOM(生产者快于消费者)
    各家自己写限流、队列、丢弃策略,实现五花八门,库与库之间流不能互通。
  6. → 于是才催生了 Reactive Streams 规范(2015)
    目标两件事:
    • 定义一套统一接口,不同异步流式库可以互通
    • 内置标准化背压协议(request(n)),解决异步push模型下的OOM问题

重点一句话区分两层目标:

✅ 异步、异步库诞生:为了解决IO等待浪费线程,提升并发承载能力,同时解决回调地狱。

✅ Reactive Streams 规范诞生:异步库已经很多了,为了统一契约、标准化背压。

五、再对应回你一直在学的 Reactor / Mono/Flux

Project Reactor 本身就是一个异步编排库,建立在 Reactive Streams 规范之上:

  1. 它首先是异步库:用来高效处理大量IO(HTTP、DB、消息),用少量线程扛高并发,规避BIO线程浪费;
  2. 其次,它遵守 Reactive Streams,自带背压契约,解决异步push的OOM风险;

同时再次呼应你上一个问题:

Mono 不是为背压而生,Mono/Flux 是Reactor为了区分「单结果异步任务」和「持续流式异步任务」设计的类型。

六、补充两个高频混淆点

  1. 异步 ≠ 非阻塞

    异步是编程模型;非阻塞是IO模型。

    你之前写的 Mono.fromCallable + subscribeOn(boundedElastic) 是:异步编程模型 + 阻塞IO(JDBC) ,只是把阻塞放到独立线程池隔离。

    WebFlux 的 WebClient 是:异步编程模型 + 非阻塞IO(Netty epoll),是原生最优形态。

  2. 同步也可以限流,异步才需要背压

    同步调用天然是"一问一答",调用者等返回,自带拉取限速;

    一旦解耦变成异步推送,生产者和消费者不在同一个调用栈,这个天然约束消失,才需要背压协议来重建限速。

极简时间线浓缩版(方便放进md)

  1. 痛点:BIO 一个请求一个线程,IO等待时线程空耗,并发上不去
  2. OS 提供 epoll/IOCP:允许线程不卡死在IO上
  3. 原生回调:难写,回调地狱,异常、编排、取消很难处理
  4. 各种异步库出现(Guava、Netty、RxJava1):封装回调,方便异步任务编排,提升并发能力
  5. 新痛点:异步push模型没有统一控速,上游疯狂发数据,下游处理慢,队列无限膨胀OOM;并且各个异步流式API互不兼容
  6. 推出 Reactive Streams 规范:统一接口 + 标准化背压契约

如果你愿意,我可以把这一段替换/追加到md文档里,调整章节顺序,逻辑变成:

    1. 为什么出现异步、异步库(本章)
    1. 异步带来的新问题:背压是什么,生产者消费者
    1. Reactive Streams 规范(接口、时序、规则)
    1. Mono / Flux 和规范的关系
    1. 误区汇总

需要我直接输出更新后的完整md文本吗?

相关推荐
叼烟扛炮1 小时前
Linux 学习笔记(九):自动化构建神器 Make 与 Makefile
linux·运维·服务器
鬼手点金1 小时前
单轮对话:最简单的 API 调用
java·服务器·前端·javascript·react.js·servlet·tomcat
箓维1 小时前
线程池&&线程安全与可重入
linux·网络·安全
码农1001号1 小时前
emqx5.8开源版WSS连接
运维·服务器
程序员-Benothing1 小时前
Linux端口与连接排查:ss、lsof、nc、telnet、curl
linux·运维·服务器
zxanz12 小时前
免费 SSL 证书的隐性成本:续期压力、稳定性与商业风险评估
网络·网络协议·ssl
weixin_417396832 小时前
酒店IPTV怎么和PMS对接?能实现哪些联动
网络·技术·网络运维·酒店iptv·组播排查
zzu123zsw2 小时前
面对多 RPC 扇入超时崩溃:深入 asyncTool 核心 DAG 编排与线程池隔离防护
java·网络·spring boot·网络协议·rpc·并发编程
桌管小李2 小时前
终端安全一体化实战:AI 审计、行为分析、零信任、防拍照与远程运维全解析
运维·服务器·网络·人工智能·windows·安全·macos