play-cache - 记住一个到期的Future,它取决于未来的价值

时间:2014-12-16 05:59:57

标签: scala caching asynchronous playframework-2.0

给出以下功能

val readV : String => Future[V]
val isExpired: V => Boolean

如何使用播放缓存(或其他内容)将readV的结果记忆到isExpired

我是这样做的:

  def getCached(k: String) = Cache.getAs[Future[V]](k)
  def getOrRefresh(k: String) = getCached(k).getOrElse {
    this.synchronized {
      getCached(k).getOrElse {
        val vFut = readV(k)
        Cache.set(k, vFut)
        vFut
      }
    }
  }
  def get(k: String) = getOrRefresh(k).flatMap {
    case v if !isExpired(v) =>  Future.successful(v)
    case _ =>
      Cache.remove(k)
      getOrRefresh(k)
  }

这太复杂了,无法确保正确性

  

有没有更简单的解决方案来做到这一点。

2 个答案:

答案 0 :(得分:3)

如果可以将isExpired: V => Boolean更改为timeToLive: V => Duration,那么您可以使用

def refresh(k: String): Future[V] = readV(k) andThen {
  case Success(v) => Cache.set(k, Future.successful(v), timeToLive(v))
}

def get(k: String): Future[V] = Cache.getOrElse(k)(refresh(k))

为了控制并发性,我喜欢actor模型:

object Lookup {
  case class Get(key: String)


  class LookupActor extends Actor {
    def receive = {
      case Get(key) =>
        Cache.get(key) match {
          case Some(v) => sender ! v
          case None    => val v = Await.result(readV(k), timeout)
                          Cache.set(key, v, timeToLive(v))
                          sender ! v
        }
    }
  }
}

使用actor,如果我们有readV同步提供结果,那就好了,因为actor模型提供了并发(和控制)。

客户端,它是:

val futureV = lookupActor ? Lookup.Get(key) mapTo[V]

答案 1 :(得分:1)

谷歌有一个很好的缓存实现 - https://code.google.com/p/guava-libraries/wiki/CachesExplained(番石榴缓存)

在我们的项目中,我们使用ScalaCache https://github.com/cb372/scalacache,它抽象缓存层,并允许在默认情况下超时后使值过期。

以下是我们代码中的示例:

private val underlyingCache = CacheBuilder.newBuilder().expirationAfterAccess(1, TimeUnit.HOURS).build[String, Object]
implicit val cache = ScalaCache(
    cache = GuavaCache(underlyingCache),
    memoization = MemoizationConfig(toStringConvertor = ParamsToStringConverter))


def getCachedByID(id: String)(implicit format: Format[T]): Option[T] = memoize (cacheTimeout) {
    super.getByID(id)(format)
}

在这种情况下,memoize是一个宏调用,用于处理生成正确的处理。默认情况下,底层番石榴缓存配置为在1小时后使值过期,但memoize中的cacheTimeout参数可以覆盖它。

它不一定需要是番石榴,你可以使用其他支持的缓存实现,我只是更熟悉并习惯番石榴。