广播不输出Akka流

时间:2019-12-20 16:21:38

标签: scala akka akka-stream

我目前正在尝试使用Scala中具有管道和过滤器体系结构的Akka Streams编写程序。我有一个特定的图,它应该接受一个输入并将其输出到多个流。最后,所有不同流程的结果应合并为一个。就我而言,输入内容将是各种推文。然后,这些推文首先进入不同的过滤器,所有过滤器都位于一种类型上,然后进行扫描,该扫描仅计算已看到的某种类型的数量。在此之后,我希望将输出作为这些扫描的返回值并将其组合成一个元组。

现在,我为此设置了一个特定的图DSL,它使用Broadcast和ZipWith来做到这一点。我的代码如下:

val splitStreams =
  Flow.fromGraph(GraphDSL.create() { implicit builder =>
    import GraphDSL.Implicits._

    val bcastTweets = builder.add(Broadcast[Tweet](4))
    val zipTweets = builder.add(ZipWith[Int, Int, Int, Int, (Int, Int, Int, Int)]((a, b, c, d) => (a, b, c, d)))

    bcastTweets.out(0) ~> retweetFlow ~> retweetCount ~> zipTweets.in0
    bcastTweets.out(1) ~> replyFlow ~> replyCount ~> zipTweets.in1
    bcastTweets.out(2) ~> quotedFlow ~> quotedCount ~> zipTweets.in2
    bcastTweets.out(3) ~> normalFlow ~> normalCount ~> zipTweets.in3

    FlowShape(bcastTweets.in, zipTweets.out)
  })

问题是,当我测试此代码时,广播似乎都没有进入任何一个流。

谁能告诉我我做错了什么,我已经看了大约2天了,无法解决。

2 个答案:

答案 0 :(得分:1)

所描述的问题与ZipWith(和Zip)不能使用过滤的Shapes作为其输入有关。我的猜测是Akka Stream不知道如何正确压缩单独过滤的Shapes的元素。显然,如果所涉及的流是使用ZipWith的纯映射,则Zip / map会起作用。

您需要的一种解决方法是将ZipWithMerge一起替换成grouped,如以下带有大量虚拟过滤流的琐碎示例所示:

import akka.actor.ActorSystem
import akka.stream.scaladsl._
import akka.stream._

implicit val system = ActorSystem("system")
implicit val materializer = ActorMaterializer()  // Not needed for Akka Stream 2.6+
implicit val ec = system.dispatcher

val n = 4

def filterFlow(i: Int) = Flow[Int].filter(_ % n == i)

val customFlow = Flow.fromGraph(GraphDSL.create() { implicit builder =>
  import GraphDSL.Implicits._

  val bcast = builder.add(Broadcast[Int](n))
  val merger = builder.add(Merge[Int](n))

  (0 until n).foreach{ i =>
    bcast.out(i) ~> filterFlow(i) ~> merger.in(i)
  }

  FlowShape(bcast.in, merger.out)
})

Source(0 to 9).via(customFlow).grouped(n).runForeach(println)
// Output:
// Vector(0, 1, 2, 3)
// Vector(4, 5, 6, 7)
// Vector(8, 9)

如果输出需要为元组,只需像下面那样应用collect(例如,对于n = 4):

val empty = -1  // Default place-holder value

Source(0 to 9).via(customFlow).grouped(n).collect{
    case Vector(a)          => (a, empty, empty, empty)
    case Vector(a, b)       => (a, b, empty, empty)
    case Vector(a, b, c)    => (a, b, c, empty)
    case Vector(a, b, c, d) => (a, b, c, d)
  }.runForeach(println)
// Output:
// (0, 1, 2, 3)
// (4, 5, 6, 7)
// (8, 9, -1, -1)

答案 1 :(得分:0)

以下是发生的情况的背景:

Zip要求一个元素从每个上游向下移动以压缩成一个元组(如果尚未看到,则无法弥补头寸的值),并且将不需要直到上游,直到它压缩了一个元组并发送到下游为止。

另一方面,

Broadcast仅在看到其所有下游的需求时才发出,以便它可以安全地向所有下游发出元素。因此,如果广播和zip之间的流之一丢弃任何元素,您最终将陷入阻塞的流-zip不能要求更多,广播也不能发送给所有人。

您可以通过在每个广播流中添加分离器或缓冲区作为第一运算符来摆脱此僵局。但是,您必须仔细考虑是否要实现。

Merge并将仅发出从上游到下游的各个元素,而MergeLatest将在所有输入上看到至少一个输入后立即发出(意味着它如果第一个元素在输入中的一个上被过滤,则可能也会死锁),然后还可能重复值,因此这两个值与zip完全不同。