使用MapMaker#makeComputingMap来防止同一数据的同时RPC

时间:2010-10-28 01:08:27

标签: java scala concurrency rpc guava

我们有一个缓慢的后端服务器,正在被负载压缩,我们希望中间层Scala服务器对每个唯一查找只有一个未完成的后端请求。

后端服务器只存储不可变数据,但是在添加新数据时,中间层服务器将代表客户端请求最新数据,后端服务器很难加载。使用写入时生成的唯一键将不可变数据缓存在memcached中,但写入速率很高,因此我们获得了较低的memcached命中率。

我的一个想法是使用Google Guava的MapMaker#makeComputingMap()来包装实际的查找,在ConcurrentMap#get()返回后,中间层将保存结果并从Map中删除密钥。

这看起来有点浪费,虽然代码很容易编写,请参阅下面的例子来了解我的想法。

是否有更自然的数据结构,库或Guava的一部分可以解决这个问题?

import com.google.common.collect.MapMaker

object Test
{
  val computer: com.google.common.base.Function[Int,Long] =
  {
    new com.google.common.base.Function[Int,Long] {
      override
      def apply(i: Int): Long =
      {
        val l = System.currentTimeMillis + i
        System.err.println("For " + i + " returning " + l)
        Thread.sleep(2000)
        l
      }
    }
  }

  val map =
  {
    new MapMaker().makeComputingMap[Int,Long](computer)
  }

  def get(k: Int): Long =
  {
    val l = map.get(k)
    map.remove(k)
    l
  }

  def main(args: Array[String]): Unit =
  {
    val t1 = new Thread() {
      override def run(): Unit =
      {
        System.err.println(get(123))
      }
    }

    val t2 = new Thread() {
      override def run(): Unit =
      {
        System.err.println(get(123))
      }
    }

    t1.start()
    t2.start()
    t1.join()
    t2.join()

    System.err.println(get(123))
  }
}

2 个答案:

答案 0 :(得分:4)

我不确定你为什么要自己实施删除,为什么不简单地使用弱值或软值并让GC为你清理?

new MapMaker().weakValues().makeComputingMap[Int, Long](computer)

答案 1 :(得分:2)

我认为你做的很合理。您只能使用该结构在密钥上获取锁定条带,以确保访问相同的密钥冲突。不用担心您不需要每个键的值映射。 ConcurrentHashMap和朋友是Java库+ Guava中唯一为您提供锁定条带化的结构。

这确实会导致一些较小的运行时开销,加上你不需要的哈希表的大小(如果访问同一段堆积并且remove()无法跟上,则可能会增长)。

如果你想让它尽可能便宜,你可以自己编写一些简单的锁定条带。基本上是Object[](或Array[AnyRef] :))N个锁(N =并发级别),您只需将查找键的哈希值映射到此数组中,然后锁定。这样做的另一个好处是你真的不必做CHM需要做的哈希码技巧,因为后者必须在一个部分中分割哈希码以选择锁,而另一个部分用于哈希表的需要,但是你可以使用整个它只是为了锁定选择。

修改:在下面草绘我的评论:

val concurrencyLevel = 16
val locks = (for (i <- 0 to concurrencyLevel) yield new AnyRef).toArray

def access(key: K): V = {
   val lock = locks(key.hashCode % locks.size)
   lock synchronized {
     val valueFromCache = cache.lookup(key)
     valueFromCache match {
       case Some(v) => return v
       case None =>
         val valueFromBackend = backendServer.lookup(key)
         cache.put(key, valueFromBackend)
         return valueFromBackend
     }
   }
}

(顺便说一下,需要toArray来电吗?或者返回的IndexSeq已经快速通过索引访问?)