在项目反应器或 akka 流中,接收器和订阅者之间的概念区别是什么?
In project reactor or akka streams, what is the conceptual difference between sink and subscriber?
sink 和 subscriber 的概念跟我好像差不多。另外,我没有看到反应流规范中明确定义了接收器的概念。
我看到来自 Project Reactor (missing disclaimer there), already, however much of its assumptions about Akka Streams and Reactive Streams 的 Oleh Dokuka 不正确,所以请允许我在下面澄清一下。
免责声明:我参与 Reactive Streams 还很早,authored most of its Technology Compatibility Kit。我还维护 Akka 和 Akka Streams。
另请注意:Reactive Streams 已包含在 Java 9 中,并且被称为 java.util.concurrent.Flow.* 因此下面所有关于 RS 的评论与 j.u.c.Flow.Subscriber
完全相同和其他类型。
答案
Reactive Streams 是一种服务提供商接口 (SPI) 规范
Reactive Streams,特别是发布者/订阅者/订阅/处理器类型,是 Service Provider Interface. This is confirmed even in the earliest discussions 关于可追溯到 2014 年的规范。
在规范的早期,甚至规范的类型都试图隐藏发布者、订阅者和其他类型。可悲的是,无论在后面,类型都会泄漏,然后考虑 API、thus the API(!) was removed and the SPI types are all that remained.
现在你看到一些 Reactive Streams 的实现声称它们直接扩展这些类型是出于某种原因的好处。这是不正确的,因为这不是,也不是 Reactive Streams 接口的目标。这是对这些类型的误解——严格来说,Reactive Streams 库同意理解的互操作接口和 "speak"(一种协议)。
作为参考,RxJava 2.0 和 Reactor 确实直接扩展了这些类型,而 Akka Streams 通过将它们隐藏为应用程序开发人员编程接口来保持 RS 的设计和原则——这就是为什么Sink 不扩展 Subscriber。这与 "native support" 我看到人们声称直接 IS-A 关系是什么无关(相反,声称互操作库是您的 "native" 是对概念的误解)。
接收器和订阅者、源和发布者
The concepts of sink and subscriber seem similar to me.
正确,它们在故意和设计上是相似的。
因为 Sink 是 lifted representation 可以有效产生 Subscriber 的东西。为简化起见,您可以将其视为 "Subscriber factory"(更具体地说,Sink 是 "blueprint",Materializer 获取接收器的蓝图并创建适当的 RS 阶段,包括 Sources 的发布者和 Subscribers汇。
因此,当您说 Sink.ignore 时,它实际上是一个工厂,最终将创建一个订阅者,根据 Reactive Streams 进行所有请求和忽略。与在 Sink 上声明的所有其他方法相同。
这同样适用于 Source
,它将 1:1 与 Reactive Streams Publisher
相关联。因此,Source.single(1)
会在内部具体化为 Publisher
来完成它的工作 - 如果下游允许这样做,则会发出 1 个元素。
A.K.A。为什么 Reactive Streams 中没有 Sink?
如前所述,Akka的Sink并没有直接扩展一个Subscriber。然而,它基本上是他们的工厂。
您可能会问:"Does the user never see these Publisher/Subscriber types at all though during normal usage?" 答案是:是的,这是一个功能和设计目标(根据 Reactive Streams 是什么)。如果底层的 Publisher 和 Subscriber 实例始终直接暴露给用户,则可能会错误地调用它们,从而导致错误和混淆。如果除非明确要求,否则从不公开这些类型,则意外错误的可能性就会降低!
有些人误解了该设计,并声称 Akka Streams 不支持它 "native"(事实并非如此)。让我们看看在 API 中与订阅者分离的内容给我们带来了什么:
Also, I don't see the concept of sink being explicitly defined in the reactive streams spec.
确实,Sink 不是 Reactive Streams 的一部分,这绝对没问题。
避免 "Sink IS-A Subscriber"
的好处
Sink
s 是 Akka Streams 的一部分,它们的目的是提供流畅的 DSL,同时也是 Subscribers
的工厂。换句话说,如果 Subscriber 是乐高积木,Sink
就是构建它们的东西(而 Akka Stream Materializer
是将各种乐高积木放在一起以便 "run" 它们的东西)。
事实上,Sink 不像其他库那样携带任何带有订阅者的明确 IS-A(原文如此!)对用户是有益的:
这是因为 org.reactivestreams.Subscriber
现已包含在 Java 9 中,并且已成为 Java 本身的一部分,因此图书馆应该改用 java.util.concurrent.Flow.Subscriber
org.reactivestreams.Subscriber
。选择公开和直接扩展 Reactive Streams 类型的库现在将更难适应 JDK9 类型——它们所有 类 扩展 Subscriber 和 friends 都需要复制或更改以扩展完全相同的接口,但来自不同的包。在 Akka 中,我们只是在被要求时公开新类型——从 JDK9 发布之日起就已经支持 JDK9 类型。
Reactive Streams 是一个 SPI——一个服务提供商接口——它旨在供图书馆共享,以便它们可以 "talk the same types and protocol"。 Akka Streams 和其他 Reactive Streams 库所做的所有通信都遵守这些规则,如果您想将其他一些库连接到 Akka Streams,您只需这样做——给 Akka Streams inter-op 类型,它是订阅者、处理者或发布者;不是 Sink,因为那是 Akka 的 "Akka specific" DSL(领域特定语言),它在它之上增加了便利性和其他细节,隐藏(故意!)订阅者类型。
Akka(老实说,其他 RS 实现也被鼓励这样做,但选择不这样做)隐藏这些类型的另一个原因是因为它们很容易做错事。 如果您传递一个订阅者,任何人都可以调用它,甚至 un-knowingly break rules and guarantees that the Reactive Streams Specification requires 任何与该类型交互的人。
为了避免错误的发生,Akka Streams 中的 Reactive Streams 类型 "hidden" 并且仅在明确要求时才公开——最大限度地降低人们因意外调用“原始”Reactive 上的方法而犯错误的风险不遵循其协议的流类型。
sink 和 subscriber 的概念跟我好像差不多。另外,我没有看到反应流规范中明确定义了接收器的概念。
我看到来自 Project Reactor (missing disclaimer there),
免责声明:我参与 Reactive Streams 还很早,authored most of its Technology Compatibility Kit。我还维护 Akka 和 Akka Streams。
另请注意:Reactive Streams 已包含在 Java 9 中,并且被称为 java.util.concurrent.Flow.* 因此下面所有关于 RS 的评论与 j.u.c.Flow.Subscriber
完全相同和其他类型。
答案
Reactive Streams 是一种服务提供商接口 (SPI) 规范
Reactive Streams,特别是发布者/订阅者/订阅/处理器类型,是 Service Provider Interface. This is confirmed even in the earliest discussions 关于可追溯到 2014 年的规范。
在规范的早期,甚至规范的类型都试图隐藏发布者、订阅者和其他类型。可悲的是,无论在后面,类型都会泄漏,然后考虑 API、thus the API(!) was removed and the SPI types are all that remained.
现在你看到一些 Reactive Streams 的实现声称它们直接扩展这些类型是出于某种原因的好处。这是不正确的,因为这不是,也不是 Reactive Streams 接口的目标。这是对这些类型的误解——严格来说,Reactive Streams 库同意理解的互操作接口和 "speak"(一种协议)。
作为参考,RxJava 2.0 和 Reactor 确实直接扩展了这些类型,而 Akka Streams 通过将它们隐藏为应用程序开发人员编程接口来保持 RS 的设计和原则——这就是为什么Sink 不扩展 Subscriber。这与 "native support" 我看到人们声称直接 IS-A 关系是什么无关(相反,声称互操作库是您的 "native" 是对概念的误解)。
接收器和订阅者、源和发布者
The concepts of sink and subscriber seem similar to me.
正确,它们在故意和设计上是相似的。
因为 Sink 是 lifted representation 可以有效产生 Subscriber 的东西。为简化起见,您可以将其视为 "Subscriber factory"(更具体地说,Sink 是 "blueprint",Materializer 获取接收器的蓝图并创建适当的 RS 阶段,包括 Sources 的发布者和 Subscribers汇。 因此,当您说 Sink.ignore 时,它实际上是一个工厂,最终将创建一个订阅者,根据 Reactive Streams 进行所有请求和忽略。与在 Sink 上声明的所有其他方法相同。
这同样适用于 Source
,它将 1:1 与 Reactive Streams Publisher
相关联。因此,Source.single(1)
会在内部具体化为 Publisher
来完成它的工作 - 如果下游允许这样做,则会发出 1 个元素。
A.K.A。为什么 Reactive Streams 中没有 Sink?
如前所述,Akka的Sink并没有直接扩展一个Subscriber。然而,它基本上是他们的工厂。
您可能会问:"Does the user never see these Publisher/Subscriber types at all though during normal usage?" 答案是:是的,这是一个功能和设计目标(根据 Reactive Streams 是什么)。如果底层的 Publisher 和 Subscriber 实例始终直接暴露给用户,则可能会错误地调用它们,从而导致错误和混淆。如果除非明确要求,否则从不公开这些类型,则意外错误的可能性就会降低!
有些人误解了该设计,并声称 Akka Streams 不支持它 "native"(事实并非如此)。让我们看看在 API 中与订阅者分离的内容给我们带来了什么:
Also, I don't see the concept of sink being explicitly defined in the reactive streams spec.
确实,Sink 不是 Reactive Streams 的一部分,这绝对没问题。
避免 "Sink IS-A Subscriber"
的好处Sink
s 是 Akka Streams 的一部分,它们的目的是提供流畅的 DSL,同时也是 Subscribers
的工厂。换句话说,如果 Subscriber 是乐高积木,Sink
就是构建它们的东西(而 Akka Stream Materializer
是将各种乐高积木放在一起以便 "run" 它们的东西)。
事实上,Sink 不像其他库那样携带任何带有订阅者的明确 IS-A(原文如此!)对用户是有益的:
这是因为 org.reactivestreams.Subscriber
现已包含在 Java 9 中,并且已成为 Java 本身的一部分,因此图书馆应该改用 java.util.concurrent.Flow.Subscriber
org.reactivestreams.Subscriber
。选择公开和直接扩展 Reactive Streams 类型的库现在将更难适应 JDK9 类型——它们所有 类 扩展 Subscriber 和 friends 都需要复制或更改以扩展完全相同的接口,但来自不同的包。在 Akka 中,我们只是在被要求时公开新类型——从 JDK9 发布之日起就已经支持 JDK9 类型。
Reactive Streams 是一个 SPI——一个服务提供商接口——它旨在供图书馆共享,以便它们可以 "talk the same types and protocol"。 Akka Streams 和其他 Reactive Streams 库所做的所有通信都遵守这些规则,如果您想将其他一些库连接到 Akka Streams,您只需这样做——给 Akka Streams inter-op 类型,它是订阅者、处理者或发布者;不是 Sink,因为那是 Akka 的 "Akka specific" DSL(领域特定语言),它在它之上增加了便利性和其他细节,隐藏(故意!)订阅者类型。
Akka(老实说,其他 RS 实现也被鼓励这样做,但选择不这样做)隐藏这些类型的另一个原因是因为它们很容易做错事。 如果您传递一个订阅者,任何人都可以调用它,甚至 un-knowingly break rules and guarantees that the Reactive Streams Specification requires 任何与该类型交互的人。
为了避免错误的发生,Akka Streams 中的 Reactive Streams 类型 "hidden" 并且仅在明确要求时才公开——最大限度地降低人们因意外调用“原始”Reactive 上的方法而犯错误的风险不遵循其协议的流类型。