我的目标是在内存中缓存数据 60 秒。一旦从缓存中再次读取该条目,我想将其从缓存中删除(只允许单次读取)。
如果这 60 秒同时过期并且条目在缓存中仍然可用,我想将条目写入数据库。
有没有现有的技术/spring/apache框架已经提供了这样的缓存? (旁注:对于这样一个简单的用例,我不想使用复杂的库,如 redis、ehcache 等)。
如果手动设置,我会做如下。但可能有更好的选择?
@Service
public class WriteBehindCache {
static class ObjectEntry {
Object data;
LocalDateTime timestamp;
public ObjectEntry(Object data) {
this.data = data;
timestamp = LocalDateTime.now();
}
}
Map<String, ObjectEntry> cache = new ConcurrentHashMap<>();
//batch every minute
@Scheduled(fixedRate = 60000)
public void writeBehind() {
LocalDateTime now = LocalDateTime.now();
List<ObjectEntry> outdated = cache.values()
.filter(entry -> entry.getValue().timestamp.plusSeconds(60).isBefore(now))
.collect(Collectors.toList());
databaseService.persist(outdated);
cache.removeAll(outdated); //pseudocode
}
//always keep most recent entry
public void add(String key, Object data) {
cache.put(key, new ObjectEntry(data));
}
//fallback lookup to database if cache is empty
public Object get(String key) {
ObjectEntry entry = cache.remove(key);
if (entry == null) {
entry = databaseService.query(key);
if (entry != null) databaseService.remove(entry);
}
return entry;
}
}
最佳答案
您的解决方案有两个问题:
- 您正在进行顺序扫描以持久化,当有很多条目时,这会变得很昂贵
- 代码有竞争条件
- 由于竞争条件,代码不能满足您的要求。可以构造一个并发访问序列,其中一个条目从缓存中删除,但也被写入数据库
Is there any existing technology/spring/apache framework that already offers such a cache? (sidenote: I don't want to use complex libraries like redis, ehcache etc for such a simple usecase).
我认为你可以基于ConcurrentHashMap
解决并发问题。但我不知道超时的优雅方式。不过,一个可能的解决方案是使用缓存库。我想提供一个基于 cache2k 的示例它并不重(大约 400k jar )并且还有其他不错的用例。此外,还有对 Spring 缓存抽象的良好支持。
public static class WriteBehindCache {
Cache<String, Object> cache = Cache2kBuilder.of(String.class, Object.class)
.addListener((CacheEntryExpiredListener<String, Object>) (cache, entry)
-> persist(entry.getKey(), entry.getValue()))
.expireAfterWrite(60, TimeUnit.SECONDS)
.build();
public void add(String key, Object data) {
cache.put(key, data);
}
public Object get(String key) {
return cache.invoke(key, e -> {
if (e.exists()) {
Object v = e.getValue();
e.remove();
return v;
}
return loadAndRemove(e.getKey());
});
}
// stubs
protected void persist(String key, Object value) {
}
protected Object loadAndRemove(String key) {
return null;
}
}
通过这种连接,缓存会阻止对一个条目的并发操作,因此可以肯定一次只有一个数据库操作对一个条目运行。
您可以使用与其他缓存库类似的方式来完成此操作。使用 JCache/JSR107 API,代码看起来几乎相同。
更“轻松”的方法是使用 jhalterman 的 expiringmap
就我个人而言,我认为每个开发人员的工具箱中都应该有一个缓存。但是,我是 cache2k 的作者。当然,我必须这么说。
关于java - 如何创建从内存到数据库的后写缓存?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52850064/