番石榴缓存的预加载值

2022-09-01 23:51:05

我有一个要求,我们从数据库中加载静态数据以在Java应用程序中使用。任何缓存机制都应具有以下功能:

  • 从数据库加载所有静态数据(一旦加载,此数据将不会更改)
  • 从数据库加载新数据(启动时数据库中存在的数据不会更改,但可以添加新数据)

延迟加载所有数据不是一个选项,因为应用程序将部署到多个地理位置,并且必须与单个数据库进行通信。延迟加载数据会使对特定元素的第一个请求太慢,其中应用程序与数据库位于不同的区域。

我一直在Guava中成功使用MapMaker API,但我们现在正在升级到最新版本,我似乎无法在CacheBuilder API中找到相同的功能;我似乎找不到一种在启动时加载所有数据的干净方法。

一种方法是从数据库中加载所有密钥,然后通过缓存单独加载这些密钥。这将起作用,但会导致对数据库的N + 1调用,这不是我正在寻找的有效解决方案。

public void loadData(){
    List<String> keys = getAllKeys();
    for(String s : keys)
        cache.get(s);
}

或者另一种解决方案是使用 ConcurrentHashMap 实现并自己处理所有线程和缺失的条目?我并不热衷于这样做,因为MapMaker和CacheBuilder API免费提供基于密钥的线程锁定,而无需提供额外的测试。我也非常确定MapMaker / CacheBuilder实现将具有一些我不知道/没有时间调查的效率。

public Element get(String key){
    Lock lock = getObjectLock(key);
    lock.lock();
    try{
        Element ret = map.get(key)
        if(ret == null){
            ret = getElement(key); // database call
            map.put(key, e);
        }
        return ret;
    }finally {
        lock.unlock();
    } 
}

有谁能想到一个更好的解决方案来解决我的两个要求?


功能请求

我不认为预加载缓存是不常见的要求,所以如果 CacheBuilder 提供一个配置选项来预加载缓存,那就太好了。我认为提供一个接口(很像CacheLoader),它将在启动时填充缓存将是一个理想的解决方案,例如:

CacheBuilder.newBuilder().populate(new CachePopulator<String, Element>(){

    @Override
    public Map<String, Element> populate() throws Exception {
        return getAllElements();
    }

}).build(new CacheLoader<String, Element>(){

    @Override
    public Element load(String key) throws Exception {       
        return getElement(key);
    }

});

此实现将允许使用所有相关的元素对象预先填充缓存,同时保持底层的 CustomConcurrentHashMap 对外界不可见。


答案 1

在短期内,我只使用.Cache.asMap().putAll(Map<K, V>)

一旦Guava 11.0发布,您可以使用Cache.getAll(Iterable<K>),它将为所有缺失的元素发出单个批量请求。


答案 2

我会从数据库加载所有静态数据,并使用([Guava 10.0.1允许在Cache.asMap()视图上执行写入操作][1])将其存储在缓存中。cache.asMap().put(key, value)

当然,如果您的缓存配置为逐出条目,则此静态数据可能会被逐出...

CachePopulator的想法很有趣。