2009-10-05 59 views
13

我有一个高流量的网站,我使用休眠。我还使用ehcache来缓存生成页面所需的一些实体和查询。避免同一缓存区域的多个重新填充(由于并发)

问题是“并行缓存未命中”,长时间的解释是,当应用程序启动并且缓存区域很冷时,每个缓存区域被不同的线程多次填充(而不是一次),因为该站点正在同时受到许多用户的打击。另外,由于相同的原因,当某个缓存区域无效时,它会被重新填充很多次。 我该如何避免这种情况?

我设法为convert 1 entity and 1 query cache to a BlockingCache通过提供我自己的实现hibernate.cache.provider_class但BlockingCache的语义似乎不工作。甚至有时最糟糕的是BlockingCache死锁(块)和应用程序完全挂起。线程转储显示处理在get操作的BlockingCache的互斥体上被阻止。

那么问题是,Hibernate是否支持这种用法呢?

如果不是,您如何解决生产中的这个问题?

编辑:本在hibernate.cache.provider_class点到我的自定义缓存提供商是从SingletonEhCacheProvider并在start()方法(线136之后)的末尾复制粘贴我做的:

Ehcache cache = manager.getEhcache("foo"); 
if (!(cache instanceof BlockingCache)) { 
    manager.replaceCacheWithDecoratedCache(cache, new BlockingCache(cache)); 
} 

这种方式在初始化时,在其他人触及名为“foo”的缓存之前,我使用BlockingCache对其进行了修饰。 “foo”是查询缓存,“bar”(相同的代码,但省略)是pojo的实体缓存。

编辑2:“似乎不起作用”意味着最初的问题仍然存在。由于并发性,高速缓存“foo”仍然使用相同的数据多次重新填充。我通过用10个线程的JMeter强调站点来验证这一点。我期望9个线程阻塞,直到第一个请求来自“foo”的数据完成它的工作(执行查询,将数据存储在缓存中),然后直接从缓存中获取数据。

编辑3:这个问题的另一种解释可以在https://forum.hibernate.org/viewtopic.php?f=1&t=964391&start=0看到,但没有明确的答案。

+0

一个有趣的开发可能有助于缓解这个问题,现在ehcache(自2.1)支持hibernate的事务性缓存并发策略:http://stackoverflow.com/questions/3472613/does-ehcache-2-1-support - 事务性缓存并发策略在hibernat/3474011#3474011 – cherouvim 2011-03-17 07:39:40

回答

1

在这个问题上最大的改进是ehcache现在(自2.1)supports the transactional hibernate cache policy。这极大地缓解了这个问题中描述的问题。

为了更进一步(锁的线程同时访问相同的查询缓存区)一个需要实现一个QueryTranslatorFactory返回定制(扩展)QueryTranslatorImpl情况下,这将检查查询和参数,并在列表中所需批方法。这当然是关于使用hql的查询缓存的具体用例,它提取许多实体。

5

我不太清楚,但是:

它允许已经 元素在缓存并发读取访问。如果 元素为空,则其他读取将 阻塞,直到具有相同 密钥的元素被放入缓存。

这不是说Hibernate会等到其他线程将对象放入缓存中吗?这就是你观察到的,对吧?

Hib和高速缓存的工作原理是这样的:

  1. 的Hib得到一个请求的对象,如果
  2. 的Hib检查的对象是在高速缓存 - 缓存。get()
  3. 否?乙型流感嗜血杆菌加载从DB对象并投入缓存 - cache.put()

因此,如果对象不在缓存中(不是由于前面的更新操作放在那里),乙型流感嗜血杆菌会等待1)永远。

我想你需要一个缓存变体,其中线程只等待一个对象很短的时间。例如。 100毫秒。如果对象没有到达,线程应该为null(因此Hibernate会从DB加载对象并放入缓存中)。

其实,更好的逻辑是:

  1. 检查另一个线程请求同一个对象
  2. 如果为true,等待长(500毫秒)为对象,以到达
  3. 如不属实,立即返回null

(我们无法永远等待2,因为线程可能无法将对象放入缓存 - 由于例外)。

如果BlockingCache不支持此行为,则需要自己实现缓存。我过去做过,不难 - 主要的方法是get()和put()(尽管API显然已经增长)。

UPDATE

其实,我只是读BlockingCache的来源。它正是我所说的 - 锁定并等待超时。因此,你不需要做任何事情,只需使用它...

public Element get(final Object key) throws RuntimeException, LockTimeoutException { 
    Sync lock = getLockForKey(key); 
    Element element; 
     acquiredLockForKey(key, lock, LockType.WRITE); 
     element = cache.get(key); 
     if (element != null) { 
      lock.unlock(LockType.WRITE); 
     } 
    return element; 
} 

public void put(Element element) { 
    if (element == null) { 
     return; 
    } 
    Object key = element.getObjectKey(); 
    Object value = element.getObjectValue(); 

    getLockForKey(key).lock(LockType.WRITE); 
    try { 
     if (value != null) { 
      cache.put(element); 
     } else { 
      cache.remove(key); 
     } 
    } finally { 
     getLockForKey(key).unlock(LockType.WRITE); 
    } 
} 

所以这有点奇怪,它不适合你。告诉我一些东西:在你的代码中这个点:

Ehcache cache = manager.getEhcache("foo"); 

它是否同步?如果多个请求同时发生,是否只有一个缓存实例?

+0

感谢您的回答。我仍然觉得很难相信我需要用休眠和ehcache这么低的水平来解决这个问题。 – cherouvim 2009-10-06 04:53:23

+0

它不同步,但它只被调用一次。它生活在公共的最终无效的BlockingCacheProvider开始,它在任何事情开始之前只被调用一次。 – cherouvim 2009-12-09 18:02:19