我有一个高流量的网站,我使用休眠。我还使用ehcache来缓存生成页面所需的一些实体和查询。
问题是“并行缓存未命中”,长期解释是当应用程序启动并且缓存区域很冷时,每个缓存区域被不同的线程多次填充(而不是仅一次),因为该站点正在被命中许多用户同时使用。此外,当某些缓存区域无效时,由于相同的原因,它会被重新填充多次。 我怎么能避免这个?
我通过向hibernate.cache.provider_class提供我自己的实现来设法convert 1 entity and 1 query cache to a BlockingCache但是BlockingCache的语义似乎不起作用。甚至最糟糕的是BlockingCache死锁(块)和应用程序完全挂起。线程转储显示在get操作上阻塞处理在BlockingCache的互斥锁上。
所以,问题是,Hibernate是否支持这种用途?
如果没有,你如何在生产中解决这个问题?
编辑: hibernate.cache.provider_class 指向我的自定义缓存提供程序,该提供程序是SingletonEhCacheProvider的复制粘贴,并在开始结束时( )方法(在第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 :(得分:5)
我不太确定,但是:
它允许并发读取访问 已经在缓存中的元素。如果 element为null,其他读取将为 阻止直到具有相同的元素 密钥被放入缓存中。
这不意味着Hibernate会等到其他线程将对象放入缓存吗?这就是你观察到的,对吧?
Hib和缓存的工作原理如下:
因此,如果对象不在缓存中(未通过某些先前的更新操作放置在那里),Hib将永远等待1)。
我认为你需要一个缓存变体,其中线程只在短时间内等待一个对象。例如。 100毫秒。如果没有到达对象,则线程应该为null(因此Hibernate将从DB加载对象并放入缓存中)。
实际上,更好的逻辑是:
(我们不能永远等待2,因为线程可能无法将对象放入缓存 - 由于异常)。
如果BlockingCache不支持此行为,则需要自己实现缓存。我过去做过,这并不难 - 主要的方法是get()和put()(虽然API显然已经增长了)。
<强>更新强>
实际上,我刚刚阅读了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");
是同步的吗?如果多个请求同时出现,那么只有一个缓存实例吗?
答案 1 :(得分:1)
这个问题上最大的改进是ehcache现在(从2.1开始)supports the transactional
hibernate cache policy。这极大地缓解了本期中描述的问题。
为了更进一步(在访问相同的查询缓存区域时锁定线程),需要实现QueryTranslatorFactory来返回自定义(扩展)QueryTranslatorImpl实例,这将检查查询和参数并在list方法中根据需要进行阻止。这当然与使用hql获取许多实体的查询缓存的具体用例有关。