反应式:将服务响应缓存几个小时

时间:2019-02-26 17:06:31

标签: java spring spring-webflux project-reactor

因此,我的应用程序将多次调用以下昂贵的HTTP服务(同时,对于我的应用程序的每个客户端请求,具有相同或不同ID的多个线程同时调用)。

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

我想将响应(内存中)缓存几个小时,然后仅在下一个客户端请求时才调用一次以刷新缓存。

方法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;
    }));    
}

以下方法可以吗?特别是因为多个线程可以使用相同的id调用get方法。另外,当缓存在4小时后失效时,是否会使cachedResponse Mono空,以便switchIfEmpty正常工作?

方法2: 我可以使用一些缓存解决方案将缓存存储几个小时。例如

Foo getFromCacheSolution(String id);

然后

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
   }
}

此解决方案的问题是,将多次调用第7行,从而导致多次调用昂贵的获取服务(例如,如果3个线程进入ID为123的get方法,并且cachedFoo为null)。同步制作方法可能无济于事,因为第7行会立即完成。

一种解决方法是将Mono存储在缓存解决方案中,而不是Foo中(不确定这是否是一个好主意):

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

然后

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

有什么建议或更好的选择吗?

1 个答案:

答案 0 :(得分:1)

您的问题包括两部分:关于缓存和关于具有相同参数的调用的排他锁定。

  1. 缓存。

    第二种方法适用于内存中缓存。或者,您可以使用reactor-extra

    中的 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if list.count == 0 { let alertView = UIAlertController(title: "Alert", message: "No layout found. Please add at least one plist file with correct format.", preferredStyle: .alert) alertView.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) UIApplication.shared.keyWindow?.rootViewController?.present(alertView, animated: true, completion: nil) self.removeFromSuperview() } return list.count } var btn = UIButton() func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { var cell = tableView.dequeueReusableCell(withIdentifier: "cell") if let cell = cell { print("cell is available") // Remove previously created button from reused cell view if let button = cell.contentView.subviews.first(where: { (view: UIView) -> Bool in view.isKind(of: UIButton.self) }) { button.removeFromSuperview() } } else { cell = UITableViewCell(style: .default, reuseIdentifier: "cell") } cell?.textLabel?.textAlignment = .left cell?.textLabel?.text = list[indexPath.row] if indexPath.row == (list.count - 1) { cell?.textLabel?.textAlignment = .center } else { btn = UIButton(type: UIButtonType.custom) as UIButton btn.frame = CGRect(x: 146, y: 0, width: 20, height: (cell?.frame.height)!) btn.addTarget(self, action: #selector(buttonPressed(sender: )), for: .touchUpInside) btn.tag = indexPath.row btn.setImage(UIImage(named: "delete.png"), for: .normal) cell?.contentView.addSubview(btn) } return cell! } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if list[indexPath.row] == "➕ Add Room"{ print("ADd reoom selrctj") self.delegate?.enterRoomName() }else{ self.delegate?.slecetedPicklist(fileName: list[indexPath.row]) self.removeFromSuperview() //hiding drop down if indexPath.row == btn.tag{ //Here it's not correct i think btn.setImage(UIImage(named: "check_mark.png"), for: .normal) }else{ btn.setImage(UIImage(named: "delete.png"), for: .normal) } self.table?.reloadData() } }
    CacheMono
  2. 具有相同参数的呼叫的独占锁定。

    通常,我们应该避免在反应世界中发生任何锁定。但是,如果您确实需要它,那么您的锁应该是非阻塞的。我不知道任何图书馆,但是您可以在this question thread

  3. 中找到一些想法和示例链接