Akka流丢弃消息?

时间:2018-07-19 13:33:19

标签: java akka akka-stream

我是Akka Streams的新手。我在Java中使用它。 (akka-stream_2.12,版本:2.5.14)。

我写了下面的课:

package main;

import java.io.IOException;

import akka.actor.ActorSystem;
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.OverflowStrategy;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import akka.stream.javadsl.SourceQueueWithComplete;

public class AkkaTest {
    public static void main(String[] args) throws IOException, InterruptedException {
        final ActorSystem actorSystem = ActorSystem.create("VehicleSystem");
        final Materializer materializer = ActorMaterializer.create(actorSystem);

        SourceQueueWithComplete<Object> componentA_outPort1 = 
                Source.<Object>queue(100, OverflowStrategy.backpressure()).async()
                    .to(Sink.foreach(str -> System.out.println(str)))
                    .run(materializer);

        for(int i=1; i<100000; i++)
            componentA_outPort1.offer("Akka rocks: " + i);

        System.in.read();
        actorSystem.terminate();
        System.out.println("Done.");
    }
}

我希望代码可以打印100000条消息,因为这是迭代次数。相反,它只打印消息1-101,然后打印大约从61000开始的消息(即“ Akka岩石:61000”)。

因此大多数消息都不会打印。你能解释为什么吗?

2 个答案:

答案 0 :(得分:1)

问题的第一个提示是“完成”。最后未打印到控制台。取而代之的是,它被打印在“ Akka岩石”版画的开头或中间。

其原因是SourceQueue.offer是异步的。它返回CompletionStage,而您不等待其完成。某些流元素“丢失”的事实可以用method's documentation来解释,特别是以下部分:

  

另外,在使用背压溢出策略时:-如果缓冲区已满,则只有在缓冲区中有空间时,才能完成Future-在这种情况下,在Future完成之前调用报价会返回失败的Future

您可以通过执行以下操作来验证这一点:

SourceQueueWithComplete<Object> componentA_outPort1 = 
    Source.<Object>queue(100, OverflowStrategy.backpressure()).async()
        .to(Sink.foreach(str -> System.out.println(str)))
        .run(materializer);

for (int i=1; i<100000; i++) {
  CompletionStage<QueueOfferResult> result = componentA_outPort1.offer("Akka rocks: " + i);
  System.out.println(result);
}

您将看到很多这样的“ scala.concurrent.java8.FuturesConvertersImpl$CF@39471dfa [未完成]”

为了解决该问题,您应该等待要约的CompletionStage完成,从而有效地使整个通话同步,这似乎是您的意图:

SourceQueueWithComplete<Object> componentA_outPort1 = 
    Source.<Object>queue(100, OverflowStrategy.backpressure()).async()
        .to(Sink.foreach(str -> System.out.println(str)))
        .run(materializer);

for (int i=1; i<100000; i++) {
  componentA_outPort1.offer("Akka rocks: " + i).toCompletableFuture().join();
}

仍然不一定会在最后打印“完成”,因为要约的完成只能保证您队列已接受该元素,而不是已对其进行了完全处理。另外,请记住actorSystem.terminate()也是异步的。

以上方法适用于您的情况,但是在某些情况下,可能不希望阻塞当前线程。对于像您这样的简单情况,可以通过使用其他Source来轻松避免:

Source.range(1,  1000).map(i -> "Akka rocks: " + i)

对于更复杂的情况,请考虑Source的其他静态方法,例如使用Iterable的Source.fromSource.fromIterator

答案 1 :(得分:1)

几件事:

  1. 馈送SourceQueue的惯用方式是与另一个Source(如Pedro在他的回答中提到的那样)。
  2. 您可能会在流完成处理之前终止actor系统。

接收器的物化值完成后,关闭系​​统:

import java.util.concurrent.CompletionStage;

import akka.Done;
import akka.japi.Pair;
import akka.stream.javadsl.Keep;
import akka.stream.javadsl.RunnableGraph;
// other imports...

Sink<String, CompletionStage<Done>> sink =
  Sink.foreach(str -> System.out.println(str));

Source<String, SourceQueueWithComplete<String>> outPort =
  Source.<String>queue(100, OverflowStrategy.backpressure()).async();

RunnableGraph<Pair<SourceQueueWithComplete<String>, CompletionStage<Done>>> stream =
  outPort.toMat(sink, Keep.both());

Pair<SourceQueueWithComplete<String>, CompletionStage<Done>> pair = stream.run();

Source.range(1, 100000)
  .map(i -> "Akka rocks: " + i)
  .mapAsync(1, s -> pair.first().offer(s))
  .runWith(Sink.ignore(), materializer);

pair.second().thenRun(() -> actorSystem.terminate());