一次流处理的元素数量限制

时间:2019-11-25 22:16:25

标签: scala akka akka-stream

AKKA中的一个如何在不删除任何元素的情况下限制流(一部分)中当前存在的元素数量?

2 个答案:

答案 0 :(得分:0)

对于Akka Streams中的这种情况,我将使用mapAsync,并注意不要使用.async(因为后者至少要缺少自定义的ActorMaterializer或调度程序才能进行调度)任务,不会那么紧地传播背压。

object AkkaStreamLimitInflight {
  implicit val actorSystem = ActorSystem("foo")
  implicit val mat = ActorMaterializer()

  def main(args: Array[String]): Unit = {
    import actorSystem.dispatcher

    val inflight = new AtomicInteger(0)

    def printWithInflight(msg: String): Unit = {
      println(s"$msg (${inflight.get} inflight)")
    }

    val source = Source.unfold(0) { state => 
      println(s"Emitting $state (${inflight.incrementAndGet()} inflight)")
      Some((state + 1, state))
    }.take(10)

    def quintuple(i: Int): (Int, Int) = {
      val quintupled = 5 * i
      printWithInflight(s"$i quintupled is $quintupled (originally $i)")
      (i, quintupled)
    }

    def minusOne(tup: (Int, Int)): (Int, Int) = {
      val (original, i) = tup
      val minus1 = i - 1
      printWithInflight(s"$i minus one is $minus1 (originally $original)")
      (original, minus1)
    }

    def double(tup: (Int, Int)): (Int, Int) = {
      val (original, i) = tup
      val doubled = 2 * i
      printWithInflight(s"$i doubled is $doubled (originally $original)")
      (original, doubled)
    }

    val toUnit = Flow[(Int, Int)]
      .map { case (original, i) =>
        println(s"Done with $i (originally $original) 
(${inflight.decrementAndGet()} inflight)")
        ()
      }

    val fut: Future[Done] = source
      .mapAsync(1) { i => Future { quintuple(i) }}
      .mapAsync(1) { tup => Future { minusOne(tup) }.map(double) }
      .via(toUnit)
      .runWith(Sink.ignore)


    fut.onComplete { _ => actorSystem.terminate() }
  }
}

在这种情况下,飞行计数永远不会超过4(由于运算符融合而在第一个mapAsync之前一个,第一个mapAsync中一个,第二个mapAsync中一个) ,以及第二个mapAsync之后的一个)。如果您想限制特定阶段的飞行中元素的数量,这就是方法。

但是,如果您要做的只是限制流中正在进行的工作,将业务逻辑移动到单个Future中并在mapAsync中生成有限数量的期货,那么您只需要一个旋转旋钮:

val fut: Future[Done] = source
  .mapAsync(5) { i =>
    Future { quintuple(i) }
      .map(minusOne)
      .map(double)
  }
  .via(toUnit)
  .runWith(Sink.ignore)

答案 1 :(得分:0)

您所说的被称为溢出策略。在documentation you link to中,第一个示例显示了所需的溢出策略:OverflowStrategy.backpressure

Akka流是反应性的,这意味着通过将溢出策略设置为背压,您可以告诉所连接的生产者您现在无法再使用任何数据。但是,只有在上游的所有内容都可以处理您不再处理任何元素的事实时,这才起作用。

在您的情况下,这样的事情应该起作用,因为我们的Source可以承受压力:

import scala.concurrent.duration._

implicit val system = ActorSystem()

FileIO.fromPath(Paths.get(...))
  .via(Compression.gunzip())
  .via(Framing.delimiter(ByteString("\n"), maximumFrameLength = 255))
  .map(_.utf8String)
  .buffer(10, OverflowStrategy.backpressure)
  .throttle(elements = 1, per = 1.second)
  .to(Sink.foreach(println))
  .run()

通过这个非常简单的示例,很容易看到实际的背压。由于使用的是throttle,因此流量将对上游产生背压,因此每秒仅发射1个元素。给定足够大的输入文件,这应该会迅速填充10个元素的缓冲区。

如果您换用OverflowStrategy.fail并尝试再次运行它,您会发现由于缓冲区已满,流几乎立即失败:

[ERROR] [Buffer(akka://default)] Failing because buffer is full and overflowStrategy is: [Fail]