番石榴缓存的预加载值
我有一个要求,我们从数据库中加载静态数据以在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 对外界不可见。