【问题标题】:Reactor - how to retry on hot flux without dropping elements?Reactor - 如何在不丢失元素的情况下重试热助焊剂?
【发布时间】:2020-01-07 20:09:11
【问题描述】:

我有无限的热数据流。我即将对流中的每个元素执行一个操作,每个元素都返回一个 Mono,它将在有限的时间后(以一种或另一种方式)完成。

这些操作可能会引发错误。如果是这样,我想重新订阅热通量而不会丢失任何内容,重试抛出错误时正在处理的元素(即任何未成功完成的元素)。

我在这里做什么?我可以容忍对相同元素的重复操作,但不能完全从流中丢失元素。

我尝试使用 ReplayProcessor 来处理这个问题,但是如果不重复很多可能已经成功的操作(使用非常保守的超时),或者由于丢失元素,我看不到让它工作的方法覆盖缓冲区中旧元素的新元素(如下所示)。

测试用例:


    @Test
    public void fluxTest() {


        List<String> strings = new ArrayList<>();
        strings.add("one");
        strings.add("two");
        strings.add("three");
        strings.add("four");

        ConnectableFlux<String> flux = Flux.fromIterable(strings).publish();

        //Goes boom after three uses of its method, otherwise 
        //returns a mono. completing after a little time
        DangerousClass dangerousClass = new DangerousClass(3);

        ReplayProcessor<String> replay = ReplayProcessor.create(3);

        flux.subscribe(replay);

        replay.flatMap(dangerousClass::doThis)
              .retry(1)
              .doOnNext(s -> LOG.info("Completed {}", s))
              .subscribe();

        flux.connect();

        flux.blockLast();
    }


    public class DangerousClass {

        Logger LOG = LoggerFactory.getLogger(DangerousClass.class);

        private int boomCount;
        private AtomicInteger count;

        public DangerousClass(int boomCount) {
            this.boomCount = boomCount;
            this.count = new AtomicInteger(0);
        }

        public Mono<String> doThis(String s) {
            return Mono.fromSupplier(() -> {
                LOG.info("doing dangerous {}", s);
                if (count.getAndIncrement() == boomCount) {
                    LOG.error("Throwing exception from {}", s);
                    throw new RuntimeException("Boom!");
                }
                return s;
            }).delayElement(Duration.ofMillis(600));
        }
    }

打印出来:

doing dangerous one
doing dangerous two
doing dangerous three
doing dangerous four
Throwing exception from four
doing dangerous two
doing dangerous three
doing dangerous four
Completed four
Completed two
Completed three

一个永远不会完成。

【问题讨论】:

  • 我想出了一个强硬的解决方案,它使用 WorkQueueProcessor 而不是重放处理器和 SynchronizedCollection - 在执行危险任务之前,将每个元素添加到集合中。在平面图的下游,现在已经确保成功,每个元素都被删除。发生错误时,元素会从集合中移至处理器以进行重试。这看起来既笨重又阻塞,我希望有一些更“被动”的方式来解决这个问题。

标签: java project-reactor reactor


【解决方案1】:

错误(至少在上面的示例中)只能发生在 flatMap(dangerousClass::doThis) 调用中 - 因此重新订阅根 Flux 并在此 flatMap() 调用失败时重播元素似乎有点奇怪,并且(可能)不是你想做的。

相反,我建议放弃 ReplayProcessor,而只在内部 flatMap() 调用上调用 retry,这样你最终会得到类似的结果:

ConnectableFlux<String> flux = Flux.range(1, 10).map(n -> "Entry " + n).publish();

DangerousClass dangerousClass = new DangerousClass(3);

flux.flatMap(x -> dangerousClass.doThis(x).retry(1))
        .doOnNext(s -> System.out.println("Completed " + s))
        .subscribe();

flux.connect();

这将为您提供以下内容,所有条目均已完成且无需重试:

doing dangerous Entry 1
doing dangerous Entry 2
doing dangerous Entry 3
doing dangerous Entry 4
Throwing exception from Entry 4
doing dangerous Entry 4
Completed Entry 2
Completed Entry 1
Completed Entry 3
Completed Entry 4

【讨论】:

  • 谢谢!我考虑过这个选项,但我希望能够使用根Flux 上的错误/重试处理在发生不好的事情时暂停,整理混乱,然后恢复。重试内部的 flatMap 调用将导致在对混乱进行排序时进行大量重试(可能还有竞争条件)。
  • 经过一番思考,我认为这是正确的方法,我已将其标记为已接受。主通量上的错误不适合从平面图中解决错误。
猜你喜欢
  • 1970-01-01
  • 2016-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-29
  • 2015-02-06
相关资源
最近更新 更多