章节摘要:建立响应式编程的认知坐标——从「时间是连续流」的本体论,到 Reactive Manifesto 四支柱,再到 FRP、Rx 与 Reactive Streams 的工业标准化脉络。
Flow,关键里程碑有哪些?本章是全书的认知地基。后续章节讲 Publisher/Subscriber 契约、背压、框架选型,全部建立在「响应式到底在重新定义什么」之上。如果只看 API 不看范式,你会得到一堆「能用但不知道为什么」的代码——而这正是第六章要警示的反模式。
| 节 | 主题 | 读完应能 |
|---|---|---|
| 1.1 | 核心定义与哲学 | 对比命令式赋值与 combineLatest 因果链 |
| 1.2 | Reactive Manifesto 四支柱 | 用四支柱评审一个网关设计 |
| 1.3 | 发展历程与里程碑 | 列出 Rx→Manifesto→Reactive Streams 时间线 |
| 维度 | 命令式 | 异步回调 | 响应式 |
|---|---|---|---|
| 时间观 | 离散瞬时 | 事件点 | 连续流 + 生命周期 |
| 组合 | 嵌套调用 | 回调地狱 | 声明式算子链 |
| 流量控制 | 线程池上限 | 队列缓冲 | 背压 request(n) |
| 错误 | try/catch 中断 | 分散处理 | onError 信号通道 |
| 取消 | 无(不可中断) | 依赖 API 约定 | cancel() 一等公民 |
这张表是三节内容的总纲。时间观回答「程序如何看待时间」——命令式假设计算瞬间完成,响应式承认事件在时间轴上持续发生;组合回答「多个任务如何拼装」——嵌套调用形成树,算子链形成图;流量控制回答「生产快于消费怎么办」——线程池是粗粒度缓冲,request(n) 是细粒度协商;错误与取消则决定系统面对失败与资源回收时的行为。记住这张表,1.1 解释为什么,1.2 解释怎么评审,1.3 解释从哪里来。
为了让对照不流于表格,下面给出同一业务——「读取用户、加积分、查风控、合并返回」——在三种范式下的写法差异。
命令式(阻塞串行):
User u = userDao.findById(id); // 线程挂起等待 DB u.setPoints(u.getPoints() + 10); userDao.update(u); // 再次等待 Risk risk = riskClient.check(u); // 第三次等待 return new Result(u, risk);
异步回调(非阻塞但嵌套):
userDao.findByIdAsync(id, u -> { u.setPoints(u.getPoints() + 10); userDao.updateAsync(u, () -> { riskClient.checkAsync(u, risk -> callback.accept(new Result(u, risk))); }); });
响应式(声明式流):
userRepo.findById(id) // Flux<Mono<User>> .map(u -> { u.setPoints(u.getPoints() + 10); return u; }) .flatMap(u -> userRepo.update(u).thenReturn(u)) .flatMap(u -> riskClient.check(u).map(r -> new Result(u, r)));
三者最终结果相同,但结构差异明显:命令式的等待是隐式的(线程被占用),回调的嵌套是硬编码的(组合顺序写死),响应式把每个异步步骤变成链上的一个节点——换顺序、加分支、加背压、加取消都是改链而不是改嵌套。这就是第一章要建立的第一个直觉。
阅读本章时请始终带着一个问题:「变化」在一个系统里到底以什么形式存在? 命令式把变化藏在内存赋值里,回调把变化藏在函数调用里,响应式把变化显式建模为流上的信号。1.1 会论证为什么这个显式化值得付出心智成本,1.2 会给出系统层面的评审框架,1.3 会说明这场迁徙经历了怎样的三十年。
读 1.1 时留意 Netflix 每分钟超 10 亿次事件、Uber 50ms 调度匹配等 SOURCE 场景——它们说明「变化」已是默认负载,而非边缘 case。