web-dev-qa-db-ja.com

Akka Streamsでアクターからフローを作成する

それぞれSource.actorPublisher()メソッドとSink.actorSubscriber()メソッドを使用して、アクターからソースとシンクを作成できます。しかし、アクターからFlowを作成することは可能ですか?

概念的には、ActorPublisherActorSubscriberの両方の特性を実装することを考えると、そうしない正当な理由はないようですが、残念ながら、Flowオブジェクトには何もありません。これを行う方法。 this 優れたブログ投稿では、Akka Streamsの以前のバージョンで行われているため、最新(2.4.9)バージョンでも可能かどうかは疑問です。

22
Ori Popowski

私はAkkaチームの一員です。この質問を使用して、未加工のReactive Streamsインターフェースに関するいくつかのことを明確にしたいと思います。これがあなたのお役に立てば幸いです。

最も注目すべきは、Akkaチームのブログに、Flowsを含むカスタムステージの構築に関する複数の投稿を近日公開する予定ですので、注目してください。

ActorPublisher/ActorSubscriberを使用しないでください

ActorPublisherActorSubscriberを使用しないでください。レベルが低すぎるため、 Reactive Streams仕様 に違反するような方法で実装することになります。それらは過去の遺物であり、それでも「パワーユーザーモードのみ」でした。今日、これらのクラスを使用する理由は本当にありません。フローを構築する方法を提供したことはありません。「生の」アクターAPIとして公開されて複雑になった場合、単純に爆発するので、 すべてのルールが正しく実装された です。

生のReactiveStreamsインターフェースを本当に実装したい場合は、 Specification's TCK を使用して、実装が正しいことを確認してください。 Flow(またはRS用語ではProcessorが処理する必要がある)のより複雑なコーナーケースのいくつかに不意を突かれる可能性があります。

ほとんどの操作は、低レベルにせずに構築できます

多くのフローは、単に例として_Flow[T]_からビルドし、それに必要な操作を追加することで簡単にビルドできるはずです。

_val newFlow: Flow[String, Int, NotUsed] = Flow[String].map(_.toInt)
_

これは、フローの再利用可能な説明です。

パワーユーザーモードについて質問しているので、これはDSL自体で最も強力な演算子です:statefulFlatMapConcat。プレーンストリーム要素を操作する操作の大部分は、それを使用して表現できます:Flow.statefulMapConcat[T](f: () ⇒ (Out) ⇒ Iterable[T]): Repr[T]

タイマーが必要な場合は、Zipで_Source.timer_などを使用できます。

GraphStageは、the最も単純で、safestカスタムステージを構築するためのAPIです

代わりに、Sources/Flows/Sinksの構築には、強力なおよび安全なAPIであるGraphStageがあります。 カスタムGraphStagesの構築に関するドキュメント (Sink/Source/Flowまたはany任意の形状)をお読みください。複雑なリアクティブストリームルールをすべて処理すると同時に、ステージ(フローなど)の実装中に完全な自由とタイプセーフを提供します。

たとえば、ドキュメントから取得した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があらゆる形のカスタムステージを構築する聖杯である理由について詳しく説明しています。

Konradのソリューションは、アクターを利用するカスタムステージを作成する方法を示していますが、ほとんどの場合、それは少しやり過ぎだと思います。

通常、質問に応答できるアクターがいくつかあります。

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を任意のストリームに統合できるようになりました...

グラフステージを使用して構築したソリューションを次に示します。アクターは、バックプレッシャーをかけるためにすべてのメッセージを確認する必要があります。ストリームが失敗/完了するとアクターに通知され、アクターが終了するとストリームが失敗します。これは、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)

}
2
Meeuw