简体   繁体   English

如何从外部正确阻止 Akka 流

[英]How to properly stop Akka streams from the outside

I'm designing a small tool that will generate CSV test data.我正在设计一个可以生成 CSV 测试数据的小工具。 I want to use Akka Streams (1.0-RC4) to implement the data flow.我想使用 Akka Streams (1.0-RC4) 来实现数据流。 There will be a Source that generates random numbers, a transformation into CSV strings, some rate limiter and a Sink that writes into a file.将有一个生成随机数的源,转换为 CSV 字符串,一些速率限制器和一个写入文件的接收器。

Also there should be a clean way of stopping the tool using a small REST interface.此外,应该有一种使用小型 REST 接口停止工具的干净方法。

This is where I'm struggling.这是我挣扎的地方。 After the stream has been started (RunnableFlow.run()) there seems to be no way of stopping it.流开始后 (RunnableFlow.run()) 似乎没有办法停止它。 Source and Sink are infinite (at least until disk runs full :)) so they will not stop the stream. Source 和 Sink 是无限的(至少在磁盘运行满之前 :)),因此它们不会停止流。

Adding control logic to Source or Sink feels wrong.给 Source 或 Sink 添加控制逻辑感觉不对。 Using ActorSystem.shutdown() too.也使用 ActorSystem.shutdown() 。 What would be a good way of stopping the stream?什么是停止流的方法?

Ok, so I found a decent solution. 好的,我找到了一个不错的解决方案。 It was already sitting there under my nose, I just did not see it. 它已经坐在我的鼻子底下,我只是没有看到它。 Source.lazyEmpty materializes into a promise that when completed will terminate the Source and the stream behind it. Source.lazyEmpty实现了一个承诺,即完成后将终止Source及其后面的流。

The remaining question is, how to include it into the infinite stream of random numbers. 剩下的问题是,如何将其包含在无限的随机数流中。 I tried Zip . 我试过Zip The result was that no random numbers made it through the stream because lazyEmpty never emits values ( doh ). 结果是没有随机数通过流,因为lazyEmpty从不发出值( doh )。 I tried Merge but the stream never terminated because Merge continues until all sources have completed. 我尝试了Merge但是流程从未终止,因为Merge一直持续到所有来源都已完成。

So I wrote my own merge. 所以我写了自己的合并。 It forwards all values from one of the input ports and terminates when any source completed. 它从输入端口中的一个转发所有的值和当任何源完成终止。

object StopperFlow {

  private class StopperMergeShape[A](_init: Init[A] = Name("StopperFlow")) extends FanInShape[A](_init) {
    val in = newInlet[A]("in")
    val stopper = newInlet[Unit]("stopper")

    override protected def construct(init: Init[A]): FanInShape[A] = new StopperMergeShape[A](init)
  }

  private class StopperMerge[In] extends FlexiMerge[In, StopperMergeShape[In]](
    new StopperMergeShape(), Attributes.name("StopperMerge")) {
    import FlexiMerge._

    override def createMergeLogic(p: PortT) = new MergeLogic[In] {
      override def initialState =
        State[In](Read(p.in)) { (ctx, input, element) =>
          ctx.emit(element)
          SameState
        }

      override def initialCompletionHandling = eagerClose
    }
  }

  def apply[In](): Flow[In, In, Promise[Unit]] = {
    val stopperSource = Source.lazyEmpty[Unit]

    Flow(stopperSource) { implicit builder =>
      stopper =>
        val stopperMerge = builder.add(new StopperMerge[In]())

        stopper ~> stopperMerge.stopper

        (stopperMerge.in, stopperMerge.out)
    }
  }    
}

The flow can be plugged into any stream. 流可以插入任何流。 When materialized it will return a Promise that terminates the stream on completion. 实现后,它将返回一个Promise ,在完成时终止流。 Here's my test for it. 这是我对它的测试。

implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()

val startTime = System.currentTimeMillis()

def dumpToConsole(f: Float) = {
  val timeSinceStart = System.currentTimeMillis() - startTime
  System.out.println(s"[$timeSinceStart] - Random number: $f")
}

val randomSource = Source(() => Iterator.continually(Random.nextFloat()))
val consoleSink = Sink.foreach(dumpToConsole)
val flow = randomSource.viaMat(StopperFlow())(Keep.both).to(consoleSink)

val (_, promise) = flow.run()

Thread.sleep(1000)
val _ = promise.success(())
Thread.sleep(1000)

I hope this is useful for other too. 我希望这对其他人也有用。 Still leaves me puzzled why there is no built in way for terminating streams from outside of the stream. 仍然让我感到困惑的是,为什么没有内置的方法来终止来自流外部的流。

Not exactly stopping, but limiting. 不是完全停止,而是限制。 You can use limit or take . 你可以使用limittake

Example from Streams Cookbook : 来自Streams Cookbook的示例:

val MAX_ALLOWED_SIZE = 100

// OK. Future will fail with a `StreamLimitReachedException`
// if the number of incoming elements is larger than max
val limited: Future[Seq[String]] =
  mySource.limit(MAX_ALLOWED_SIZE).runWith(Sink.seq)

// OK. Collect up until max-th elements only, then cancel upstream
val ignoreOverflow: Future[Seq[String]] =
  mySource.take(MAX_ALLOWED_SIZE).runWith(Sink.seq)

You can use Akka KillSwitches to abort (fail) or shutdown a stream.您可以使用Akka KillSwitches中止(失败)或关闭流。

There are two types of killswitches:有两种类型的终止开关:

Code examples are available in the links, but here is an example of aborting multiple streams with a shared killswitch:链接中提供了代码示例,但这里有一个使用共享 killswitch 中止多个流的示例:

val countingSrc = Source(Stream.from(1)).delay(1.second)
val lastSnk = Sink.last[Int]
val sharedKillSwitch = KillSwitches.shared("my-kill-switch")

val last1 = countingSrc.via(sharedKillSwitch.flow).runWith(lastSnk)
val last2 = countingSrc.via(sharedKillSwitch.flow).runWith(lastSnk)

val error = new RuntimeException("boom!")
sharedKillSwitch.abort(error)

Await.result(last1.failed, 1.second) shouldBe error
Await.result(last2.failed, 1.second) shouldBe error

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM