RxJava中的指数退避
我有一个API,它接受一个触发事件的RxJava中的指数退避,java,java-8,rx-java,exponential-backoff,Java,Java 8,Rx Java,Exponential Backoff,我有一个API,它接受一个触发事件的可观察的 我想返回一个可观察的值,如果检测到Internet连接,该值每defaultDelay秒发出一个值,如果没有连接,则延迟numberOfFailedAttempts^2次 我尝试过多种风格,最大的问题是retryWhen的observable只评估一次: Observable .interval(defaultDelay,TimeUnit.MILLISECONDS) .observeOn(Schedulers.io()) .r
可观察的
我想返回一个可观察的值,如果检测到Internet连接,该值每defaultDelay
秒发出一个值,如果没有连接,则延迟numberOfFailedAttempts^2次
我尝试过多种风格,最大的问题是retryWhen的
observable只评估一次:
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();
有什么办法可以做我想做的事吗?我发现了一个相关的问题(目前无法在搜索中找到),但所采取的方法似乎不适用于动态值。您可以使用retryWhen
操作符在没有连接时配置延迟。如何定期发出项目是一个单独的主题(查找interval
或timer
操作符)。如果你想不出来,就另问一个问题
我有一个关于我的Github的广泛的例子,但我会在这里给你要点
RetryWithDelay retryWithDelay = RetryWithDelay.builder()
.retryDelayStrategy(RetryDelayStrategy.RETRY_COUNT)
.build()
Single.fromCallable(() -> {
...
}).retryWhen(retryWithDelay)
.subscribe(j -> {
...
})
RetryWithDelay
定义如下。我使用的是RxJava2.x,所以如果您使用的是1.x,那么签名应该是Func1我总是发现retryWhen
有点低级,所以对于指数回退,我使用了一个经过单元测试的构建器(比如Abhijit),它可以随时用于RxJava1.x。我建议使用封顶版本,这样延迟的指数增长不会超过您定义的最大值
这是您使用它的方式:
observable.retryWhen(
RetryWhen.exponentialBackoff(
delay, maxDelay, TimeUNIT.SECONDS)
.build());
我不同意retryWhen
有bug,但如果发现bug,请将其报告给RxJava。bug修复得很快
您将需要Maven Central上的rxjava extras 0.8.0.6或更高版本:
<dependency>
<groupId>com.github.davidmoten</groupId>
<artifactId>rxjava-extras</artifactId>
<version>0.8.0.6</version>
</dependency>
com.github.davidmoten
从0.1.4开始,代码中有两个错误:
为了重复某个可观察序列,该序列必须是有限的。也就是说,你最好像我在下面的示例中所做的那样,使用just
或fromCallable
之类的内容,而不是interval
从repeatWhen
的内部函数中,您需要返回新的延迟可观测源,因此您必须返回observatable.delay()
,而不是observatable.timer()
工作代码:
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
的详细文章问题中的示例可能来自我的尝试,因为它似乎混合了我使用的两种方法(一种是基于计时器+重试,一种是基于间隔+延迟订阅),问题实际上来自那篇文章,这表示要重试/重复的可观察输入应该再次使用。不使用该可观察项是否会导致订阅泄漏问题?@SockedTrailMix是关于第一级输入的,而不是关于内部flatMap
。关于非常相似的模式,请参见文章中的最后一个示例。哦,我明白了,很抱歉我错过了计数
是获得flatMap的原因。我知道我在某处看到过这一点!我不想在这里重新发明轮子,所以我可能会继续,看看实现,看看我应该怎么做今天我注意到我忘了实现最大后退,但在使用0.8.0.6版时,我似乎不存在该方法签名。前几天我很忙,所以我告诉自己,我会回到我需要它的代码段,似乎该解决方案没有我预期的行为,并且在成功调用后应该重置重试(这是有道理的,因为这需要“带外”通信)。我认为下面概述的repeatWhen方法是我目前的情况所需要的,这种方法似乎针对更常见的情况进行了优化,即“重试直到成功”与“总是重试,如果不成功则延迟更长”如何repeat().retryWhen()
为了满足您的“始终重试”要求?这就是我根据retryWhen observable+flatMap的答案所做的。我认为我缺少的两件事是在retryWhen observable上进行flatMap,而不是仅仅返回一个新的,以及不从flatMap内部返回源observable(这导致了轻微的内存泄漏,无法正常工作)
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");
}