规则全集:43 条,分四组
Reactive Streams 1.0.4 的规范正文一共 43 条,分组如下:
| 组 | 规则编号 | 条数 | 角色 |
|---|---|---|---|
| §1 Publisher | 1.1–1.11 | 11 | 产生并发送信号的一方 |
| §2 Subscriber | 2.1–2.13 | 13 | 请求并消费信号的一方 |
| §3 Subscription | 3.1–3.17 | 17 | 连接 Publisher 与 Subscriber 的合同 |
| §4 Processor | 4.1–4.2 | 2 | 同时是 Publisher 和 Subscriber |
两个常见错误先纠正:
- §2 是 Subscriber,§3 是 Subscription。旧版把两者对调,导致
request(0)、cancel之后的行为被归到了错误的角色上。 - 不是 15 条,也不存在「9 条与背压直接相关」这种官方口径。43 条里很多规则都在约束背压链路,硬切一个「9 条」既没有出处,也会漏掉 3.8、3.9、3.17 这些关键条款。
本附录不要求你背下 43 条。先抓住 request → demand → onNext 这条主链,再按角色查表。
背压主链
Subscriber.onSubscribe(subscription)
│
├── subscription.request(n) §2.1
│ │
│ └── Subscription 累加 demand §3.8
│ │
│ └── Publisher 最多发 demand 个 onNext §1.1
│
└── subscription.cancel() §3.6 / §3.7
│
└── Publisher 最终停止发信号 §1.8 / §3.12
这条链上有五个关键点:
| 问题 | 规则 | 结论 |
|---|---|---|
| 谁决定要多少元素 | §2.1 | Subscriber 通过 request(n) 表达需求 |
request 是覆盖还是累加 | §3.8 | 累加,不是覆盖 |
| 能不能多发 | §1.1 | 任意时刻 onNext 总数 ≤ 累计 request |
| 能不能少发 | §1.2 | 可以,发完剩余部分后 onComplete 或 onError |
request(0) 怎么办 | §3.9 | 必须 onError(IllegalArgumentException) |
Flux.range 这类同步源之所以“天然有背压”,不是因为它特殊,而是因为它把 §3.8 的累加和 §1.1 的上限实现对了。手写 Publisher 出错,通常也错在这两条。
§1 Publisher:11 条
| 规则 | 要点 | 与背压的关系 | TCK 覆盖 |
|---|---|---|---|
| 1.1 | 任意时刻 onNext 总数 ≤ 该 Subscriber 累计请求数 | 背压的定义本身 | required_spec101... |
| 1.2 | 可以少发,然后 onComplete / onError | 需求是上限,不是必须交满 | required_spec102... |
| 1.3 | onSubscribe、onNext、onError、onComplete 必须串行 | 并发 request 不能导致信号交错 | stochastic_spec103... |
| 1.4 | Publisher 失败必须发 onError | 不能吞掉生产侧异常 | optional_spec104... |
| 1.5 | 有限流正常结束必须发 onComplete | 终止信号与 demand 无关 | required_spec105... |
| 1.6 | 发出终止信号后,Subscription 视为已取消 | 终止后不再有“未结算需求” | untested_spec106... |
| 1.7 | 终止后不得再发任何信号 | 防止 cancel/终止竞争下重复发 | required_spec107...(仅 onComplete) |
| 1.8 | cancel 后 Subscriber 必须最终停止收到信号 | “最终”允许异步传播延迟 | 由 312/313 间接验证 |
| 1.9 | subscribe 必须先调 onSubscribe、正常返回;null 抛 NPE;拒绝订阅只能走 onError | 拒绝也不能绕过 onSubscribe | required_spec109... 三个 |
| 1.10 | subscribe 可多次调用,但每次必须是不同的 Subscriber | 同一 Subscriber 只能订阅一次 | untested_spec110... |
| 1.11 | 可支持多个 Subscriber,由实现决定单播还是多播 | 多播时每个订阅各自结算 demand | optional_spec111... |
几个容易误解的点:
- 1.1 不是「发了 N 个就一定要等下一个 request」。如果一次
request(10),Publisher 可以连续发 10 个,不必每个元素之间插入一次 request。 - 1.6 与 1.7 是两道不同的门。1.6 说终止后 Subscription 失效,1.7 说终止后不能再有信号;前者约束状态,后者约束信号。
- 1.9 的「返回正常」指不向调用方抛异常。内部错误必须转成
onError,而不是让subscribe抛出。
§2 Subscriber:13 条
| 规则 | 要点 | 与背压的关系 | TCK 覆盖 |
|---|---|---|---|
| 2.1 | 必须通过 request(n) 表达需求才能收到 onNext | 背压的起点 | required_spec201... |
| 2.2 | 若处理会阻塞 Publisher,建议异步派发(RECOMMENDED) | 不阻塞生产侧 | untested_spec202... |
| 2.3 | onComplete / onError 内不得调用 Subscription 或 Publisher 的方法 | 防止终止阶段的循环与竞态 | required_spec203... 两个 |
| 2.4 | 收到终止信号后必须把 Subscription 视为已取消 | 终止后不再 request | untested_spec204... |
| 2.5 | 已有活动 Subscription 时又收到 onSubscribe,必须取消新来的这个 | 保留旧订阅,防止两个 Publisher 同时驱动一个 Subscriber | required_spec205... |
| 2.6 | Subscription 不再需要时必须 cancel() | 及时释放资源 | untested_spec206... |
| 2.7 | 对 request/cancel 的调用必须串行 | 并发 request 由 Subscriber 自己保证顺序 | untested_spec207... |
| 2.8 | cancel 之后仍可能收到已请求的 onNext | cancel 不是同步切断 | 白盒 required_spec208...;黑盒 untested_spec208_blackbox... |
| 2.9 | 必须能处理“没有 request 也收到 onComplete” | 完成信号不受 demand 约束 | required_spec209... 两个 |
| 2.10 | 必须能处理“没有 request 也收到 onError” | 错误信号不受 demand 约束 | required_spec210... 两个 |
| 2.11 | 对信号方法的调用必须 happen-before 对应处理逻辑 | 异步处理时自己保证可见性 | untested_spec211... |
| 2.12 | 同一个 Subscriber 的 onSubscribe 最多调用一次(对象相等) | 一个 Subscriber 只绑定一个 Subscription | untested_spec212... |
| 2.13 | 信号方法必须正常返回;参数为 null 时抛 NPE;其他失败只能 cancel | Subscriber 抛异常不能反向污染 Publisher | required_spec213... 三个 |
实践中最常踩的是 2.8:cancel() 返回不代表立刻安全。如果 cancel 之前已经 request(100),Publisher 可能还有元素在路上。Subscriber 的 onNext 必须能容忍“取消后仍被调用几次”,不能一 cancel 就把状态清空到必然 NPE。
§3 Subscription:17 条
| 规则 | 要点 | 与背压的关系 | TCK 覆盖 |
|---|---|---|---|
| 3.1 | request/cancel 只能在对应 Subscriber 的上下文里调用 | Subscription 是点对点合同 | untested_spec301... |
| 3.2 | 必须允许在 onNext / onSubscribe 内同步调用 request | 支持同步 Publisher | required_spec302... |
| 3.3 | 必须给 request 与 onNext 的同步递归设上界 | 防止栈溢出 | required_spec303... |
| 3.4 | request 应及时返回,不做重计算(SHOULD) | 不阻塞调用线程 | untested_spec304... |
| 3.5 | cancel 不得在调用线程同步执行重计算 | 取消不能被重计算拖住 | untested_spec305... |
| 3.6 | cancel 后继续 request 必须是 NOP | 取消后需求不再生效 | required_spec306... |
| 3.7 | cancel 后继续 cancel 必须是 NOP | 幂等 | required_spec307... |
| 3.8 | 未取消时,request(n) 必须累加 n 个待生产元素 | 需求是加法 | required_spec308... |
| 3.9 | request(n ≤ 0) 必须 onError(IllegalArgumentException) | 非法需求不能静默 | required_spec309... 两个 |
| 3.10 | request 可以同步调用 onNext | 同步 Publisher 合法 | untested_spec310... |
| 3.11 | request 可以同步调用 onComplete / onError | 同步终止合法 | untested_spec311... |
| 3.12 | cancel 必须让 Publisher 最终停止发信号,但不要求立刻生效 | 取消是“请求停止”,不是“立即停止” | required_spec312... |
| 3.13 | cancel 必须让 Publisher 最终丢弃对 Subscriber 的引用 | 防内存泄漏 | required_spec313... |
| 3.14 | 无其他订阅时,cancel 可让有状态 Publisher 进入 shutdown | 允许 Publisher 释放资源 | untested_spec314... |
| 3.15 | cancel 必须正常返回 | 不得抛异常 | untested_spec315... |
| 3.16 | request 必须正常返回 | 不得抛异常(非法参数走 3.9 的 onError) | 无独立 required 测试 |
| 3.17 | 必须支持无限次 request,demand 上限为 Long.MAX_VALUE | 无界需求用 MAX 表示 | required_spec317... 三个 |
3.8 与 3.9 是手写 Subscription 最容易错的地方。一个正确的累加应该是饱和加法:
void request(long n) {
if (n <= 0) {
subscriber.onError(new IllegalArgumentException("§3.9: " + n));
return;
}
for (;;) {
long current = requested.get();
long next = current + n;
if (next < 0) next = Long.MAX_VALUE; // 饱和,不能溢出成负数
if (requested.compareAndSet(current, next)) return;
}
}
写成 requested.addAndGet(n) 会在累计值接近 Long.MAX_VALUE 时回绕成负数,下一次判断 requested > 0 就失效,表现为“订阅突然不再发元素”。
§4 Processor:2 条
| 规则 | 要点 |
|---|---|
| 4.1 | Processor 同时是 Subscriber 和 Publisher,必须同时遵守两套契约 |
| 4.2 | 可以选择恢复 onError;一旦恢复,必须把上游 Subscription 视为已取消,否则必须立即把 onError 传播给下游 |
4.1 的含义不是“实现两个接口就自动合规”,而是两条链都要各自满足:作为 Subscriber 要正确 request,作为 Publisher 要正确结算下游 demand。PublishProcessor 在 TCK 的 IdentityProcessorVerification 中出现大量失败,根源通常就在这里——它把上游和下游当成了同一个 demand 池。
4.2 只在 Processor 主动“吞掉错误并继续”时生效。普通转发型 Processor 不应该恢复错误,直接把 onError 往下传即可。
Reactor 的背压操作符
| 操作符 | 行为 | 注意 |
|---|---|---|
onBackpressureBuffer(int) | 有界缓冲,溢出默认 ERROR | 溢出时发 OverflowException,流终止 |
onBackpressureBuffer(int, Consumer) | 有界缓冲 + 溢出回调 | 默认策略仍是 ERROR,回调不是“丢弃钩子” |
onBackpressureBuffer(int, Consumer, BufferOverflowStrategy) | 显式选择 DROP_LATEST / DROP_OLDEST / ERROR | 要让流继续,必须用三参重载 |
onBackpressureDrop(Consumer) | 无队列,需求为零时丢弃 | 丢弃计数走回调 |
onBackpressureLatest() | 只保留最新一个 | 被覆盖的旧值用 doOnDiscard 统计 |
onBackpressureError() | 需求不足直接报错 | 适合“绝不能丢”的场景 |
limitRate(n) | 按批向上游 request | 减少 request 往返,不改变总需求 |
limitRequest(n) | 把上游总需求限制为 n | 不是“只取前 n 个”,下游仍要自己停止 |
publishOn(scheduler, prefetch) | 切换下游执行线程 | prefetch 越大,越容易掩盖背压 |
这些 onBackpressure* 操作符有一个共同点:它们会向上游请求 Long.MAX_VALUE,把上游变成“推”模型,然后由自己在中间结算下游需求。它们不是“把背压传回上游”的工具,而是“上游不会背压时,在中间做有损/有界处理”的工具。
两参数重载不是“溢出即丢弃”
flux.onBackpressureBuffer(50, dropped::incrementAndGet)
这里的 dropped 是溢出回调,默认策略是 BufferOverflowStrategy.ERROR。缓冲区满时它会调用一次回调,然后以 OverflowException 终止整条流,后面的元素不会再产生。要丢弃并继续,必须写三参:
flux.onBackpressureBuffer(
50,
dropped::incrementAndGet,
BufferOverflowStrategy.DROP_LATEST);
实测(Flux.range(1, 1000) + publishOn(..., 1) + 每次只 request 1):
error: OverflowException: The receiver is overrun by more signals than expected
delivered=10 overflowCallback=1
容量 10,交付 10 个后溢出,回调触发 1 次,流终止。这个例子同时说明:onBackpressureBuffer 会向上游请求无界需求,Flux.range 的 1000 个元素会一次性推进缓冲区;如果没有 publishOn 这样的异步边界,下游在 onNext 里同步补充 request,缓冲反而可能永远不满,测不出溢出。
delayElements 不是背压缓冲边界
旧版附录给过这样的例子:
Flux.interval(Duration.ofMillis(10))
.onBackpressureDrop(i -> metrics.counter("dropped").increment())
.delayElements(Duration.ofMillis(100))
.subscribe(i -> dashboard.update(i));
看起来是“10ms 生产、100ms 消费,所以中间会丢弃”。但 delayElements(d) 在 Reactor 3.8 里的实现是 delayUntil(x -> Mono.delay(d)),它一次只向上游请求一个元素。丢弃确实会发生,但原因是它自己的 100ms 节流,不是下游消费者慢。实测对比:
fast consumer (no sleep) delivered=3 drops=27
slow consumer (80ms sleep) delivered=2 drops=28
消费者睡不睡几乎不影响结果,因为瓶颈是 delayElements 的 100ms,而不是消费者的 80ms。用这条链演示“消费者驱动背压”会得出错误结论。
要观察真正的下游需求,应该显式控制 request:
Flux.interval(Duration.ofMillis(10))
.onBackpressureDrop(dropped::incrementAndGet)
.publishOn(Schedulers.boundedElastic(), 1)
.subscribe(new BaseSubscriber<Long>() {
@Override protected void hookOnSubscribe(Subscription s) {
request(1);
}
@Override protected void hookOnNext(Long v) {
sleep(50);
request(1);
}
});
publishOn(..., 1) 把预取压到 1,需求才会及时回传;默认预取 256 会把慢消费掩盖在队列里。
与 TCK 的对应关系
TCK 的方法名就是规则编号,排查失败时可以直接定位:
| TCK 方法(前缀) | 规则 | 验证什么 |
|---|---|---|
required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements | 1.1 | request 多少,最多收到多少 |
required_spec102_maySignalLessThanRequestedAndTerminateSubscription | 1.2 | 少发并终止是合法的 |
required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates | 1.5 | 有限流结束发 onComplete |
required_spec201_mustSignalDemandViaSubscriptionRequest | 2.1 | 不 request 不应收到元素 |
required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete | 2.3 | 终止回调里不得调用 Subscription/Publisher |
required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscription... | 2.5 | 第二个 onSubscribe 要取消新收到的订阅 |
required_spec209/210_... | 2.9 / 2.10 | 无 request 也能收到终止信号 |
required_spec213_... | 2.13 | null 参数抛 NPE |
required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe | 3.2 | onNext/onSubscribe 内同步 request |
required_spec303_mustNotAllowUnboundedRecursion | 3.3 | request 递归有上界 |
required_spec306_afterSubscriptionIsCancelledRequestMustBeNops | 3.6 | cancel 后 request 无效 |
required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops | 3.7 | cancel 幂等 |
required_spec308_requestMustRegisterGivenNumberElementsToBeProduced | 3.8 | request 累加 |
required_spec309_... | 3.9 | request(0)/负数报错 |
required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling | 3.12 | cancel 最终生效 |
required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber | 3.13 | cancel 后释放引用 |
required_spec317_... | 3.17 | Long.MAX_VALUE 需求与饱和 |
PublisherVerification 这个名字容易让人以为它只测 §1。实际上它同时包含 §1 的 11 条和 §3 的 17 条中可测的部分,共 22 个 required_* 方法。§2 的验证在 SubscriberWhiteboxVerification(14 个 required)和 SubscriberBlackboxVerification(11 个 required)里。§4 只由 IdentityProcessorVerification 覆盖,且源码只引用 4.1,4.2 没有对应的 required 测试。
自检清单
实现自定义 Publisher 时,按这个顺序检查:
| 检查项 | 规则 | 验证方法 |
|---|---|---|
| 没有 request 时不发元素 | 1.1 | 订阅后不 request,确认无 onNext |
| request(10) 最多收 10 个 | 1.1 / 3.8 | 计数断言 |
| 多次 request 是累加 | 3.8 | request(1); request(2) 后应收 3 个 |
request(0) 与负数发 IllegalArgumentException | 3.9 | 两个方向各测一次 |
| 累计需求不溢出 | 3.17 | 连续 request(Long.MAX_VALUE) |
| 终止后不再发信号 | 1.7 | onComplete 后继续 request,确认无信号 |
| cancel 后 request 无效果 | 3.6 | cancel 后 request,确认无 onNext |
| cancel 后最终停止 | 3.12 | 异步源要留出传播时间再断言 |
| 信号串行 | 1.3 | 多线程并发 request,检查无交错 |
异常转 onError | 1.4 | 触发内部异常,确认收到 onError |
小结
| 结论 | 说明 |
|---|---|
| 规则总数 | 43 条:11 + 13 + 17 + 2 |
| 背压核心 | 1.1、1.2、2.1、3.8、3.9、3.17 |
| 取消相关 | 1.8、2.8、3.6、3.7、3.12、3.13 |
两参数 onBackpressureBuffer | 溢出默认 ERROR,不是丢弃 |
delayElements | 是节流/延迟,不是背压缓冲边界 |
| TCK | Publisher 22 个 required,Whitebox 14 个,Blackbox 11 个 |
阅读导航
上一篇:附录 B 调度器选择指南
下一篇:附录 D 从 CompletableFuture 到响应式流迁移指南
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。