AKKA中的一个如何在不删除任何元素的情况下限制流(一部分)中当前存在的元素数量?
答案 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]