本文已参与「新人创作礼」活动,一起开启掘金创作之路。
Flowable.publish pause
Flowable.publish的实现持有一个内部队列以支持来自下游的背压支持. 在2.x中, 如果所有产生的"ConnectableFlowable"消费者都被取消, 那么这个队列, 以及因此而产生的上游源, 正在缓慢地自行耗尽. 当缺乏消费者只是暂时的时候, 这导致了意外的数据损失.
有了3.x, 该实现能够暂停, 并且已经处于内部队列的数据项将会对稍后订阅的消费者即时可用.
publish-pause 示例
ConnectableFlowable<Integer> connectable = Flowable.range(1, 200).publish();
connectable.connect();
// the first consumer takes only 50 items and cancels
connectable.take(50).test().assertValueCount(50);
// with 3.x, the remaining items will be still available
connectable.test().assertValueCount(150);
Processor.offer 空检测
使用null参数调用PublishProcessor.offer(), BehaviorProcessor.offer()或MulticastProcessor.offer现在将抛出NullPointerException而非通过onError进行通知并由此通知Processor. 这现在匹配了响应流规范要求的onNext方法.
offer-示例
PublishProcessor<Integer> pp = PublishProcessor.create();
TestSubscriber<Integer> ts = pp.test();
try {
pp.offer(null);
} catch (NullPointerException expected) {
}
// no error received
ts.assertEmpty();
pp.offer(1);
// consumers are still there to receive proper items
ts.asssertValuesOnly(1);
MulticastProcessor.offer 聚合检测
MulticastProcessor被设计成搭配背压的处理器, 就像Flowable.publish操作符做的那样. 它包括内部优化, 例如将其订阅到正确类型的源时的运算符聚合.
因为用户可能保留Processor本身的引用, 因此从概念上讲, 他们可以调用onXXX方法并可能引起麻烦. 这和offer是一样的, 在先前提到的聚合发生的时候, offer方法被调用, 将引起2.x中未定义的行为.
有了3.x, offer方法将会抛出IllegalStateException并且不会打扰Processor内部的状态.
groupBy中的组抛弃
groupBy操作符是一种特殊的操作符,它将响应源作为其主要输出信号,用户也可以订阅这些内部源. 因此, 如果主序列被取消了(比如Flowable<GroupedFlowable<T>>本身), 消费者应该依然持续接收所在组的数据, 但却不会有新的组产生. 如果所有内部消费者已经取消了, 初始源因此只能被取消.
然而, 在2.x中, 没有任何机制强制内部源的消费, 由此组可能仅仅被全部忽略, 而且阻止了初始源的取消并且可能导致源泄露.
有了3.x, groupBy已经发生了变更, 由此当它发射了一个组时, 下游必须同步订阅它. 否则, 该组将被认为"被抛弃"并被终止. 通过这种方式, 被抛弃的组将不能阻止初始源的取消. 如果后来的消费者依然订阅该组, 触发组创建的数据项将依然可用.
同步订阅意味着下面的流设置将导致组抛弃及由此可能的组重建:
groupBy抛弃 示例
// observeOn creates a time gap between group emission
// and subscription
source.groupBy(v -> v)
.observeOn(Schedulers.computation())
.flatMap(g -> g)
// subscribeOn creates a time gap too
source.groupBy(v -> v)
.flatMap(g -> g.subscribeOn(Schedulers.computation()))
因此组本质上是热源, 由此应该使用observeOn将数据项的处理安全地移动到其它线程上:
source.groupBy(v -> v)
.flatMap(g ->
g.observeOn(Schedulers.computation())
.map(v -> v + 1)
)
groupBy中的背压
操作符Flowable.groupBy在某种程度上甚至更加的特殊, 它协调了来自内部组的消费者的背压和来自源Flowable的请求. 复杂的是, 这样的请求可能会导致创建一个新组, 为自己请求的组创建一个新项, 或者为完全不同的组创建新项. 由此, 组可以影响彼此接收数据项的能力, 能够挂起序列, 尤其是如果有些组完全不能被消费的时候.
当通过flatMap合并组时,可能会发生后者,其中单个组的数量大于flatMap的并发级别(默认128),因此新组将无法订阅,旧组为腾出空间可能无法完成。有了concatMap,同样的问题可以立即显现.
因为RxJava是非阻塞式的, 这些静默的挂起很难探测和诊断(例如, 没有线程在groupBy或者flatMap中阻塞). 由此, 3.x改变了groupBy的行为, 之后如果直接下游不能接收新组, 序列会以MissingBackpressureException终止:
groupBy 背压示例
Flowable.range(1, 1000)
.groupBy(v -> v)
.flatMap(v -> v, 16)
.test()
.assertError(MissingBackpressureException);
而且错误消息也将展示组序:
Unable to emit a new group (#16) due to lack of requests. Please make sure the downstream can always accept a new group and each group is consumed for the whole operator to be able to proceed.
将并发水平提升到恰当的数目(或者在预先不知道组数的情况下置为Integer.MAX_VALUE)应该解决了这个问题:
.flatMap(v -> v, 1000)