在Akka Streams中创建一个演员流

时间:2016-08-24 14:12:43

标签: scala akka akka-stream

可以分别使用Source.actorPublisher()Sink.actorSubscriber()方法从actor创建源和接收器。但是可以从演员创建Flow吗?

从概念上讲,似乎不是一个很好的理由,因为它实现了ActorPublisherActorSubscriber特征,但不幸的是,Flow对象并没有&#39 ; t有任何方法可以做到这一点。在this优秀博客文章中,它已在早期版本的Akka Streams中完成,所以问题是它是否也可以在最新版本(2.4.9)中使用。

3 个答案:

答案 0 :(得分:39)

我是Akka团队的一员,并希望使用此问题来澄清有关原始Reactive Streams接口的一些内容。我希望你会发现这很有用。

最值得注意的是,我们将很快在Akka团队博客上发布关于构建自定义阶段(包括Flows)的多个帖子,因此请密切关注它。

不要使用ActorPublisher / ActorSubscriber

请勿使用ActorPublisherActorSubscriber。它们的级别太低,您最终可能会以违反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)

}