Ehcache 持久化到磁盘问题

28

我想在Java中使用ehcache做一些事情,我认为这应该非常简单,但是我已经花了足够的时间通过文档来解决问题…

  1. 将一个值写入磁盘持久缓存中。然后关闭程序。

  2. 再次启动程序并读取该值。

以下是我的Java函数:

private static void testCacheWrite() {

  // create the cache manager from our configuration
  URL url = TestBed.class.getClass().getResource("/resource/ehcache.xml");
  CacheManager manager = CacheManager.create(url);
  // check to see if our cache exits, if it doesn't create it
  Cache testCache = null;
  if (!manager.cacheExists("test")) {
    System.out.println("No cache found. Creating cache...");
    int maxElements = 50000;
    testCache = new Cache("test", maxElements,
      MemoryStoreEvictionPolicy.LFU, true, null, true, 60, 30,
      true, Cache.DEFAULT_EXPIRY_THREAD_INTERVAL_SECONDS, null);
    manager.addCache(testCache);
    // add an element to persist
    Element el = new Element("key", "value");
    testCache.put(el);
    testCache.flush();
    System.out.println("Cache to disk. Cache size on disk: " +
      testCache.getDiskStoreSize());
  } else {
    // cache exists so load it
    testCache = manager.getCache("test");
    Element el = testCache.get("key");
    if (null == el) {
      System.out.print("Value was null");
      return;
    }
    String value = (String) el.getObjectValue();
    System.out.println("Value is: " + value);
  }
  manager.shutdown();
}

以下是我的缓存配置(ehcache.xml):

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
  <diskStore path="C:/mycache"/><!-- java.io.tmpdir -->
  <defaultCache
    maxElementsInMemory="10000"
    eternal="true"
    timeToIdleSeconds="120"
    timeToLiveSeconds="120"
    overflowToDisk="true"
    maxElementsOnDisk="10000000"
    diskPersistent="true"
    diskExpiryThreadIntervalSeconds="120"
    memoryStoreEvictionPolicy="LRU" />
</ehcache>

尽管第一次运行后我在磁盘上看到了test.index和test.data文件,但该函数的输出始终如下(似乎从未从磁盘加载缓存):

未找到缓存。正在创建缓存...
缓存到磁盘。磁盘上的缓存大小为2

我可能在这里做了一些愚蠢的事情,但我不确定是什么!
8个回答

18

好的,我用配置文件来配置我的缓存,解决了这个问题。这是更新后的配置:

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">

    <diskStore path="C:/mycache" />

    <defaultCache
        maxElementsInMemory="10000" 
        eternal="true"
        timeToIdleSeconds="120" 
        timeToLiveSeconds="120" 
        overflowToDisk="true"
        maxElementsOnDisk="10000000" 
        diskPersistent="true"
        diskExpiryThreadIntervalSeconds="120" 
        memoryStoreEvictionPolicy="LRU" />

    <cache 
        name="test" 
        maxElementsInMemory="500" 
        eternal="true"
        overflowToDisk="true" 
        timeToIdleSeconds="300" 
        timeToLiveSeconds="600"
        diskPersistent="true" 
        diskExpiryThreadIntervalSeconds="1"
        memoryStoreEvictionPolicy="LFU" />

</ehcache>

所以基本上我没有使用构造函数来定义缓存。

我想这样应该可以工作,但我仍然想知道为什么通过编程定义的缓存不能在磁盘上持久化(尤其是它们仍然被写入磁盘!)。

谢谢大家的评论。


您能帮我解决这个问题吗?https://stackoverflow.com/questions/45295279/spring-ehcache-disk-persistent-issue - Emil

5

在花费了一些时间与调试器相处之后,我相信我有一个关于OP的答案。

问题(至少从我所见)集中于非集群磁盘缓存文件以及它们如何被读回。在文件net.sf.ehcache.store.compound.factories.DiskPersistentStorageFactory.java中,该方法:

