我有一个Observable,其中每个项目的转换方式可能会导致异常,但可以重试。我不希望失败打破流,因为每个项目代表一个独立的事务。我能想出的最佳解决方案是:
final AtomicLong errCount = new AtomicLong();
Observable.from(ImmutableList.of(1L, 2L, 3L)).flatMap(new Func1<Long, Observable<Long>>() {
@Override
public Observable<Long> call(Long aLong) {
return Observable.from(ImmutableList.of(aLong)).map(new Func1<Long, Long>() {
@Override
public Long call(Long aLong) {
if (aLong == 2 && errCount.getAndIncrement() < 1) {
throw new RuntimeException("retryable error");
}
return aLong * 100;
}
}).retry(2);
}
}).forEach(new Action1<Long>() {
@Override
public void call(Long aLong) {
System.out.println(aLong);
}
});
// Desired output: 100, 200, 300 (not 100, 100, 200, 300)
问题:
答案 0 :(得分:1)
重试逻辑真的很冗长。
您可以通过切换到ImmutableList
构造函数来完全避免使用Observable.just(t1, t2, t3)
。这基本上做同样的事情,但不那么冗长。
我看到你是flatMapping以便将每个值转换为Observable。这样可以防止在取消订阅整个链时映射单个值时遇到onError。因此,当操作员抛出它时,将取消订阅该值的内部可观察链。否则,错误将导致取消订阅并重新订阅主外部可观察对象。
如果你想保持这种行为但是减少锅炉板(除了明显切换到Java8 lambdas之外),我可以想到两个选择。
首先,在重试后重新订阅并重复数据删除您的数据。如果您的值具有良好的hashcode
和equals
实现,则只有在该集合尚未包含该值时,才能使用过滤器附加到有状态集和onNext。
Observable.<Long> just(1L, 2L, 3L)
.map(new Func1<Long, Long>() {
@Override
public Long call(Long aLong) {
if (aLong == 2 && errCount.getAndIncrement() < 1) {
throw new RuntimeException("retryable error");
}
return aLong * 100;
}})
.retry(2)
.filter(new Func1<Long, Boolean>() {
Set<Long> state = null;
@Override
public Boolean call(Long a) {
if (state == null)
state = new HashSet<Long>();
if (!state.contains(a)) {
state.add(a);
return true;
}
return false;
}})
.forEach(new Action1<Long>() {
@Override
public void call(Long aLong) {
System.out.println(aLong);
}});
其次,您可以将您的observable切换为 resume ,从重新订阅时停止的位置。请注意,使用缓冲区运算符(observeOn,merge,flatMap)时,这可能会导致数据丢失问题。这是因为他们将继续以与下游消费者分离的方式从生产者那里消费。因此,您需要确保在重试之前不进行缓冲。如果要实现支持背压的可观察源,还有其他注意事项。
// Should resume right where it left off
resumableObservable.map(...).retry(2).observeOn()
// Don't do this. ObserveOn will buffer values and resume will lose data.
resumableObservable.map(...).observeOn().retry(2)
// Also bad if running async observables. Merging buffers so this could have data loss.
Observable.merge(resumableObservable.map(...)).retry(2)
如果任何项目在2次重试后失败,则流会中断(不再处理任何项目)。我想要一个简洁的方法来返回异常和结果,比如Finagle的Try,所以我可以处理所有异常。
您可以将不可靠的地图从Long -> Long
更改为Long -> Tuple<Long, List<Exception>>
。由于这是相当多的泛型并且很快变得麻烦,我建议使用不同的重试运算符变体,即retryWhen(Func1<Observable<Throwable>, Observable<?>>)
。以下是如何在代码中使用它的示例。
}).retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>(){
@Override
public Observable<?> call(Observable<? extends Throwable> o) {
final AtomicInteger count = new AtomicInteger();
return o.filter(new Func1<Throwable, Boolean>() {
@Override
public Boolean call(Throwable t) {
return t instanceof RuntimeException || count.getAndIncrement() < 5;
}}).delay(1, TimeUnit.SECONDS, Schedulers.immediate());
}})
使用重试的好处是,您可以在非阻塞样式的一段时间后轻松实现延迟重试。