【问题标题】:ExpireAfterWrite does not seem to workExpireAfterWrite 似乎不起作用
【发布时间】:2015-01-10 15:31:08
【问题描述】:

我正在尝试使用 Google 的 Guava Cache,我只是用 expireAfterWrite 以 1 分钟的间隔对其进行测试。在我正在处理的应用程序中,有数百个用户。所以我的理解是,当第一个人访问缓存时,我们从数据库中填充它,然后一分钟计时器应该开始倒计时。现在缓存已满。我们总是会在那一分钟内从缓存中获取它。一分钟后,它将过期,我们再次从数据库中获取它。

但是,似乎每次我在一分钟内访问缓存时,计时器都会重置。这是为什么?如果我在一分钟内没有访问缓存,缓存就会过期并且工作正常。

代码:

 Cache<Long, List<someObj>> cacheMap = CacheBuilder.newBuilder().maximumSize(maxSize).expireAfterWrite(maxTime, TimeUnit.MINUTES).removalListener(new RemovalListener<Long, List<someObj>>() {
    public void onRemoval(RemovalNotification<Long, List<someObj>> notification) {
        if (notification.getCause() == RemovalCause.EXPIRED) {
            logger.info("Value " + notification.getValue() + " has been expired");
        } else {
            logger.info("Just removed for some reason");
        }
    }
}).build();

...
//Convert to ConcurrentHashMap
cacheMap = someCache.asMap();

...


public List<someObj> getAllData(final Long orgId, final User user) {
    // Get requests from cache
    List<Request> cacheList = overdueSlaMap.get(orgId);

    // Fill cached requests if null
    cacheList = fillCacheIfNecessary(orgId, cacheList, overdueSlaMap);  

    return cacheList;
}

//Strategy for reading and writing to cache
//This method is fine. On first load, cache is empty so we fill it from database. Wait one minute, value expires and cache is empty again so we get from cache. 
//However, everytime I refresh the page the one minute timer starts again. Surely, the one minute timer within the Guava Cache should not refresh regardless of how many times I access the cache within that one minute period. 
 private List<someObj> fillCacheIfNecessary(List<someObj> cacheList, ConcurrentMap<Long, List<someObj>> cacheMap) {

        // If the cache is empty then we will try to fill it from the database.
        if (cacheList == null) {
            logger.info("populating cache");
            List<someObj> objList = new ArrayList<someObj>();

            // if bla bla
            if (such and such) {
                objList = service.getFromDatabase(someArg);
            } else {
                objList = service.getFromDatabase(anotherarg);
            }

            // Concurrently lock the new cacheList retrieved from the database.
            List<someObj> newValue = Collections.unmodifiableList(objList);

            // Only insert if new value does not exist in cache map
            List<someObj> oldValue = cacheMap.putIfAbsent(orgId, newValue);

            // If old value already exists then we use the old value otherwise we use the new value
            cacheList = ((oldValue != null && !oldValue.isEmpty()) ? oldValue : newValue);
        }
        return cacheList;

    }

编辑

我正在从控制器调用缓存:

public ModelAndView getNotifications(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
    User user = getCurrentUser();
    CacheManager cacheManager = new CacheManager();
    List<someObj> objList = new ArrayList<Request>();

    // Get from database
    if (bla bla) {
        objList = getFromDatabase(user);
    }
    // Get from cache
    else {
        Long orgId = user.getUserOrganisation().getId();
        objList = cacheManager.getAllData(orgId, user);      
    }

    return new ModelAndView(SOMEVIEW);
}

它将有用的数据传递给将其传递给调用数据库的方法的方法。但是,我现在正在尝试重构我的代码以使用 LoadingCache 但这需要我重写 load() 方法,该方法只使用一个参数,这是关键。我需要的不仅仅是键,我需要调用控制器中的 cacheManager,所以它调用了适当的方法。什么是加载方法?什么时候使用?何时调用以及多久调用一次?它会使我的其他方法无效吗?

【问题讨论】:

    标签: java caching guava cache-expiration evict


    【解决方案1】:

    我相信您的问题是您使用从asMap 返回的Map 作为缓存。 asMap 记录为 Returns a view of the entries stored in this cache as a thread-safe map。这里没有任何内容表明您可以使用Map 作为您的缓存。我建议更新您的代码以使用Cache API 并直接在缓存上调用putget(key, Callable)

    您可能需要考虑使用LoadingCache,因为我发现 API 更简单。

    编辑

    所以首先,只有在缓存中不存在 key 时才会调用 load 方法。缓存调用 load 方法来获取应该为它们的键返回的值,然后缓存该值以供下次使用。使用CacheLoaderCallable 的好处之一是检索数据的调用是线程安全的,因为即使同时对同一个键进行多次调用,每个键也只会调用一次.

    如果您需要传递更多信息,请创建一个包装器对象:

     public class MyKey{
           final Long orgId; // true key value
           final User user; // other values needed
    
           // implement equals and hashCode to ONLY use orgId. This will ensure that
           // the same cached value is returned for all requests with the same
           // orgId which is the ACTUAL key.
      }
    

    您也可以使用常规 Cache 执行相同的操作,并创建一个 Callable 包装所需的值。

    我建议CacheLoader 使fillCacheIfNecessary 无效。对cache.get() 的简单调用将在内部确定该值是必须被检索还是已经存在于缓存中。它正在为你做get, check if null, retrieve 的工作。

    【讨论】:

    • 但它也在 JavaDocs 中说,“对地图的修改直接影响缓存。”
    • 对不起,你是对的。但是,请尝试使用 LoadingCache 或 get(key, Callable),因为它是首选方法。如果这不起作用,请告诉我。
    • 您需要能够实现CacheLoader。为此,必须从传递给缓存的键和CacheLoader 中获取确定如何获取值所需的所有信息。您可以将多个值包装到您的键中(并实现 equals / hashCode 以仅使用您的真实键值)或从现有键中确定它。看起来fillCacheIfNecessary 从传递的orgId 确定参数。所以CacheLoader 应该能够做同样的事情。 CacheLoader 可以调用类中的其他实例方法来寻求帮助。
    • 回答中的更新
    • 正确。这就是使用 LoadingCache 的意义所在。它会根据需要为您调用 CacheLoader 来填充缓存。
    猜你喜欢
    • 1970-01-01
    • 2016-11-29
    • 2016-02-01
    • 2020-09-23
    • 2010-12-05
    • 2011-06-14
    • 2016-02-24
    • 2011-01-18
    • 2018-06-20
    相关资源
    最近更新 更多