public DiskPersistentStorageFactory(Ehcache cache, String diskPath) {
    super(getDataFile(diskPath, cache), cache.getCacheConfiguration().getDiskExpiryThreadIntervalSeconds(),
            cache.getCacheConfiguration().getDiskSpoolBufferSizeMB(), cache.getCacheEventNotificationService(), false);

    indexFile = new File(getDataFile().getParentFile(), getIndexFileName(cache));
    flushTask = new IndexWriteTask(indexFile, cache.getCacheConfiguration().isClearOnFlush());

    if (!getDataFile().exists() || (getDataFile().length() == 0)) {
        LOG.debug("Matching data file missing (or empty) for index file. Deleting index file " + indexFile);
        indexFile.delete();
    } else if (getDataFile().exists() && indexFile.exists()) {
        if (getDataFile().lastModified() > (indexFile.lastModified() + TimeUnit.SECONDS.toMillis(1))) {
            LOG.warn("The index for data file {} is out of date, probably due to an unclean shutdown. " 
                    + "Deleting index file {}", getDataFile(), indexFile);
            indexFile.delete();
        }
    }

    diskCapacity = cache.getCacheConfiguration().getMaxElementsOnDisk();
    memoryCapacity = cache.getCacheConfiguration().getMaxElementsInMemory();
    memoryPolicy = determineEvictionPolicy(cache.getCacheConfiguration());
}

检查数据文件的时间戳。我遇到的问题是,无论我如何关闭缓存/管理器,文件都无法正确同步。我的快速而简单的解决方法是将数据文件的时间调整为刚好超过索引文件上的时间戳:

File index = new File( path, name + ".index" );
File data  = new File( path, name + ".data"  );

data.setLastModified( index.lastModified() + 1 );

虽然这不太优雅,但它满足了我的需求,因为我们的项目使用集群缓存,这使我能够使用持久缓存进行独立调试...而无需实际在本地运行Terracotta。

一个注意点是,对于非集群缓存,在每次put()和remove()之后都需要flush()以保持磁盘映像的新鲜度,特别是在调试时由于没有关闭支持,你只能“拔掉插头”。


1
不错的发现。至少我现在知道为什么会发生这种情况了。 - hross

4
这个问题花了我一些时间才弄清楚,但基本上需要做的是相应地创建CacheManager。
如果您以与xml中创建方式相同的方式创建缓存管理器和缓存,则会起作用。
net.sf.ehcache.CacheManager manager = net.sf.ehcache.CacheManager
        .create(new Configuration().diskStore(
            new DiskStoreConfiguration().path("C:/mycache")
        )
        .cache(new CacheConfiguration()
            .name(testName)
            .eternal(true)
            .maxBytesLocalHeap(10000, MemoryUnit.BYTES)
            .maxBytesLocalDisk(1000000, MemoryUnit.BYTES)
            .diskExpiryThreadIntervalSeconds(0)
            .diskPersistent(true)));

我认为这个答案不适用。这个问题特别涉及到磁盘持久缓存,在它们被关闭后的第二次启动时无法持久化。 - hross

2

2
是的,我也同意这一点。然而,如果应用程序突然终止,关闭缓存管理器并不总是可能的。我们最终采取的做法是启动一个定时线程,在缓存上调用flush()方法。但是请勿在非持久性缓存上执行此操作,因为它会将其清除。 - scharette

1

我认为你应该删除manager.cacheExists(..)测试,并使用testCache = manager.getCache("test");创建缓存,而不是使用new Cache(..)。即使您的缓存是磁盘持久性的,在第一次获取它之前它也不存在。(至少我是这么认为的,因为我只使用getCache(..),它正好可以满足您的需求)

注意:

您还可以添加类似以下内容以确保缓存存在:

Cache cache = manager.getCache(name);
if (cache == null) {
    throw new NullPointerException(String.format("no cache with name %s defined, please configure it in %s", name, url));
}

注意2: 如果您的配置文件名为ehcache.xml,则不应使用CacheManager.create(url)。而是使用CacheManager单例: 我认为我混淆了CacheManager.create(url)new CacheManager(url)的使用。 但仍然应该对ehcache.xml使用单例,并对其他任何内容使用new CacheManager(url)
// ehcache.xml - shared between different invocations
CacheManager defaultManager = CacheManager.getInstance();
// others - avoid calling twice with same argument
CacheManager manager = CacheManager.create(url);

使用CacheManager.create(..)有问题,因为如果之前调用了任何create(..)方法或getInstance(),那么它可能会完全忽略传递的URL:

public static CacheManager create(URL configurationFileURL) throws CacheException {
    synchronized (CacheManager.class) {
        if (singleton == null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Creating new CacheManager with config URL: " + configurationFileURL);
            }
            singleton = new CacheManager(configurationFileURL);

        }
        return singleton;
    }
}

