用例是这样的: 我想暂时缓存最新发出的昂贵的Observable响应,但在它过期后,返回昂贵的源Observable并再次缓存它等等。
一个非常基本的网络缓存方案,但我真的很难让它运行起来。
private Observable<String> getContentObservable() {
// expensive upstream source (API, etc.)
Observable<String> sourceObservable = getSourceObservable();
// cache 1 result for 30 seconds, then return to the source
return sourceObservable
.replay(1, 30, TimeUnit.SECONDS)
.autoConnect()
.switchIfEmpty(sourceObservable);
}
初始请求:转到源代码 源发出后30秒内的第二个请求:从缓存传递 缓存到期窗口之外的第三个请求:没有。我订阅它并且我没有获得数据,但它没有切换到上游源Observable。
看起来好像我只是从autoConnect()
连接到我的ConnectableObservable而且它永远不会以空的方式完成,因此它永远不会触发我的switchIfEmpty()
。
如何使用replay(1,x,x)
和switchIfEmpty()
的这种组合?
或者我从一开始就接近这个错误?
答案 0 :(得分:3)
return sourceObservable .replay(1, 30, TimeUnit.SECONDS) .autoConnect() .switchIfEmpty(sourceObservable);
初始请求:在源发出后30秒内转到源第二个请求:从缓存传递缓存到期窗口之外的第三个请求:没有。我订阅它并且我没有获得数据,但它没有切换到上游源Observable。
这里的问题是,重播只是重复过去30秒内sourceObservable
发出的相同序列,但是当你在30秒后订阅时,序列没有事件,甚至没有onCompleted()
,所以你不能switchIfEmpty()
,它不会起作用,因为它取决于'onCompleted()'
信号,没有任何排放,要知道它是'空的'。
通常,在缓存方案中使用重放是不够的,因为您需要的是在缓存过期的情况下再次重新订阅的方法,并且另外按需要执行,这意味着仅在某些客户端订阅它时。 (你可以做每100秒刷新一次的缓存,但这不是我想要的行为)
因此,正如@Yurly Kulikov建议的那样,您需要维护一个状态,并控制订阅操作以维持状态。 但是我认为解决方案中有一个主要的流程,因为它实际上并不是完全线程安全的,这意味着如果2在另一个之后订阅它,比如说A和B,而A执行请求并等待以保存新的结果是缓存,B也可以订阅,另一个请求将被执行,因为A尚未设置缓存值(它还没有完成第一个网络请求。
我建议在不同的实现中使用类似的方法,我建议here:
public class CachedRequest<T> {
private final AtomicBoolean expired = new AtomicBoolean(true);
private final Observable<T> source;
private final long cacheExpirationInterval;
private final TimeUnit cacheExpirationUnit;
private Observable<T> current;
public CachedRequest(Observable<T> o, long cacheExpirationInterval,
TimeUnit cacheExpirationUnit) {
source = o;
current = o;
this.cacheExpirationInterval = cacheExpirationInterval;
this.cacheExpirationUnit = cacheExpirationUnit;
}
private Observable<T> getCachedObservable() {
return Observable.defer(() -> {
if (expired.compareAndSet(true, false)) {
current = source.cache();
Observable.timer(cacheExpirationInterval, cacheExpirationUnit)
.subscribe(aLong -> expired.set(true));
}
return current;
});
}
}
with defer你可以根据缓存过期状态返回正确的Observable,因此每个订阅发生在缓存过期内都会被缓存Observable(使用cache()) - 意味着请求只会执行一次。在缓存过期后,附加订阅将触发新请求,并将设置新计时器以重置缓存过期。
答案 1 :(得分:1)
您必须维护许多呼叫者之间共享的状态。这就是为什么每次调用getContentObservable()时都无法创建Observable。
一种方法是在外部创建一个Observable,在Observable中保持内部状态(例如使用缓冲区),但是如果没有Observable,实现有状态行为通常会更容易。
以下是字段中共享状态的示例:
private Optional<String> cached = Optional.empty();
private Observable<String> getContentObservable() {
//use defer to delay cache evaluation to the point when someone subscribes
return Observable.defer(
() ->
cached.isPresent()
? Observable.just(cached)
: fetchAndCache()
)
//use the same scheduler for every cached field access
.subscribeOn(scheduler);
}
private Observable<String> fetchAndCache() {
Observable<String> cachedSource = getSourceObservable()
//I assume you only need one, make sure it is 1
.take(1)
.cache();
cachedSource
.observeOn(scheduler)
//side-effect stores the state
.doOnNext(str -> cached = Optional.of(str))
.flatMap(str -> Observable.timer(30, TimeUnit.SECONDS, scheduler))
//another side-effect clears the cache
.subscribe(l -> cached = Optional.empty());
return cachedSource;
}
答案 2 :(得分:1)
事实证明,即使在处置完毕后,您也可以使用Jake Wharton的重播份额来缓存最后一个值。 https://github.com/JakeWharton/RxReplayingShare