Guava Cache 的预加载值
我有一个要求,我们从数据库加载静态数据以在 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);
}
});
此实现将允许缓存预先填充所有相关的 Element 对象,同时保留底层的 CustomConcurrentHashMap对外界不可见。
I have a requirement where we are loading static data from a database for use in a Java application. Any caching mechanism should have the following functionality:
- Load all static data from the database (once loaded, this data will not change)
- Load new data from the database (data present in the database at start-up will not change but it is possible to add new data)
Lazy loading of all the data isn't an option as the application will be deployed to multiple geographical locations and will have to communicate with a single database. Lazy loading the data will make the first request for a specific element too slow where the application is in a different region to the database.
I have been using the MapMaker API in Guava with success but we are now upgrading to the latest release and I can't seem to find the same functionality in the CacheBuilder API; I can't seem to find a clean way of loading all data at start-up.
One way would be to load all keys from the database and load those through the Cache individually. This would work but would result in N+1 calls to the database, which isn't quite the efficient solution I'm looking for.
public void loadData(){
List<String> keys = getAllKeys();
for(String s : keys)
cache.get(s);
}
Or the other solution is to use a ConcurrentHashMap implementation and handle all of the threads and missing entries myself? I'm not keen on doing this as the MapMaker and CacheBuilder APIs provide the key-based thread locking for free without having to provide extra testing. I'm also pretty sure the MapMaker/CacheBuilder implementations will have some efficiencies that I don't know about/haven't got time to investigate.
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();
}
}
Can anyone think of a better solution to my two requirements?
Feature Request
I don't think pre-loading a cache is an uncommon requirement, so it would be nice if the CacheBuilder provided a configuration option to pre-load the cache. I think providing an Interface (much like CacheLoader) which will populate the cache at start-up would be an ideal solution, such as:
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);
}
});
This implementation would allow the Cache to be pre-populated with all relevant Element objects, whilst keeping the underlying CustomConcurrentHashMap non-visible to the outside world.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
短期内我只会使用 Cache.asMap().putAll(Map)。
Guava 11.0 发布后,您可以使用
Cache.getAll(Iterable)
,这将为所有不存在的元素发出单个批量请求。In the short-term I would just use
Cache.asMap().putAll(Map<K, V>)
.Once Guava 11.0 is released you can use
Cache.getAll(Iterable<K>)
, which will issue a single bulk request for all absent elements.我将从数据库加载所有静态数据,并使用
cache.asMap().put(key, value)
将其存储在缓存中([Guava 10.0.1 允许在缓存上进行写入操作。 asMap() 视图][1])。当然,如果您的缓存配置为逐出条目,则此静态数据可能会被逐出......
CachePopulator 的想法很有趣。
I'd load all static data from the DB, and store it in the Cache using
cache.asMap().put(key, value)
([Guava 10.0.1 allows write operations on the Cache.asMap() view][1]).Of course, this static data might get evicted, if your cache is configured to evict entries...
The CachePopulator idea is interesting.