RxJava中的指数退避

时间:2017-01-16 14:43:11

标签: java java-8 rx-java exponential-backoff

我有一个API,它会触发一个触发事件的Observable

如果检测到互联网连接,我想返回Observable每隔defaultDelay秒发出一次值,如果没有连接则延迟numberOfFailedAttempts^2次。

我尝试过各种各样的风格,我遇到的最大问题是retryWhen's可观察的,只评估一次:

Observable
    .interval(defaultDelay,TimeUnit.MILLISECONDS)
    .observeOn(Schedulers.io())
    .repeatWhen((observable) ->
         observable.concatMap(repeatObservable -> {
             if(internetConnectionDetector.isInternetConnected()){
                 consecutiveRetries = 0;
                 return observable;
             } else {
                 consecutiveRetries++;
                 int backoffDelay = (int)Math.pow(consecutiveRetries,2);
                 return observable.delay(backoffDelay, TimeUnit.SECONDS);
                }
         }).onBackpressureDrop())
    .onBackpressureDrop();

有没有办法做我想做的事情?我发现了一个相关的问题(目前无法找到它),但采用的方法似乎不适用于动态值。

3 个答案:

答案 0 :(得分:4)

在您的代码中有两个错误:

  1. 为了重复某些可观察的序列,该序列必须是有限的。即而不是interval,您最好使用justfromCallable之类的内容,就像我在下面的示例中所做的那样。
  2. repeatWhen的内部函数中,您需要返回新的延迟可观察源,因此您必须返回observable.delay()而不是Observable.timer()
  3. 工作代码:

    public void testRepeat() throws InterruptedException {
        logger.info("test start");
    
        int DEFAULT_DELAY = 100; // ms
        int ADDITIONAL_DELAY = 100; // ms
        AtomicInteger generator = new AtomicInteger(0);
        AtomicBoolean connectionAlive = new AtomicBoolean(true); // initially alive
    
        Disposable subscription = Observable.fromCallable(generator::incrementAndGet)
                .repeatWhen(counts -> {
                    AtomicInteger retryCounter = new AtomicInteger(0);
                    return counts.flatMap(c -> {
                        int retry = 0;
                        if (connectionAlive.get()) {
                            retryCounter.set(0); // reset counter
                        } else {
                            retry = retryCounter.incrementAndGet();
                        }
                        int additionalDelay = ADDITIONAL_DELAY * (int) Math.pow(retry, 2);
                        logger.info("retry={}, additionalDelay={}ms", retry, additionalDelay);
                        return Observable.timer(DEFAULT_DELAY + additionalDelay, TimeUnit.MILLISECONDS);
                    });
                })
                .subscribe(v -> logger.info("got {}", v));
    
        Thread.sleep(220);
        logger.info("connection dropped");
        connectionAlive.set(false);
        Thread.sleep(2000);
        logger.info("connection is back alive");
        connectionAlive.set(true);
        Thread.sleep(2000);
        subscription.dispose();
        logger.info("test complete");
    }
    

    查看有关repeatWhen here的详细文章。

答案 1 :(得分:2)

我总是发现retryWhen有点低级,所以对于指数退避,我使用了一个单元测试的构建器(如Abhijit),可用于rxjava-extras的RxJava 1.x 。我建议使用加盖版本,以便延迟的指数增长不会超出您定义的最大值。

这是您使用它的方式:

observable.retryWhen(
    RetryWhen.exponentialBackoff(
        delay, maxDelay, TimeUNIT.SECONDS)
    .build());

我不同意retryWhen是错误的,但如果你发现错误报告给RxJava。错误很快就被修复了!

你需要 rxjava-extras 0.8.0.6或更高版本,它位于Maven Central:

<dependency>
    <groupId>com.github.davidmoten</groupId>
    <artifactId>rxjava-extras</artifactId>
    <version>0.8.0.6</version>
</dependency>

如果您需要RxJava 2.x版本,请告诉我。 {0.1}中的rxjava2-extras提供了相同的功能。

答案 2 :(得分:1)

您可以使用retryWhen运算符在没有连接时配置延迟。如何定期发出项目是一个单独的主题(查找intervaltimer运算符)。如果你想不通,请打开一个单独的问题。

我在Github上有一个广泛的例子,但我会在这里给你一个要点。

RetryWithDelay retryWithDelay = RetryWithDelay.builder()
    .retryDelayStrategy(RetryDelayStrategy.RETRY_COUNT)
    .build()

Single.fromCallable(() -> {
    ...
}).retryWhen(retryWithDelay)
.subscribe(j -> {
    ...
})

RetryWithDelay定义如下。我使用了RxJava 2.x,所以如果你使用的是1.x,签名应该是Func1<Observable<? extends Throwable>, Observable<Object>>

public class RetryWithDelay implements
        Function<Flowable<? extends Throwable>, Publisher<Object>> {
    ...
}

RetryWithDelay上课。

RetryStrategy枚举。

这允许我根据RetryDelayStrategy配置各种超时,常量,线性,指数。对于您的使用案例,您需要选择CONSTANT_DELAY_TIMES_RETRY_COUNT延迟策略,并在构建retryDelaySeconds(2)时致电RetryWithDelay

retryWhen是一个复杂的,甚至是错误的运营商。您在网上找到的大多数示例都使用range运算符,如果没有重试,它将失败。有关详细信息,请参阅我的回答here