【问题标题】:ehcache persist to disk issuesehcache 持续存在磁盘问题
【发布时间】:2010-12-16 07:43:13
【问题描述】:

我想用 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

我一定是在做一些蠢事,但我不确定是什么!

【问题讨论】:

    标签: java persistence ehcache ehcache-2


    【解决方案1】:

    好的,我解决这个问题的方法是使用配置文件配置我的缓存。这是更新的配置:

    <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>
    

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

    我想这会起作用,但我仍然想知道为什么以编程方式定义的缓存不能保存在磁盘上(尤其是因为它们仍然写入磁盘!)。

    感谢 cmets 伙计们。

    【讨论】:

    【解决方案2】:

    在调试器度过了一段美好的时光后,我相信我对 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() 以保持磁盘映像新鲜,尤其是在调试时,因为当您缺少关闭支持时只需“拔掉插头”。

    【讨论】:

    • 不错的发现。至少我现在知道为什么会这样了。
    【解决方案3】:

    我花了一段时间才弄清楚,但基本上这里需要做的是相应地创建 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)));
    

    【讨论】:

    • 我认为这个答案不适用。此问题专门针对在关闭后第二次启动时不会持续存在的磁盘持久缓存。
    【解决方案4】:

    这可能有点晚了,但我遇到了同样的问题:帮助关闭缓存管理器。

    (来自文档:http://ehcache.org/documentation/code-samples#ways-of-loading-cache-configuration

    关闭单例缓存管理器:

    CacheManager.getInstance().shutdown();
    

    关闭一个 CacheManager 实例,假设你有一个名为 CacheManager 的引用:

    manager.shutdown();
    

    【讨论】:

    • 是的,我支持这个。但是,如果应用程序突然终止,并不总是可以关闭缓存管理器。我们最终要做的是启动一个调度线程,该线程调用缓存上的 flush() 方法。但是不要对非持久缓存执行此操作,因为它会清除它们。
    【解决方案5】:

    我认为您应该删除 manager.cacheExists(..) 测试并简单地使用 testCache = manager.getCache("test"); 而不是使用 new Cache(..) 创建缓存。即使你的缓存是diskPersistent,它也不会存在,直到你第一次得到它。 (至少我是这么认为的,因为我只使用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.xmlnew 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 我刚刚查看了代码,你是对的。然而,检查不应该是必要的。所以@skaffman 可能是对的,而 ehcache.xml 的位置不正确。
    • OP 正在以编程方式创建缓存,所以对我来说,检查非常好(第一次,缓存不存在)。那么,关于你的注2,即使配置文件被称为ehcache.xml,使用CacheManager.create(url)有什么问题(顺便说一句,ehcache.xml不在类路径的根目录下)?
    • 当我将配置文件的位置更改为无效的位置时,我会在控制台输出中收到警告,所以我很确定一切都是这样的。当我忽略检查缓存是否存在时,当我尝试从中加载值时出现空指针异常(经理说第二次复飞时有 0 个缓存)。也许我需要在配置文件中创建缓存而不是编程?
    • @Pascal 只要 ehache 与配置文件一起使用,以编程方式创建缓存本身就是问题所在。这应该留给 CacheManager(以及它从给定文件中读取的配置,而不是在代码和 ehcache.xml 中具有不同的配置)。因此,OP 应该使用 CacheManager 或以编程方式创建缓存。 @hross 请参阅上面的编辑:尝试改用 new CacheManager(url)。可能已经使用另一个 URL 创建了单例 CacheManager。
    【解决方案6】:

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

    【讨论】:

      【解决方案7】:

      我遇到并解决了类似的问题。

      我想将 ehcache 配置为在磁盘上具有给定的缓存持久元素。 但我只想在本地环境中执行此操作(生产环境使用distributed 持久性)所以我在应用程序启动时以编程方式切换配置(在我的情况下是一个 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);
      }
      

      我对@9​​87654323@ 的注释行有疑问,事实上,如果您在没有企业版 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() 可以帮助缓解这个问题。
      【解决方案8】:

      我想这会起作用,但我仍然想知道为什么以编程方式定义的缓存不能持久保存在磁盘上(尤其是因为它们仍然写入磁盘!)

      我的理解是,以编程方式创建的缓存(即未在ehcache.xml 中声明)可以使用本身可以持久的DiskStore,但这并不意味着该缓存将由CacheManager uppon 自动加载重新开始。实际上,我不认为前面提到的文件确实包含缓存参数。

      但是,如果您使用相同的参数以编程方式“重新创建”缓存,您会从 DiskStore 中找到以前缓存的条目。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-09-02
        • 2014-05-05
        • 2020-07-21
        • 2018-01-21
        • 1970-01-01
        • 2018-02-12
        • 1970-01-01
        相关资源
        最近更新 更多