附录 C:背压规则清单

按 Reactive Streams 1.0.4 原文重列 43 条规则:Publisher 11 条、Subscriber 13 条、Subscription 17 条、Processor 2 条,并标注每条规则与 request/emit 主链的关系及 TCK 覆盖情况。纠正旧文把 §2 与 §3 对调、「15 条中 9 条与背压相关」的错误,并用实测说明两参数 onBackpressureBuffer 的溢出语义,以及 delayElements 为什么不能当作背压缓冲边界。

规则全集:43 条,分四组

Reactive Streams 1.0.4 的规范正文一共 43 条,分组如下:

组规则编号条数角色
§1 Publisher1.1–1.1111产生并发送信号的一方
§2 Subscriber2.1–2.1313请求并消费信号的一方
§3 Subscription3.1–3.1717连接 Publisher 与 Subscriber 的合同
§4 Processor4.1–4.22同时是 Publisher 和 Subscriber

两个常见错误先纠正:

  1. §2 是 Subscriber,§3 是 Subscription。旧版把两者对调,导致 request(0)、cancel 之后的行为被归到了错误的角色上。
  2. 不是 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.1Subscriber 通过 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.3onSubscribe、onNext、onError、onComplete 必须串行并发 request 不能导致信号交错stochastic_spec103...
1.4Publisher 失败必须发 onError不能吞掉生产侧异常optional_spec104...
1.5有限流正常结束必须发 onComplete终止信号与 demand 无关required_spec105...
1.6发出终止信号后,Subscription 视为已取消终止后不再有“未结算需求”untested_spec106...
1.7终止后不得再发任何信号防止 cancel/终止竞争下重复发required_spec107...(仅 onComplete)
1.8cancel 后 Subscriber 必须最终停止收到信号“最终”允许异步传播延迟由 312/313 间接验证
1.9subscribe 必须先调 onSubscribe、正常返回;null 抛 NPE;拒绝订阅只能走 onError拒绝也不能绕过 onSubscriberequired_spec109... 三个
1.10subscribe 可多次调用,但每次必须是不同的 Subscriber同一 Subscriber 只能订阅一次untested_spec110...
1.11可支持多个 Subscriber,由实现决定单播还是多播多播时每个订阅各自结算 demandoptional_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.3onComplete / onError 内不得调用 Subscription 或 Publisher 的方法防止终止阶段的循环与竞态required_spec203... 两个
2.4收到终止信号后必须把 Subscription 视为已取消终止后不再 requestuntested_spec204...
2.5已有活动 Subscription 时又收到 onSubscribe,必须取消新来的这个保留旧订阅,防止两个 Publisher 同时驱动一个 Subscriberrequired_spec205...
2.6Subscription 不再需要时必须 cancel()及时释放资源untested_spec206...
2.7对 request/cancel 的调用必须串行并发 request 由 Subscriber 自己保证顺序untested_spec207...
2.8cancel 之后仍可能收到已请求的 onNextcancel 不是同步切断白盒 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 只绑定一个 Subscriptionuntested_spec212...
2.13信号方法必须正常返回;参数为 null 时抛 NPE;其他失败只能 cancelSubscriber 抛异常不能反向污染 Publisherrequired_spec213... 三个

实践中最常踩的是 2.8:cancel() 返回不代表立刻安全。如果 cancel 之前已经 request(100),Publisher 可能还有元素在路上。Subscriber 的 onNext 必须能容忍“取消后仍被调用几次”,不能一 cancel 就把状态清空到必然 NPE。

§3 Subscription:17 条

规则要点与背压的关系TCK 覆盖
3.1request/cancel 只能在对应 Subscriber 的上下文里调用Subscription 是点对点合同untested_spec301...
3.2必须允许在 onNext / onSubscribe 内同步调用 request支持同步 Publisherrequired_spec302...
3.3必须给 request 与 onNext 的同步递归设上界防止栈溢出required_spec303...
3.4request 应及时返回,不做重计算(SHOULD)不阻塞调用线程untested_spec304...
3.5cancel 不得在调用线程同步执行重计算取消不能被重计算拖住untested_spec305...
3.6cancel 后继续 request 必须是 NOP取消后需求不再生效required_spec306...
3.7cancel 后继续 cancel 必须是 NOP幂等required_spec307...
3.8未取消时,request(n) 必须累加 n 个待生产元素需求是加法required_spec308...
3.9request(n ≤ 0) 必须 onError(IllegalArgumentException)非法需求不能静默required_spec309... 两个
3.10request 可以同步调用 onNext同步 Publisher 合法untested_spec310...
3.11request 可以同步调用 onComplete / onError同步终止合法untested_spec311...
3.12cancel 必须让 Publisher 最终停止发信号,但不要求立刻生效取消是“请求停止”,不是“立即停止”required_spec312...
3.13cancel 必须让 Publisher 最终丢弃对 Subscriber 的引用防内存泄漏required_spec313...
3.14无其他订阅时,cancel 可让有状态 Publisher 进入 shutdown允许 Publisher 释放资源untested_spec314...
3.15cancel 必须正常返回不得抛异常untested_spec315...
3.16request 必须正常返回不得抛异常(非法参数走 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.1Processor 同时是 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_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements1.1request 多少,最多收到多少
required_spec102_maySignalLessThanRequestedAndTerminateSubscription1.2少发并终止是合法的
required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates1.5有限流结束发 onComplete
required_spec201_mustSignalDemandViaSubscriptionRequest2.1不 request 不应收到元素
required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete2.3终止回调里不得调用 Subscription/Publisher
required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscription...2.5第二个 onSubscribe 要取消新收到的订阅
required_spec209/210_...2.9 / 2.10无 request 也能收到终止信号
required_spec213_...2.13null 参数抛 NPE
required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe3.2onNext/onSubscribe 内同步 request
required_spec303_mustNotAllowUnboundedRecursion3.3request 递归有上界
required_spec306_afterSubscriptionIsCancelledRequestMustBeNops3.6cancel 后 request 无效
required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops3.7cancel 幂等
required_spec308_requestMustRegisterGivenNumberElementsToBeProduced3.8request 累加
required_spec309_...3.9request(0)/负数报错
required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling3.12cancel 最终生效
required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber3.13cancel 后释放引用
required_spec317_...3.17Long.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.8request(1); request(2) 后应收 3 个
request(0) 与负数发 IllegalArgumentException3.9两个方向各测一次
累计需求不溢出3.17连续 request(Long.MAX_VALUE)
终止后不再发信号1.7onComplete 后继续 request,确认无信号
cancel 后 request 无效果3.6cancel 后 request,确认无 onNext
cancel 后最终停止3.12异步源要留出传播时间再断言
信号串行1.3多线程并发 request,检查无交错
异常转 onError1.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是节流/延迟,不是背压缓冲边界
TCKPublisher 22 个 required,Whitebox 14 个,Blackbox 11 个

阅读导航

上一篇:附录 B 调度器选择指南

下一篇:附录 D 从 CompletableFuture 到响应式流迁移指南

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 15.3 全书总结与学习路径回顾
  2. 15.2 响应式流处理的演进趋势
  3. 15.1 引擎选型指南