您如何在Akka Flow中处理期货和mapAsync?

时间:2018-12-13 20:04:03

标签: scala concurrency parallel-processing akka akka-stream

我建立了一个定义简单流程的akka​​图DSL。但是流程f4花费3秒发送元素,而f2花费10秒。

结果,我得到了:3、2、3、2。但是,这不是我想要的。由于f2需要太多时间,我想得到:3、3、2、2。这是代码...

implicit val actorSystem = ActorSystem("NumberSystem")
implicit val materializer = ActorMaterializer()

val g = RunnableGraph.fromGraph(GraphDSL.create() { implicit builder: GraphDSL.Builder[NotUsed] =>
  import GraphDSL.Implicits._
  val in = Source(List(1, 1))
  val out = Sink.foreach(println)

  val bcast = builder.add(Broadcast[Int](2))
  val merge = builder.add(Merge[Int](2))



  val yourMapper: Int => Future[Int] = (i: Int) => Future(i + 1)
  val yourMapper2: Int => Future[Int] = (i: Int) => Future(i + 2)

  val f1, f3 = Flow[Int]
  val f2= Flow[Int].throttle(1, 10.second, 0, ThrottleMode.Shaping).mapAsync[Int](2)(yourMapper)
  val f4= Flow[Int].throttle(1, 3.second, 0, ThrottleMode.Shaping).mapAsync[Int](2)(yourMapper2)

  in ~> f1 ~> bcast ~> f2 ~> merge ~> f3 ~> out
  bcast ~> f4 ~> merge
  ClosedShape
})
g.run()

那么我要去哪里错了?与future或mapAsync吗?要不然 ... 谢谢

3 个答案:

答案 0 :(得分:1)

对不起,我是akka的新手,所以我还在学习。为了获得预期的结果,一种方法是放置异步:

val g = RunnableGraph.fromGraph(GraphDSL.create() { implicit builder: GraphDSL.Builder[NotUsed] =>
  import GraphDSL.Implicits._
  val in = Source(List(1, 1))
  val out = Sink.foreach(println)

  val bcast = builder.add(Broadcast[Int](2))
  val merge = builder.add(Merge[Int](2))



  val yourMapper: Int => Future[Int] = (i: Int) => Future(i + 1)
  val yourMapper2: Int => Future[Int] = (i: Int) => Future(i + 2)

  val f1, f3 = Flow[Int]
  val f2= Flow[Int].throttle(1, 10.second, 0, ThrottleMode.Shaping).map(_+1)
    //.mapAsyncUnordered[Int](2)(yourMapper)
  val f4= Flow[Int].throttle(1, 3.second, 0, ThrottleMode.Shaping).map(_+2)
    //.mapAsync[Int](2)(yourMapper2)

  in ~> f1 ~> bcast ~> f2.async ~> merge ~> f3 ~> out
  bcast ~> f4.async ~> merge
  ClosedShape
})
g.run()

答案 1 :(得分:1)

您已经知道了,替换:

mapAsync(i => Future{i + delta})

具有:

map(_ + delta).async
在两个流程中

将实现您想要的。

不同的结果归结为mapAsyncmap + async之间的关键区别。尽管mapAsync允许在并行线程中执行Future,但是多个mapAsync流程阶段仍由同一基础参与者管理,该参与者将在执行之前执行operator fusion(通常是为了节省成本)

另一方面,async实际上将异步边界引入到流中,其中各个流阶段由单独的参与者处理。在您的情况下,两个流阶段中的每一个都独立地向下游排放元素,而首先排放的元素首先被消耗。不可避免地,跨异步边界管理流会产生成本,并且Akka Stream使用窗口缓冲策略来分摊成本(请参见此Akka Stream doc)。

有关更多详细信息,请注意:mapAsyncasync之间的区别,此blog post可能很有趣。

答案 2 :(得分:0)

因此,您尝试将来自f2和f4的结果合并在一起。在这种情况下,您将尝试执行有时称为“散布收集模式”的操作。

我认为没有现成的方法可以实现它,而无需添加自定义的有状态阶段,该阶段将跟踪f2和f4的输出并在两者可用时发出记录。但是要牢记一些复杂之处:

  • 如果f2 / f4失败会发生什么
  • 如果他们花太长时间会发生什么
  • 每个输入记录都需要有唯一的键,这样才能知道f2的哪个输出对应于f4(反之亦然)