2013-01-23 41 views
7

如何防止加载缓存中不存在的值多次同时在高效的方式?如何防止多次加载非缓存值?

典型的高速缓存的使用是以下伪代码:

Object get(Object key) { 
Object value = cache.get(key); 
if (value == null) { 
    value = loadFromService(key); 
    cache.set(key,value); 
} 
return value; 
} 

问题:值从服务(数据库,web服务,RemoteEJB或其他任何东西)加载之前的第二呼叫可以在相同的时间内进行,这将使该值再次加载。

例如,当我为用户X缓存所有项目,并且此用户经常被查看并且有许多项目时,很可能会同时调用其所有项目的负载,从而导致服务器负载过重。

我可以让get功能同步,但这会迫使其他搜索等待,没有多大意义。我可以为每个密钥创建新锁,但我不知道是否在Java(这部分是语言特定的,我将其标记为java的原因)中管理如此大量的锁是个好主意, 。

或者还有另一种方法可以使用?如果是这样,那么效率最高的是什么?

+1

你正在过分认真地对待这件事。除非从服务中加载数据的时间非常长,否则这不会成为问题。 – pablochan

+0

我有一些外部的EJB代码,在测试环境下可能需要长达20秒,所以我怕发生10或20个并发请求会发生什么 –

回答

3

不要重新发明轮子,用番石榴的LoadingCachememoizing supplier

如果您使用Ehcache,请阅读read-through,这是您要求的模式。您必须实现CacheEntryFactory接口来指示缓存如何读取缓存未命中的对象,并且必须将Ehcache实例包装为SelfPopulatingCache的实例。

+0

据我所知,CacheLoader正在做我期望的内部管理同步? –

+0

是的,并提供更多 - 驱逐,删除听众等 – mindas

+0

我看到,有趣的是,我使用ehcache,但我可以考虑使用番石榴,但ehcache支持溢出到磁盘,仍然,如何实现它的方式本身有趣。 –

7

你可以做的一般事情是使用Object的hashCode。

您可以使用基于hashCode的锁定数组来减少冲突的可能性。或者,您可以使用自动装箱的字节总是返回相同的对象的事实。

Object get(Object key) { 
    Object value = cache.get(key); 
    if (value == null) { 
     // every possible Byte is cached by the JLS. 
     Byte b = Byte.valueOf((byte) key.hashCode()); 
     synchronized (b) { 
      value = cache.get(key); 
      if (value == null) { 
       value = loadFromService(key); 
       cache.set(key, value); 
      } 
     } 
    } 
    return value; 
} 
+0

基于hashCode的锁池的好主意! 但获取锁后,你可以找到你的值由其他进程缓存,所以你也应该检查它是否加载:) –

+2

哇,我永远不会通过这种方式使用字节! –

+0

更重要的是,这是我第一次看到通过valueOf合并字节值的实际用法。 –

1

对于加载时,在地图中插入一个中间对象而不是结果,以指示加载已启动但未完成。下面的java.util.concurrent.FutureTask用于中间对象:

Object get(final Object key) throws Exception { 
    boolean doRun = false; 
    Object value; 
    synchronized (cache) { 
     value = cache.get(key); 
     if (value == null) { 
      value = new FutureTask(new Callable() { 
       @Override 
       public Object call() throws Exception { 
        Object loadedValue = loadFromService(key); 
        synchronized (cache) {cache.put(key, loadedValue);}; 
        return loadedValue; 
       } 

      }); 
      cache.put(key, value); 
      doRun=true; 
     } 
    } 
    if (value instanceof FutureTask) { 
     FutureTask task = (FutureTask) value; 
     if (doRun) { 
      task.run(); 
     } 
     return task.get(); 
    } 
    return value; 
}` 
+0

嗯,你的解决方案需要始终对整个缓存进行同步,但同步的部分相当快。最初你会怎么想,只有当值为null时才运行同步部分? –

+0

这取决于您希望每秒钟对缓存的请求数量。同步的部分长度不到1微秒,所以如果你的速度低于每秒100000个请求,那么碰撞的可能性可以忽略不计,所以任何并发症都不会起作用。如果速度更快,那么还有另外一个故事,你必须考虑许多不同的事情,包括处理器缓存,线程切换和垃圾回收器,其中访问缓存可能不是首先从性能点看法。 –