如何在Scala中实现不可变缓存?

时间:2015-06-03 14:24:13

标签: scala caching concurrency future

假设我有一台服务器,它根据客户端请求调用耗时的函数slow: Int => String。如果slow未在timeout内返回,则服务器会向客户端返回错误。

def trySlow(timeout: Duration)(id: Int): Try[String] = {
  val fut = Future(slow(id))
  try {
    Await.ready(fut, timeout).value match {
      case Some(r) => r
      case None => Failure(new TimeoutException()) // should not happen
    }
  } catch {
     case e: TimeoutException => Failure(e)
  } 
}

现在我想缓存期货,以便多个线程调用trySlow并使用相同的id等待同一个未来。

我将使用 mutable 并发TrieMap来实现单例缓存。

case class CacheEntry (
  future: Future[String], 
  start: Long = System.currentTimeMillis() // need it to clean the cache
) 

type Cache = TrieMap[Int, CacheEntry]

def trySlow(timeout: Duration, cache: Cache)(id: Int): Try[String] = {

  val fut = cache.getOrElseUpdate(id,  CacheEntry(Future(slow(id))))

  ... // as in above
}

有意义吗?如何使用 immutable 非单例缓存执行此操作?

2 个答案:

答案 0 :(得分:3)

如果你只想在scala集合中使用东西,scala.collection.concurrent.TrieMap是一个不错的选择。但请注意,TrieMap#getOrElseUpdate的thread safety bug最近才在2.11.6中修复。

如果你能负担额外的依赖,guava cache对于编写这样的缓存非常有用。特别是如果您希望缓存中的条目以某种方式过期。

关于缓存的API:假设您正在谈论纯函数,缓存生成器应该只是一个函数T => U并返回函数T => û

这些内容如下:

object Cached {
  def apply[T,U](f: T=>U): T=>U = { ??? }
}

用法:

def slow(id: Int): Try[String] = ??? // something complex including futures, timeouts etc.
val fast: Int => Try[String] = Cached(slow)

缓存API不应该知道有关正在缓存的函数的任何信息,除了你期望它是纯粹的。

答案 1 :(得分:1)

我建议您一般使用番石榴库。 (https://code.google.com/p/guava-libraries/

与RüdigerKlaehn一样,缓存是一个很好的起点。