java - 如何创建从内存到数据库的后写缓存?

标签 java spring caching

我的目标是在内存中缓存数据 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/

相关文章:

java - 系统在线或离线的解决方案

java - 在选项卡 View 中交换 View 寻呼机时增加 Recyclerview 的项目

java - 以最小的复杂性将值匹配到最接近的值范围

java - Spring boot + tomcat 8.5 + mongoDB,AsyncRequestTimeoutException

Angular2 和 RxJS - 缓存接受参数的服务

java - 根据选定的 JComboBox 项目更改 JTextArea 颜色

java - 在 ItemReader/Processor/Writer 中访问 StepExecution/JobExecution 的最佳方式

java - 将应用程序正确划分为模块

javascript - 如何让chrome/浏览器知道javascript代码已经改变?

caching - Safari/WebKit 中是否有相当于 Ctrl-Shift-R 的功能?