当我计划有效地缓存多个值时,我应该如何实现Guava缓存?

时间:2015-07-23 00:18:46

标签: java caching guava

我有一个拥有Guava LoadingCache<String, Integer>的Java类,在那个缓存中,我打算存储两件事:活跃员工当天工作的平均时间和效率。我正在缓存这些值,因为每次请求进入时计算都会很昂贵。而且,每分钟都会刷新缓存的内容(refreshAfterWrite)。

我想在这种情况下使用CacheLoader,但是,其加载方法只为每个键加载一个值。在我的CacheLoader中,我打算做以下事情:

private Service service = new Service();

public Integer load(String key) throws Exception {
    if (key.equals("employeeAvg"))
        return calculateEmployeeAvg(service.getAllEmployees());

    if (key.equals("employeeEff"))
        return calculateEmployeeEff(service.getAllEmployees());

    return -1;
}

对我来说,我发现效率非常低,因为为了加载这两个值,我必须调用service.getAllEmployees()两次,因为如果我错了,请纠正我,CacheLoader应该是无状态的。

这使我想到使用LoadingCache.put(key, value)方法,所以我可以创建一个实用程序方法,调用service.getAllEmployees()一次并动态计算值。但是,如果我使用LoadingCache.put(),我将不会拥有refreshAfterWrite功能,因为它依赖于缓存加载器。

如何提高效率?

2 个答案:

答案 0 :(得分:3)

似乎您的问题源于使用字符串来表示值类型(Effective Java Item 50)。相反,请考虑定义存储此数据的正确值类型,并使用memoizing Supplier来避免重新计算它们。

public static class EmployeeStatistics {
  private final int average;
  private final int efficiency;
  // constructor, getters and setters
}

Supplier<EmployeeStatistics> statistics = Suppliers.memoize(
    new Supplier<EmployeeStatistics>() {
  @Override
  public EmployeeStatistics get() {
    List<Employee> employees = new Service().getAllEmployees();
    return new EmployeeStatistics(
        calculateEmployeeAvg(employees),
        calculateEmployeeEff(employees));
  }});

您甚至可以在EmployeeStatistics中移动这些计算方法,只需将所有员工传递给构造函数,然后让它计算相应的数据。

如果您需要配置超过Suppliers.memoize()Suppliers.memoizeWithExpiration()的缓存行为,请考虑这种类似的模式,这会隐藏您在内部使用Cache这一事实Supplier

Supplier<EmployeeStatistics> statistics = new Supplier<EmployeeStatistics>() {
  private final Object key = new Object();
  private final LoadingCache<Object, EmployeeStatistics> cache =
      CacheBuilder.newBuilder()
        // configure your builder
        .build(
           new CacheLoader<Object, EmployeeStatistics>() {
             public EmployeeStatistics load(Object key) {
               // same behavior as the Supplier above
             }});

  @Override
  public EmployeeStatistics get() {
    return cache.get(key);
  }};

答案 1 :(得分:2)

  

但是,如果我使用LoadingCache.put(),我就不会拥有refreshAfterWrite功能,因为它依赖于缓存加载器。

我不确定,但您可以从load方法中调用它。我的意思是,像你一样计算请求的值,在另一个计算put。然而,这感觉很糟糕。

如果service.getAllEmployees很贵,那么你可以缓存它。如果calculateEmployeeAvgcalculateEmployeeEff都很便宜,那么在需要时重新计算它们。否则,看起来你可以使用两个缓存。

我想,一次计算两个值的方法可能是一个合理的解决方案。创建一个类似于Pair的类,聚合它们并将其用作缓存值。只有一把钥匙。

关于你自己的解决方案,它可能与

一样微不足道
class EmployeeStatsCache {
    private long validUntil;
    private List<Employee> employeeList;
    private Integer employeeAvg;
    private Integer employeeEff;

    private boolean isValid() {
        return System.currentTimeMillis() <= validUntil;
    }

    private synchronized List<Employee> getEmployeeList() {
        if (!isValid || employeeList==null) {
            employeeList = service.getAllEmployees();
            validUntil = System.currentTimeMillis() + VALIDITY_MILLIS;
        }
        return employeeList;
    }

    public synchronized int getEmployeeAvg() {
        if (!isValid || employeeAvg==null) {
             employeeAvg = calculateEmployeeAvg(getEmployeeList());
        }
        return employeeAvg;
    }

    public synchronized int getEmployeeEff() {
        if (!isValid || employeeAvg==null) {
             employeeAvg = calculateEmployeeEff(getEmployeeList());
        }
        return employeeAvg;
    }
}

您可能希望在私有最终字段上进行同步,而不是synchronized种方法。还有其他可能性(例如Atomic*),但基本设计可能比调整番石榴Cache更简单。

现在,我在Guava看到了Suppliers#memoizeWithExpiration。这可能更简单。