我们有一个缓慢的后端服务器,正在被负载压缩,我们希望中间层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))
}
}
答案 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已经快速通过索引访问?)