我们最近开始使用EHCache和Terracotta为应用程序数据运行分布式数据缓存。假设任何客户端节点的堆大约有2GB,而服务器节点有8GB。我们每天产生大量1.5gb的大量数据。
通常,任何一个客户端都将使用特定日期的数据集(大约1.5gb),但服务器显然必须保留所有这些数据集。
我希望到期工作的方式是在LRU的基础上,当堆增长到大。因此,如果任何特定的L1客户端缓存变得太大(比如,从第1天切换到第2天),我希望它从L1中逐出所有day1数据。如果我们获得第6个数据集时L2太大,那么最早的数据集将完全过期。关于什么时间生活或闲置时间值,我真的没有任何意见,所以我不让他们离开。
经过几天的观察后,我认为这不会像我期望的那样有效。例如,我运行了一个L2最大元素为4的测试。我用四个元素填充它。然后我去了第五个元素。 Cache.put()返回时没有异常,但是紧跟在Cache.get()后面的同一个键返回null!
所以我的问题是,我如何让EHCache + Terracotta做我想做的事情,或者至少接近一些事情?
答案 0 :(得分:1)
经过多次搞乱后,事实证明神秘行为取决于L1缓存是否知道L2缓存中的内容。附加的单元测试意味着成对执行,对中的每个元素都在一个单独的JVM中运行,以避免EHCache单例。
我认为行为良好的L1-> L2关系应该起作用的方式是如果你没有错误地执行.put(),你应该能够在没有错误的情况下立即执行相同键的get()一个问题(假设没有其他同时运行的线程正在搞乱东西)。但是,对于Terracotta EHCache,如果.put()需要某些东西被驱逐,那么驱逐就不会发生,而且put()会被忽略,除非L1客户端“知道”可以驱逐的密钥。在* JVM2测试中,它通过使用.getAllWithLoader()找到其他键,然后* JVM2测试按预期工作。
因此,我们为解决原始问题所做的工作就是定期客户端执行.getAllWithLoader()。然后我们可以确定遵循所有驱逐规则。
package com.mine;
import java.io.Serializable;
import junit.framework.TestCase;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.Configuration;
import net.sf.ehcache.config.TerracottaClientConfiguration;
import net.sf.ehcache.config.TerracottaConfiguration;
public class CacheEvictionTest extends TestCase {
private static final String SERVER = "localhost:9510";
private CacheManager cacheManager;
private Cache cache;
private final Serializable keyA = "a";
private final Serializable keyB = "b";
private final Serializable keyC = "c";
@Override
protected void setUp() throws Exception {
Configuration configuration = new Configuration();
TerracottaClientConfiguration terracottaConfig = new TerracottaClientConfiguration();
terracottaConfig.setUrl(SERVER);
configuration.addTerracottaConfig(terracottaConfig);
int maxElementsInMemory = 1;
int maxElementsOnDisk = 2;
long timeToIdleSeconds = 15;
long timeToLiveSeconds = 15;
String cacheName = "TEST_CACHE";
CacheConfiguration amoebaCache = new CacheConfiguration(cacheName, maxElementsInMemory).statistics(true)
.terracotta(new TerracottaConfiguration())
.logging(true)
.maxElementsOnDisk(maxElementsOnDisk)
.timeToIdleSeconds(timeToIdleSeconds)
.timeToLiveSeconds(timeToLiveSeconds);
configuration.addCache(amoebaCache);
configuration.addDefaultCache(new CacheConfiguration("default", 0));
cacheManager = new CacheManager(configuration);
cache = cacheManager.getCache(cacheName);
}
@Override
protected void tearDown() throws Exception {
if (cache != null) {
cache.removeAll();
cache.clearStatistics();
}
}
public void testMaxElementOnDiskEvictionJVM1() throws Exception {
cache.clearStatistics();
cache.put(new Element(keyA, keyA));
cache.put(new Element(keyB, keyB));
cache = null;
}
public void testMaxElementOnDiskEvictionJVM2() throws Exception {
assertEquals(2, cache.getSize());
for (Object key : cache.getKeys()) {
cache.get(key;
}
cache.put(new Element(keyC, keyC));
assertEquals(2, cache.getSize());
assertNotNull(cache.get(keyC));
}
public void testEvictsExpiredElementsFromDiskWhenNotInMemoryAndWeNeverKnewAboutItJVM1() throws Exception {
cache.clearStatistics();
cache.put(new Element(keyA, keyA));
cache = null;
cacheManager = null;
}
public void testEvictsExpiredElementsFromDiskWhenNotInMemoryAndWeNeverKnewAboutItJVM2() throws Exception {
cache.clearStatistics();
for (Object key : cache.getKeys()) {
cache.get(key;
}
assertEquals(0, cache.getStatistics().getEvictionCount());
Thread.sleep(20000);
assertEquals(1, cache.getStatistics().getEvictionCount());
}
}