假设我想创建一个Flow
,它需要Int
并输出元组(doubled int, sum)
。所以我在一边是扇出,map
,另一边是scan
。然后我zip
他们,这就是结果:
object Main extends App {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val flow = Flow.fromGraph(GraphDSL.create() { implicit b =>
import GraphDSL.Implicits._
val broadcast = b.add(Broadcast[Int](2))
val zip = b.add(Zip[Int, Int])
val flowMap = b.add(Flow[Int].map(_ * 2))
val flowScan = b.add(Flow[Int].scan(0)(_ + _))
broadcast.out(0) ~> flowMap ~> zip.in0
broadcast.out(1) ~> flowScan ~> zip.in1
FlowShape(broadcast.in, zip.out)
})
Source(1 to 5).via(flow).to(Sink.foreach(println)).run()
}
不幸的是,这不输出任何内容。我研究了一下,发现:
Broadcast
在所有输出停止反压并且有可用的输入元素时发出,Scan
背压。这使整个流程陷入僵局,没有任何反应。有人知道如何实现结果:
(2,0)
(4,1)
(6,3)
(8,6)
(10,10)
以一种不错的方式?到目前为止我找到的唯一解决方案是使用.buffer
:
val flowScan = b.add(Flow[Int].buffer(1, OverflowStrategy.backpressure).scan(0)(_ + _))
但我真的不喜欢这个解决方案,因为它不仅描述了逻辑,还描述了一些技术问题......
答案 0 :(得分:3)
死锁的原因是扫描将在其第一次请求时发出起始值,因此在这种情况下0
并且不向上游传递需求,这意味着需求仅到达broadcast.out(0)
并且你说,broadcast
只在所有下游都有需求时才会发出。
缓冲区可能看起来像技术性,但它实际上是根据您想要实现的内容来表示图形,您希望压缩两个分支,但是扫描一个将始终是一个元素优先于另一个。这对于akka-streams如何运作至关重要。
所以你的结果实际上并没有匹配广播+ zip没有一些额外的图形节点的东西,我认为最干净地表达你想要发生的事情的方法是在扫描之前单独放置缓冲区,这使得它更明确的是,一个分支将领先于另一个分支:
broadcast.out(0) ~> flowMap ~> zip.in0
broadcast.out(1) ~> buffer ~> flowScan ~> zip.in1