给出以下功能
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)
}
这太复杂了,无法确保正确性
有没有更简单的解决方案来做到这一点。
答案 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参数可以覆盖它。
它不一定需要是番石榴,你可以使用其他支持的缓存实现,我只是更熟悉并习惯番石榴。