backpressure 故事
RS 规范和 Reactor 本身的主要焦点之一是背压(backpressure)。在生产者(生成)比消费者(消费)更快的 push 场景前提下,背压原理是,让消费者以信号反馈到生产者说:“喂!慢一点,我处理不过来了。” 这使得生产者有机会控制其速度,而不必丢弃数据(采取抽样)或致更糟的失败。
此时,你可能会想到 Mono:单一事件发出对消费者处理而言是没有压力的。Mono 工作和 (java8) CompletableFuture 工作原理之间仍然有一个关键的区别。后者只是推送:如果你有一个持有个 Future,这意味着处理异步结果的任务已经在执行。另一方面,背压的Flux或Mono支持:延迟(deferred)的(pull-push)交互:
- deferred(延迟),因为在调用 subscribe() 之前没有任何事发生;
- 拉(pull),因为在订阅和(request steps)请求步骤时, Subscriber 会向上游事件源发送信号,并且拉下一个数据块;
- 推(push),从生产者推到消费者哪里,限制请求元素的数量;
对于 Mono 来说,subscribe()类似按下按钮,说“我准备好接收我的数据”。对于 Flux,这个按钮是 request(n),这是一种前者的泛化。
意识到 Mono 是 一个(代价高昂的任务(如在IO,时延等方面))Publisher,对认识背压的价值而言至关重要:如果你不订阅,您不需要支付该任务的成本。由于 Mono 和背压的 Flux 经常被编排在 reactive 代码链上,可能组合来自多个异步源的结果,所以这种按需订阅触发能力是避免阻塞的关键。
有背压帮助理解一个用例:异步地将 Flux 里的数据聚合到一个 Mono。运算符如 reduce 和 hasElement 能够消费 Flux 的每一项,从中聚合出某种形式的数据(分别是 reduce 函数结果和一个 boolean 值),并将这些数据暴露为一个 Mono。在这种情况下,上游得到的背压信号是 Long.MAX_VALUE,使得上游以完全推的方式工作。
背压的另一个有趣的方面是它如何限制(数据源)流在内存中的对象数量。作为一个 Publisher,数据源在产生数据项时可能是缓慢的,所以对于来自下游的请求,它可以很好地开始产生超过已经就绪的数据项目数目。在这种情况下,整个流自然地回到推送模式(新项目直接通知给消费者)。但是,当有一个生产峰值,生产速度加快时,会很好地回到拉模型。在这两种情况下,最多将 N 数据(the request()量)保存在内存中。
通过需要的数据数量 N 与每项数据消耗的 kb 大小 W:您可以推断出最多 W*N 内存消耗。事实上,Reactor 大部分时间利通过知道 N 进行优化:创建相应地限制的队列并且应用预取策略,可以自动请求 N的75%(接收处理3/4的量)。
最后,Reactor 运算符有时会改变背压信号,将其与和运算符表示的期望和语义相关联。此行为的一个典型的例子是buffer(10):对于每一个(来自下游)请求 N,即运算符将从上游请求 10N ,它代表以足够数据填充缓冲器的数量应对消费。这被称为“活性背压”,开发者在微-批处理场景,可以自己控制以明确地告诉 Reactor 如何从一定输入量切换调整到一个不同的输出量。
著作权声明
首次发布于此,转载请保留以上链接
参考文章TODO;