可以分别使用Source.actorPublisher()
和Sink.actorSubscriber()
方法从actor创建源和接收器。但是可以从演员创建Flow
吗?
从概念上讲,似乎不是一个很好的理由,因为它实现了ActorPublisher
和ActorSubscriber
特征,但不幸的是,Flow
对象并没有&#39 ; t有任何方法可以做到这一点。在this优秀博客文章中,它已在早期版本的Akka Streams中完成,所以问题是它是否也可以在最新版本(2.4.9)中使用。
答案 0 :(得分:39)
我是Akka团队的一员,并希望使用此问题来澄清有关原始Reactive Streams接口的一些内容。我希望你会发现这很有用。
最值得注意的是,我们将很快在Akka团队博客上发布关于构建自定义阶段(包括Flows)的多个帖子,因此请密切关注它。
不要使用ActorPublisher / ActorSubscriber
请勿使用ActorPublisher
和ActorSubscriber
。它们的级别太低,您最终可能会以违反Reactive Streams specification的方式实施它们。它们是过去的遗留物,甚至仅仅是#34;仅限电力用户模式"。现在没有理由使用这些课程。我们从来没有提供过构建流程的方法,因为如果它被暴露为" raw"您可以使用Actor API实现并获取all the rules implemented correctly。
如果您真的想要实现原始的ReactiveStreams接口,请使用Specification's TCK来验证您的实现是否正确。你可能会被Flow
(或者Processor
必须处理的RS术语中的一些更复杂的极端情况所吓倒。)
大多数操作都可以在不进行低级别的情况下构建
您应该能够通过构建Flow[T]
并在其上添加所需的操作来简单地构建许多流程,仅作为示例:
val newFlow: Flow[String, Int, NotUsed] = Flow[String].map(_.toInt)
这是Flow的可重用描述。
由于您询问高级用户模式,因此这是DSL本身最强大的运营商:statefulFlatMapConcat
。在纯流元素上运行的绝大多数操作都可以使用它来表达:Flow.statefulMapConcat[T](f: () ⇒ (Out) ⇒ Iterable[T]): Repr[T]
。
如果您需要定时器,可以zip
使用Source.timer
等
GraphStage是用于构建自定义阶段的 最简单且最安全的 API
相反,构建Sources / Flows / Sinks有自己强大的和安全 API:GraphStage
。请阅读documentation about building custom GraphStages(它们可以是接收器/源/流或甚至任何任意形状)。它为您处理所有复杂的Reactive Streams规则,同时在实现阶段(可能是Flow)时为您提供完全的自由和类型安全。
例如,从文档中获取的是filter(T => Boolean)
运算符的GraphStage实现:
class Filter[A](p: A => Boolean) extends GraphStage[FlowShape[A, A]] {
val in = Inlet[A]("Filter.in")
val out = Outlet[A]("Filter.out")
val shape = FlowShape.of(in, out)
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic =
new GraphStageLogic(shape) {
setHandler(in, new InHandler {
override def onPush(): Unit = {
val elem = grab(in)
if (p(elem)) push(out, elem)
else pull(in)
}
})
setHandler(out, new OutHandler {
override def onPull(): Unit = {
pull(in)
}
})
}
}
它还处理异步通道,默认情况下是可熔的。
除了文档之外,这些博客文章还详细解释了为什么这个API是构建任何形状的自定义阶段的圣杯:
答案 1 :(得分:20)
Konrad的解决方案演示了如何创建一个使用Actors的自定义舞台,但在大多数情况下,我认为这有点矫枉过正。
通常你有一些能够回答问题的演员:
val actorRef : ActorRef = ???
type Input = ???
type Output = ???
val queryActor : Input => Future[Output] =
(actorRef ? _) andThen (_.mapTo[Output])
这可以很容易地与基本Flow
功能一起使用,该功能可以获取最大并发请求数:
val actorQueryFlow : Int => Flow[Input, Output, _] =
(parallelism) => Flow[Input].mapAsync[Output](parallelism)(queryActor)
现在actorQueryFlow
可以集成到任何流...
答案 2 :(得分:2)
这是通过使用图形阶段构建的解决方案。参与者必须确认所有消息才能产生反压力。当流失败/完成时通知角色,而在角色终止时流失败。 如果您不想使用Ask的话,这可能会很有用。当不是每个输入消息都有对应的输出消息时。
import akka.actor.{ActorRef, Status, Terminated}
import akka.stream._
import akka.stream.stage.{GraphStage, GraphStageLogic, InHandler, OutHandler}
object ActorRefBackpressureFlowStage {
case object StreamInit
case object StreamAck
case object StreamCompleted
case class StreamFailed(ex: Throwable)
case class StreamElementIn[A](element: A)
case class StreamElementOut[A](element: A)
}
/**
* Sends the elements of the stream to the given `ActorRef` that sends back back-pressure signal.
* First element is always `StreamInit`, then stream is waiting for acknowledgement message
* `ackMessage` from the given actor which means that it is ready to process
* elements. It also requires `ackMessage` message after each stream element
* to make backpressure work. Stream elements are wrapped inside `StreamElementIn(elem)` messages.
*
* The target actor can emit elements at any time by sending a `StreamElementOut(elem)` message, which will
* be emitted downstream when there is demand.
*
* If the target actor terminates the stage will fail with a WatchedActorTerminatedException.
* When the stream is completed successfully a `StreamCompleted` message
* will be sent to the destination actor.
* When the stream is completed with failure a `StreamFailed(ex)` message will be send to the destination actor.
*/
class ActorRefBackpressureFlowStage[In, Out](private val flowActor: ActorRef) extends GraphStage[FlowShape[In, Out]] {
import ActorRefBackpressureFlowStage._
val in: Inlet[In] = Inlet("ActorFlowIn")
val out: Outlet[Out] = Outlet("ActorFlowOut")
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = new GraphStageLogic(shape) {
private lazy val self = getStageActor {
case (_, StreamAck) =>
if(firstPullReceived) {
if (!isClosed(in) && !hasBeenPulled(in)) {
pull(in)
}
} else {
pullOnFirstPullReceived = true
}
case (_, StreamElementOut(elemOut)) =>
val elem = elemOut.asInstanceOf[Out]
emit(out, elem)
case (_, Terminated(targetRef)) =>
failStage(new WatchedActorTerminatedException("ActorRefBackpressureFlowStage", targetRef))
case (actorRef, unexpected) =>
failStage(new IllegalStateException(s"Unexpected message: `$unexpected` received from actor `$actorRef`."))
}
var firstPullReceived: Boolean = false
var pullOnFirstPullReceived: Boolean = false
override def preStart(): Unit = {
//initialize stage actor and watch flow actor.
self.watch(flowActor)
tellFlowActor(StreamInit)
}
setHandler(in, new InHandler {
override def onPush(): Unit = {
val elementIn = grab(in)
tellFlowActor(StreamElementIn(elementIn))
}
override def onUpstreamFailure(ex: Throwable): Unit = {
tellFlowActor(StreamFailed(ex))
super.onUpstreamFailure(ex)
}
override def onUpstreamFinish(): Unit = {
tellFlowActor(StreamCompleted)
super.onUpstreamFinish()
}
})
setHandler(out, new OutHandler {
override def onPull(): Unit = {
if(!firstPullReceived) {
firstPullReceived = true
if(pullOnFirstPullReceived) {
if (!isClosed(in) && !hasBeenPulled(in)) {
pull(in)
}
}
}
}
override def onDownstreamFinish(): Unit = {
tellFlowActor(StreamCompleted)
super.onDownstreamFinish()
}
})
private def tellFlowActor(message: Any): Unit = {
flowActor.tell(message, self.ref)
}
}
override def shape: FlowShape[In, Out] = FlowShape(in, out)
}