Akka流通过异步处理在广播中产生背压

时间:2018-12-29 13:57:43

标签: scala akka-stream backpressure

我很难理解akka-stream是否在一个分支的广播中花费大量时间(异步)在图表上对Source施加反压。

我尝试了bufferbatch来查看源上是否施加了任何背压,但看起来却不是这样。我还尝试了刷新System.out,但是它并没有改变任何东西。

object Test extends App {
/* Necessary for akka stream */
implicit val system = ActorSystem("test")
implicit val materializer: ActorMaterializer = ActorMaterializer()

val g = RunnableGraph.fromGraph(GraphDSL.create() { implicit builder: GraphDSL.Builder[NotUsed] =>
    import GraphDSL.Implicits._

    val in = Source.tick(0 seconds, 1 seconds, 1)
        in.runForeach(i => println("Produced " + i))

    val out = Sink.foreach(println)
    val out2 = Sink.foreach[Int]{ o => println(s"2 $o") }

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

    val batchedIn: Source[Int, Cancellable] = in.batch(4, identity) {
        case (s, v) => println(s"Batched ${s+v}"); s + v
    }

    val f2 = Flow[Int].map(_ + 10)
    val f4 = Flow[Int].map { i => Thread.sleep(2000); i}

    batchedIn ~> bcast ~> f2 ~> out
                 bcast ~> f4.async ~> out2
    ClosedShape
})

g.run()
}

当我运行程序时,我希望在控制台中看到“ Batched ...”(批处理...),并且由于f4的处理速度不够快,有时会使其暂时卡住。目前,由于连续生成数字并且不进行批处理,因此这些行为均不符合预期。

编辑:我注意到一段时间后,批处理消息开始在控制台中打印出来。我仍然不知道为什么它不会很快发生,因为第一个元素应该发生背压

1 个答案:

答案 0 :(得分:1)

解释此行为的原因是当设置异步边界时akka引入了内部缓冲区。

Buffers for asynchronous operators

  

使用异步运算符时作为优化引入的内部缓冲区。


  

虽然流水线通常会提高吞吐量,但实际上,传递元素穿过异步(并因此跨越线程)边界的成本非常高。为了摊销此成本,Akka Streams在内部使用了窗口化的批量反压策略。之所以要窗口化,是因为与Stop-And-Wait协议相反,多个元素可能与请求元素同时“进行中”。这也是批量处理的,因为一旦从窗口缓冲区中清空了一个元素,就不会立即请求一个新元素,而在清空多个元素之后,会请求多个元素。这种批处理策略降低了通过异步边界传播背压信号的通信成本。

我知道这是玩具流,但是如果您解释您的目标是什么,我会尽力帮助您。

您需要mapAsync而不是async

val g = RunnableGraph.fromGraph(GraphDSL.create() { implicit builder: GraphDSL.Builder[NotUsed] =>
  import akka.stream.scaladsl.GraphDSL.Implicits._

  val in = Source.tick(0 seconds, 1 seconds, 1).map(x => {println(s"Produced ${x}"); x})

  val out = Sink.foreach[Int]{ o => println(s"F2 processed $o") }
  val out2 = Sink.foreach[Int]{ o => println(s"F4 processed $o") }

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

  val batchedIn: Source[Int, Cancellable] = in.buffer(4,OverflowStrategy.backpressure)

  val f2 = Flow[Int].map(_ + 10)
  val f4 = Flow[Int].mapAsync(1) { i => Future { println("F4 Started Processing"); Thread.sleep(2000); i }(system.dispatcher) }

  batchedIn ~> bcast ~> f2 ~> out
  bcast ~> f4 ~> out2
  ClosedShape
}).run()