在流的任何阶段失败时,在Akka中重试流

时间:2018-06-21 06:31:52

标签: java akka akka-stream retry-logic

我正在使用akka流来处理我的数据。在其中有1个Source由元素UUID组成。

流程如下:

  1. 正在从某个第三方HTTP服务中获取Element,该服务会返回完整的Element及其属性。
  2. 然后,我从该元素中检索所需的数据,并将其转换为应用程序可以理解的对象。
  3. 然后我将数据从该对象写入DB。
  4. 最后,我使用流中所有元素的状态更新数据库。

现在,我想向此流添加重试机制,以便如果流中的任何阶段失败,则应重试该阶段一段时间(例如3),如果之后失败,则流的唯一失败应该发生。例如,如果第三方服务存在一些问题,例如HTTP 504错误,则大多数情况下重试此元素成功之后。那么akka中有什么方法可以做到这一点。

目前,我正在维护1个列表,以存储所有失败的元素ID,如下所示。

代码:

List<UUID> failedId = new CopyOnWriteArrayList<>();
Source.from(elementIdToProcess).map(f -> {
            failedId.add(f);
            return f;
        }).via(featureFlow()).via(filterNullFeaturesFlow())
            .via(mapToFeatureFlow()).via(setFeaturesAdditionalInfo())
            .runForeach(features -> {
                features.parallelStream().forEach(feature -> {
                    try {
                        featureCommitter.save(feature);
                        failedId.remove(UUID.fromString(feature.getFeatureId()));
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                });
            }, materializer);

2 个答案:

答案 0 :(得分:1)

我遇到了类似的问题,并使用名为retry的未来功能解决了该问题。

根据您的代码,我可以这样实现:

  @BeforeClass
  public static void setUpClass() {
      system = ActorSystem.create();
      mat = ActorMaterializer.create(system);
  }

  @AfterClass
  public static void tearDown() {
      TestKit.shutdownActorSystem(system);
      system = null;
  }

  @Test
  public void shouldRetryArbitraryNumberOfTimes() {
    doThrow(new RuntimeException()).when(featureCommitter).process(anyString(), anyString(), anyString());
    TestSubscriber.Probe<Message> probe = getJmsSource().runWith(TestSink.probe(system), mat);
    Throwable throwable = probe.request(1).expectError();
    verify(featureCommitter, timeout(5000).times(4)).save(any(Feature.class));
  }

  private Source<Feature> getJmsSource() {
    List<UUID> failedId = new CopyOnWriteArrayList<>();
      return Source.from(elementIdToProcess)
              .via(featureFlow())
              .via(filterNullFeaturesFlow())
              .via(mapToFeatureFlow())
              .via(setFeaturesAdditionalInfo())
              .mapAsync(1, features -> {
                      features.parallelStream().forEach(feature -> retry(getCompletionStageCallable(feature),
                                                                         3, 
                                                                         Duration.ofMillis(200),
                                                                         system.scheduler(),
                                                                         system.dispatcher());
                  });
  }

  private Callable<CompletionStage<Feature>> getCompletionStageCallable(Feature feature) {
    () -> {
      return return CompletableFuture.supplyAsync(() -> {
          try {
              featureCommitter.save(feature);
          } catch (Exception e) {
              throw new RuntimeException(e);
          }
          return feature;
      }
  }

这里要考虑的主要事情是,我们不是将重试作为流的一部分来处理,而是作为处理未来的一种方式。 如您所见,我将保存移出接收器,并移到了mapAsync中,在其中我将并行度设置为1。这样,我就可以使用TestSubscriber.Probe来验证流的行为。

我希望这会有所帮助。

致谢

答案 1 :(得分:0)

您可以这样重试

source.recoverWithRetries(attempts = 2, {
  case _: RuntimeException ⇒ source
})

或者您可以使用RestartSourceRestartSinkRestartFlow采取退避策略。 所有这些都记录在Akka docs