这就是为什么我不建议使用任何CacheManager.create(..)方法。请使用CacheManager.getInstance()new CacheManager(url)


如果 manager.getCache("test") 不返回 null,那么 manager.cacheExists(..) 应该为 true - Pascal Thivent
@Pascal,我刚刚看了代码,你是对的。尽管如此,这个检查是不必要的。所以@skaffman可能是对的,ehcache.xml没有被正确地定位。 - sfussenegger
OP正在以编程方式创建缓存,所以对我来说,检查是完全正常的(第一次,缓存不存在)。然后,关于您的Note 2,即使配置文件名为ehcache.xml(顺便说一下,在这里ehcache.xml不位于类路径的根目录),使用CacheManager.create(url)有什么问题? - Pascal Thivent
@sfussenegger 我不同意。对我来说,总是有一个 ehcache.xml,即使是故障安全的那个,而且 OP 可能想要配置 DiskStore 路径。因此,通过编程方式创建 Cache 并不排除使用 CacheManager 的配置文件。 - Pascal Thivent
该死...第二次(或第三次?)阅读后,我觉得我真的搞砸了 :) 对不起... - sfussenegger
显示剩余5条评论

1

如果您的磁盘缓存始终为空,请注意确保缓存中的元素可序列化。ehcache会记录日志,如果不是这种情况,但我的日志设置没有打印出这些日志条目。


1
我曾经遇到并解决了类似的问题。
我想将ehcache配置为在磁盘上持久化给定缓存中的元素。 但我只想在本地环境中这样做(生产环境使用“分布式”持久性),因此当应用程序启动时(在我的情况下是Web应用程序),我会通过编程方式切换配置。
File configurationFile = new File(event.getServletContext().getRealPath(EHCACHE_CONFIG_PATH));    
Configuration configuration = ConfigurationFactory.parseConfiguration(configurationFile);

//...doing other stuff here...

CacheConfiguration cacheConfiguration = configuration.getCacheConfigurations().get("mycachename");
if(localEnvironment){    
    cacheConfiguration.addPersistence(new PersistenceConfiguration().strategy(Strategy.DISTRIBUTED));
}else{
    //siteCacheConfiguration.addPersistence(new PersistenceConfiguration().strategy(Strategy.LOCALRESTARTABLE));
    //deprecated lines..
    siteCacheConfiguration.setDiskPersistent(true);
    siteCacheConfiguration.setOverflowToDisk(true);
}

我遇到了一个问题,涉及到以下注释行:siteCacheConfiguration.addPersistence(new PersistenceConfiguration().strategy(Strategy.LOCALRESTARTABLE)),实际上,如果你在没有企业版jar的情况下使用Strategy.LOCALRESTARTABLE,Ehcache代码(我正在使用ehcache-2.6.11)会抛出异常:

CacheException: You must use an enterprise version of Ehcache to successfully enable enterprise persistence.

深入研究代码后,我意识到这两行(已弃用)的功能相同,避免了企业版异常。
siteCacheConfiguration.setDiskPersistent(true);
siteCacheConfiguration.setOverflowToDisk(true);

记得在应用程序关闭时添加CacheManager.getInstance().shutdown()

希望这能帮到你。


这通常很正常,除非您的应用程序/进程被操作系统使用SIGKILL杀死。在这种情况下,关闭挂钩将不会被执行,并且您持久化的缓存文件(foo.data和foo.index)很可能会损坏,并且不会在启动时重新填充缓存。相反,它们将被丢弃和删除,并且将失去持久化信息。显式调用CacheManager.getCache("foo").flush()可以在某种程度上帮助缓解此问题。 - ibai

0
我想这个方法可以行得通,但我仍然想知道为什么以编程方式定义的缓存不能在磁盘上持久化(特别是它们仍然被写入磁盘!)
据我了解,通过编程创建的缓存(即未在ehcache.xml中声明的缓存)可以使用DiskStore,DiskStore本身可以是持久的,但这并不意味着该缓存会在重新启动时自动加载到CacheManager中。实际上,我认为先前提到的文件并不包含缓存参数。
但是,如果您使用相同的参数以编程方式“重新创建”缓存,则可以从DiskStore中找回以前缓存的条目。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接