Reactive: Caching service response for few hours

问题: So my application would call below expensive HTTP service multiple times (simultaneously by multiple threads with same as well as different Ids for every client request to...

问题:

So my application would call below expensive HTTP service multiple times (simultaneously by multiple threads with same as well as different Ids for every client request to my application).

Mono<Foo> response = myService.fetch(id);

I would like to cache the response (in-memory) for few hours, and then only on next client request make only one call to refresh the cache.

Approach 1:

Mono<Foo> cachedResponse = Mono.empty();

public Mono<Foo> get(String id){
   return cachedResponse.switchIfEmpty(Mono.defer(()-> 
    {
       cachedResponse = myService.fetch(id).cache(Duration.ofHours(4));
       return cachedResponse;
    }));    
}

is following approach OK? Specifically since multiple threads could call get method with same id. Also, when the cache is invalidated after 4 hours, would it make cachedResponse Mono empty for switchIfEmpty to work correctly?

Approach 2: I could use some caching solution to store cache for few hours. e.g.

Foo getFromCacheSolution(String id);

and then,

public Mono<Foo> get(String id){
   Foo cachedFoo = getFromCacheSolution(id);
   if(cachedFoo != null){
      return Mono.just(cachedFoo);
   }
   else{
      return myService.fetch(id).doOnNext(value->storeToCacheSolution(id, value)); //line 7
   }
}

The problem with this solution is that line 7 would be called multiple times resulting in multiple calls to expensive fetch service (for example if 3 threads enter into get method with id 123 and cachedFoo is null). Making method synchronized may not help as line 7 would complete instantaneously.

One work-around would be to store Mono in the cache solution instead of Foo (not sure if that's a good idea or not):

Mono<Foo> getFromCacheSolution(String id); //returns cached or empty Mono

and then,

public Mono<Foo> get(String id){
   return getFromCacheSolution(id).switchIfEmpty(Mono.defer(()-> 
    {
       cachedResponse = myService.fetch(id).doOnNext(value->storeToCacheSolution(id, value));
       return cachedResponse;
    }));
}

Any recommendations or better alternatives?


回答1:

Your question consists of two parts: about caching and about exclusive locking for calls with same parameters.

  1. Caching.

    Your second approach is good for in-memory cache. Alternatively you could use CacheMono from the reactor-extra

    Mono<Foo> myFoo =
            CacheMono.lookup(key -> Mono.justOrEmpty(myCache.getIfPresent(key))
                    .map(Signal::next), id)
                    .onCacheMissResume(() -> myService.fetch(id))
                    .andWriteWith((key, signal) -> Mono.fromRunnable(() ->
                            Optional.ofNullable(signal.get())
                                    .ifPresent(value -> myCache.put(key, value))));
    
  2. Exclusive locking for calls with same parameters.

    Usually we should avoid any locking in the reactive world. But if you really need it, your lock should be nonblocking. I don't know any library, but you could find some ideas and links with examples in this question thread

  • 发表于 2019-02-28 02:46
  • 阅读 ( 750 )
  • 分类:sof

条评论

请先 登录 后评论
不写代码的码农
小编

篇文章

作家榜 »

  1. 小编 文章
返回顶部
部分文章转自于网络,若有侵权请联系我们删除